use {
    crate::{
        ctx_map,
        lexer::TokenKind,
        parser::{self, CommentOr, Expr, ExprRef, Pos},
        utils::{self, Ent, EntVec},
        Ident,
    },
    alloc::{string::String, vec::Vec},
    core::{
        cell::Cell,
        num::NonZeroU32,
        ops::{Deref, DerefMut, Range},
    },
    hashbrown::hash_map,
};

macro_rules! impl_deref {
    ($for:ty { $name:ident: $base:ty }) => {
        impl Deref for $for {
            type Target = $base;

            fn deref(&self) -> &Self::Target {
                &self.$name
            }
        }

        impl DerefMut for $for {
            fn deref_mut(&mut self) -> &mut Self::Target {
                &mut self.$name
            }
        }
    };
}

pub type ArrayLen = u32;
pub type Offset = u32;
pub type Size = u32;

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, PartialOrd, Ord)]
pub struct Tuple(pub u32);

impl Tuple {
    const LEN_BITS: u32 = 5;
    const LEN_MASK: usize = Self::MAX_LEN - 1;
    const MAX_LEN: usize = 1 << Self::LEN_BITS;

    pub fn new(pos: usize, len: usize) -> Option<Self> {
        if len >= Self::MAX_LEN {
            return None;
        }

        Some(Self((pos << Self::LEN_BITS | len) as u32))
    }

    pub fn range(self) -> Range<usize> {
        let start = self.0 as usize >> Self::LEN_BITS;
        start..start + self.len()
    }

    pub fn len(self) -> usize {
        self.0 as usize & Self::LEN_MASK
    }

    pub fn is_empty(self) -> bool {
        self.len() == 0
    }

    pub fn empty() -> Self {
        Self(0)
    }

    pub fn args(self) -> ArgIter {
        ArgIter(self.range())
    }
}

pub struct ArgIter(Range<usize>);

pub enum Arg {
    Type(Id),
    Value(Id),
}

impl ArgIter {
    pub(crate) fn next(&mut self, tys: &Types) -> Option<Arg> {
        let ty = tys.ins.args[self.0.next()?];
        if ty == Id::TYPE {
            return Some(Arg::Type(tys.ins.args[self.0.next().unwrap()]));
        }
        Some(Arg::Value(ty))
    }

    pub(crate) fn next_value(&mut self, tys: &Types) -> Option<Id> {
        loop {
            match self.next(tys)? {
                Arg::Type(_) => continue,
                Arg::Value(id) => break Some(id),
            }
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Id(NonZeroU32);

impl From<Id> for i64 {
    fn from(value: Id) -> Self {
        value.0.get() as _
    }
}

impl crate::ctx_map::CtxEntry for Id {
    type Ctx = TypeIns;
    type Key<'a> = SymKey<'a>;

    fn key<'a>(&self, ctx: &'a Self::Ctx) -> Self::Key<'a> {
        match self.expand() {
            Kind::Struct(s) => {
                let st = &ctx.structs[s];
                debug_assert_ne!(st.pos, Pos::MAX);
                SymKey::Type(st.file, st.pos, st.captured)
            }
            Kind::Enum(e) => {
                let en = &ctx.enums[e];
                debug_assert_ne!(en.pos, Pos::MAX);
                SymKey::Type(en.file, en.pos, en.captured)
            }
            Kind::Union(e) => {
                let en = &ctx.unions[e];
                debug_assert_ne!(en.pos, Pos::MAX);
                SymKey::Type(en.file, en.pos, en.captured)
            }
            Kind::Ptr(p) => SymKey::Pointer(&ctx.ptrs[p]),
            Kind::Opt(p) => SymKey::Optional(&ctx.opts[p]),
            Kind::Func(f) => {
                let fc = &ctx.funcs[f];
                if let Some(base) = fc.base {
                    // TODO: merge base and sig
                    SymKey::FuncInst(base, fc.sig.unwrap().args)
                } else {
                    SymKey::Decl(fc.parent, fc.name)
                }
            }
            Kind::Global(g) => {
                let gb = &ctx.globals[g];
                SymKey::Decl(gb.file.into(), gb.name)
            }
            Kind::Slice(s) => SymKey::Array(&ctx.slices[s]),
            Kind::Module(_) | Kind::Builtin(_) => {
                SymKey::Decl(Module::default().into(), Ident::INVALID)
            }
            Kind::Const(c) => SymKey::Constant(&ctx.consts[c]),
        }
    }
}

impl Default for Id {
    fn default() -> Self {
        Self(unsafe { NonZeroU32::new_unchecked(UNDECLARED) })
    }
}

impl Id {
    pub const DINT: Self = Self::UINT;

    pub fn bin_ret(self, op: TokenKind) -> Id {
        if op.is_compatison() {
            Self::BOOL
        } else {
            self
        }
    }

    pub fn is_float(self) -> bool {
        matches!(self.repr(), F32 | F64) || self.is_never()
    }

    pub fn is_signed(self) -> bool {
        matches!(self.repr(), I8..=INT) || self.is_never()
    }

    pub fn is_unsigned(self) -> bool {
        matches!(self.repr(), U8..=UINT) || self.is_never()
    }

    pub fn is_integer(self) -> bool {
        matches!(self.repr(), U8..=INT) || self.is_never()
    }

    pub fn is_never(self) -> bool {
        self == Self::NEVER
    }

    pub fn strip_pointer(self) -> Self {
        match self.expand() {
            Kind::Ptr(_) => Id::UINT,
            _ => self,
        }
    }

    pub fn is_pointer(self) -> bool {
        matches!(self.expand(), Kind::Ptr(_)) || self.is_never()
    }

    pub fn is_optional(self) -> bool {
        matches!(self.expand(), Kind::Opt(_)) || self.is_never()
    }

    pub fn try_upcast(self, ob: Self) -> Option<Self> {
        self.try_upcast_low(ob, false)
    }

    pub fn try_upcast_low(self, ob: Self, coerce_pointer: bool) -> Option<Self> {
        let (oa, ob) = (Self(self.0.min(ob.0)), Self(self.0.max(ob.0)));
        let (a, b) = (oa.strip_pointer(), ob.strip_pointer());
        Some(match () {
            _ if oa == Id::NEVER => ob,
            _ if ob == Id::NEVER => oa,
            _ if oa == ob => oa,
            _ if ob.is_optional() => ob,
            _ if oa.is_pointer() && ob.is_pointer() => return None,
            _ if a.is_signed() && b.is_signed() || a.is_unsigned() && b.is_unsigned() => ob,
            _ if a.is_unsigned() && b.is_signed() && a.repr() - U8 < b.repr() - I8 => ob,
            _ if a.is_unsigned() && b.is_signed() && a.repr() - U8 > b.repr() - I8 => oa,
            _ if oa.is_integer() && ob.is_pointer() && coerce_pointer => ob,
            _ => return None,
        })
    }

    pub fn expand(self) -> Kind {
        Kind::from_ty(self)
    }

    pub const fn repr(self) -> u32 {
        self.0.get()
    }

    pub(crate) fn simple_size(&self) -> Option<Size> {
        Some(match self.expand() {
            Kind::Ptr(_) => 8,
            Kind::Builtin(Builtin(VOID)) => 0,
            Kind::Builtin(Builtin(NEVER)) => 0,
            Kind::Builtin(Builtin(INT | UINT | F64)) => 8,
            Kind::Builtin(Builtin(I32 | U32 | TYPE | F32)) => 4,
            Kind::Builtin(Builtin(I16 | U16)) => 2,
            Kind::Builtin(Builtin(I8 | U8 | BOOL)) => 1,
            _ => return None,
        })
    }

    pub(crate) fn extend(self) -> Self {
        if self.is_signed() {
            Self::INT
        } else if self.is_pointer() {
            self
        } else {
            Self::UINT
        }
    }

    pub(crate) fn loc(&self, tys: &Types) -> Loc {
        match self.expand() {
            Kind::Opt(o)
                if let ty = tys.ins.opts[o].base
                    && ty.loc(tys) == Loc::Reg
                    && (ty.is_pointer() || tys.size_of(ty) < 8) =>
            {
                Loc::Reg
            }
            Kind::Ptr(_) | Kind::Enum(_) | Kind::Builtin(_) => Loc::Reg,
            Kind::Struct(_) | Kind::Union(_) if tys.size_of(*self) == 0 => Loc::Reg,
            Kind::Struct(_) | Kind::Union(_) | Kind::Slice(_) | Kind::Opt(_) => Loc::Stack,
            c @ (Kind::Func(_) | Kind::Global(_) | Kind::Module(_) | Kind::Const(_)) => {
                unreachable!("{c:?}")
            }
        }
    }

    pub(crate) fn has_pointers(&self, tys: &Types) -> bool {
        match self.expand() {
            Kind::Struct(s) => tys.struct_fields(s).iter().any(|f| f.ty.has_pointers(tys)),
            Kind::Ptr(_) => true,
            Kind::Slice(s) => tys.ins.slices[s].len == ArrayLen::MAX,
            _ => false,
        }
    }
}

#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Loc {
    Reg,
    Stack,
}

impl From<u64> for Id {
    fn from(id: u64) -> Self {
        Self(unsafe { NonZeroU32::new_unchecked(id as _) })
    }
}

const fn array_to_lower_case<const N: usize>(array: [u8; N]) -> [u8; N] {
    let mut result = [0; N];
    let mut i = 0;
    while i < N {
        result[i] = array[i].to_ascii_lowercase();
        i += 1;
    }
    result
}
// const string to lower case

macro_rules! builtin_type {
    ($($name:ident;)*) => {
        $(const $name: u32 = ${index(0)} + 1;)*

        mod __lc_names {
            use super::*;
            $(pub const $name: &str = unsafe {
                const LCL: &[u8] = unsafe {
                    &array_to_lower_case(
                        *(stringify!($name).as_ptr() as *const [u8; stringify!($name).len()])
                    )
                };
                core::str::from_utf8_unchecked(LCL)
            };)*
        }

        impl Builtin {
            $(pub const $name: Self = Builtin($name);)*
        }

        impl Id {
            $(pub const $name: Self = Kind::Builtin(Builtin($name)).compress();)*
        }

        impl Kind {
            $(pub const $name: Self = Kind::Builtin(Builtin($name));)*
        }

        pub fn from_str(name: &str) -> Option<Builtin> {
            match name {
                $(__lc_names::$name => Some(Builtin($name)),)*
                _ => None,
            }
        }

        pub fn to_str(ty: Builtin) -> &'static str {
            match ty.0 {
                $($name => __lc_names::$name,)*
                v => unreachable!("invalid type: {}", v),
            }
        }
    };
}

builtin_type! {
    UNDECLARED;
    LEFT_UNREACHABLE;
    RIGHT_UNREACHABLE;
    NEVER;
    VOID;
    TYPE;
    BOOL;
    U8;
    U16;
    U32;
    UINT;
    I8;
    I16;
    I32;
    INT;
    F32;
    F64;
}

macro_rules! type_kind {
    ($(#[$meta:meta])* $vis:vis enum $name:ident {$( $variant:ident, )*}) => {
        crate::utils::decl_ent! {
            $(pub struct $variant(u32);)*
        }

        $(#[$meta])*
        $vis enum $name {
            $($variant($variant),)*
        }

        impl $name {
            const FLAG_BITS: u32 = (${count($variant)} as u32).next_power_of_two().ilog2();
            const FLAG_OFFSET: u32 = core::mem::size_of::<Id>() as u32 * 8 - Self::FLAG_BITS;
            const INDEX_MASK: u32 = (1 << (32 - Self::FLAG_BITS)) - 1;

            $vis fn from_ty(ty: Id) -> Self {
                let (flag, index) = (ty.repr() >> Self::FLAG_OFFSET, ty.repr() & Self::INDEX_MASK);
                match flag {
                    $(${index(0)} => Self::$variant($variant(index)),)*
                    i => unreachable!("{i}"),
                }
            }

            $vis const fn compress(self) -> Id {
                let (index, flag) = match self {
                    $(Self::$variant(index) => (index.0, ${index(0)}),)*
                };
               Id(unsafe { NonZeroU32::new_unchecked((flag << Self::FLAG_OFFSET) | index) })
            }
        }

        $(
            impl From<$variant> for $name {
                fn from(value: $variant) -> Self {
                    Self::$variant(value)
                }
            }

            impl From<$variant> for i64 {
                fn from(value: $variant) -> Self {
                    Id::from(value).into()
                }
            }

            impl From<$variant> for Id {
                fn from(value: $variant) -> Self {
                    $name::$variant(value).compress()
                }
            }
        )*
    };
}

type_kind! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub enum Kind {
        Builtin,
        Struct,
        Enum,
        Union,
        Ptr,
        Slice,
        Opt,
        Func,
        Global,
        Module,
        Const,
    }
}

impl Func {
    pub const ECA: Func = Func(u32::MAX);
    pub const MAIN: Func = Func(u32::MIN);
}

impl Module {
    pub const MAIN: Self = Self(0);
}

impl Default for Module {
    fn default() -> Self {
        Self(u32::MAX)
    }
}

impl TryFrom<Ident> for Builtin {
    type Error = ();

    fn try_from(value: Ident) -> Result<Self, Self::Error> {
        if value.is_null() {
            Ok(Self(value.len()))
        } else {
            Err(())
        }
    }
}

impl Default for Kind {
    fn default() -> Self {
        Id::UNDECLARED.expand()
    }
}

pub struct Display<'a> {
    tys: &'a Types,
    files: &'a [parser::Ast],
    ty: Id,
}

impl<'a> Display<'a> {
    pub fn new(tys: &'a Types, files: &'a [parser::Ast], ty: Id) -> Self {
        Self { tys, files, ty }
    }

    pub fn rety(&self, ty: Id) -> Self {
        Self::new(self.tys, self.files, ty)
    }
}

impl core::fmt::Display for Display<'_> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        use Kind as TK;
        match TK::from_ty(self.ty) {
            TK::Module(idx) => {
                f.write_str("@use(\"")?;
                self.files[idx.index()].path.fmt(f)?;
                f.write_str(")[")?;
                idx.fmt(f)?;
                f.write_str("]")
            }
            TK::Builtin(ty) => f.write_str(to_str(ty)),
            TK::Opt(ty) => {
                f.write_str("?")?;
                self.rety(self.tys.ins.opts[ty].base).fmt(f)
            }
            TK::Ptr(ty) => {
                f.write_str("^")?;
                self.rety(self.tys.ins.ptrs[ty].base).fmt(f)
            }
            TK::Struct(idx) => {
                let record = &self.tys.ins.structs[idx];
                if record.name.is_null() {
                    f.write_str("[")?;
                    idx.fmt(f)?;
                    f.write_str("]{")?;
                    for (i, &StructField { name, ty }) in
                        self.tys.struct_fields(idx).iter().enumerate()
                    {
                        if i != 0 {
                            f.write_str(", ")?;
                        }
                        f.write_str(self.tys.names.ident_str(name))?;
                        f.write_str(": ")?;
                        self.rety(ty).fmt(f)?;
                    }
                    f.write_str("}")
                } else {
                    let file = &self.files[record.file.index()];
                    f.write_str(file.ident_str(record.name))
                }
            }
            TK::Union(idx) => {
                let record = &self.tys.ins.unions[idx];
                if record.name.is_null() {
                    f.write_str("[")?;
                    idx.fmt(f)?;
                    f.write_str("]{")?;
                    for (i, &StructField { name, ty }) in
                        self.tys.union_fields(idx).iter().enumerate()
                    {
                        if i != 0 {
                            f.write_str(", ")?;
                        }
                        f.write_str(self.tys.names.ident_str(name))?;
                        f.write_str(": ")?;
                        self.rety(ty).fmt(f)?;
                    }
                    f.write_str("}")
                } else {
                    let file = &self.files[record.file.index()];
                    f.write_str(file.ident_str(record.name))
                }
            }
            TK::Enum(idx) => {
                let enm = &self.tys.ins.enums[idx];
                debug_assert!(!enm.name.is_null());
                let file = &self.files[enm.file.index()];
                f.write_str(file.ident_str(enm.name))
            }
            TK::Func(idx) => {
                f.write_str("fn")?;
                idx.fmt(f)
            }
            TK::Global(idx) => {
                let global = &self.tys.ins.globals[idx];
                let file = &self.files[global.file.index()];
                f.write_str(file.ident_str(global.name))?;
                f.write_str(" (global)")
            }
            TK::Slice(idx) => {
                let array = self.tys.ins.slices[idx];
                f.write_str("[")?;
                self.rety(array.elem).fmt(f)?;
                if array.len != ArrayLen::MAX {
                    f.write_str("; ")?;
                    array.len.fmt(f)?;
                }
                f.write_str("]")
            }
            TK::Const(idx) => {
                let cnst = &self.tys.ins.consts[idx];
                let file = &self.files[cnst.file.index()];
                f.write_str(file.ident_str(cnst.name))?;
                f.write_str(" (const)")
            }
        }
    }
}

#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub enum SymKey<'a> {
    Pointer(&'a PtrData),
    Optional(&'a OptData),
    Type(Module, Pos, Tuple),
    FuncInst(Func, Tuple),
    Decl(Id, Ident),
    Array(&'a ArrayData),
    Constant(&'a ConstData),
}

#[derive(Clone, Copy)]
pub struct Sig {
    pub args: Tuple,
    pub ret: Id,
}

#[derive(Default, Clone, Copy)]
pub struct FuncData {
    pub file: Module,
    pub parent: Id,
    pub name: Ident,
    pub base: Option<Func>,
    pub expr: ExprRef,
    pub sig: Option<Sig>,
    pub is_inline: bool,
    pub returns_type: bool,
    pub comp_state: [CompState; 2],
}

#[derive(Default, PartialEq, Eq, Clone, Copy)]
pub enum CompState {
    #[default]
    Dead,
    Queued(usize),
    Compiled,
}

#[derive(Clone, Default)]
pub struct GlobalData {
    pub file: Module,
    pub name: Ident,
    pub ty: Id,
    pub data: Vec<u8>,
}

#[derive(PartialEq, Eq, Hash)]
pub struct ConstData {
    pub ast: ExprRef,
    pub name: Ident,
    pub file: Module,
    pub parent: Id,
}

pub struct EnumField {
    pub name: Ident,
}

#[derive(Default)]
pub struct TypeBase {
    pub file: Module,
    pub parent: Id,
    pub pos: Pos,
    pub name: Ident,
    pub field_start: u32,
    pub captured: Tuple,
    pub ast: ExprRef,
}

#[derive(Default)]
pub struct EnumData {
    pub base: TypeBase,
}

impl_deref!(EnumData { base: TypeBase });

#[derive(Default)]
pub struct UnionData {
    pub base: TypeBase,
    pub size: Cell<Size>,
    pub align: Cell<u8>,
}

impl_deref!(UnionData { base: TypeBase });

pub struct StructField {
    pub name: Ident,
    pub ty: Id,
}

#[derive(Default)]
pub struct StructData {
    pub base: TypeBase,
    pub size: Cell<Size>,
    pub align: Cell<u8>,
    // TODO: make this compact
    pub explicit_alignment: Option<u8>,
}

impl_deref!(StructData { base: TypeBase });

#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct OptData {
    pub base: Id,
}

#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct PtrData {
    pub base: Id,
}

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ArrayData {
    pub elem: Id,
    pub len: ArrayLen,
}

impl ArrayData {
    #[expect(clippy::len_without_is_empty)]
    pub fn len(&self) -> Option<usize> {
        (self.len != ArrayLen::MAX).then_some(self.len as usize)
    }
}

impl ctx_map::CtxEntry for Ident {
    type Ctx = str;
    type Key<'a> = &'a str;

    fn key<'a>(&self, ctx: &'a Self::Ctx) -> Self::Key<'a> {
        unsafe { ctx.get_unchecked(self.range()) }
    }
}

#[derive(Default)]
pub struct IdentInterner {
    lookup: ctx_map::CtxMap<Ident>,
    strings: String,
}

impl IdentInterner {
    pub fn intern(&mut self, ident: &str) -> Ident {
        let (entry, hash) = self.lookup.entry(ident, &self.strings);
        match entry {
            hash_map::RawEntryMut::Occupied(o) => o.get_key_value().0.value,
            hash_map::RawEntryMut::Vacant(v) => {
                let id = Ident::new(self.strings.len() as _, ident.len() as _).unwrap();
                self.strings.push_str(ident);
                v.insert(ctx_map::Key { hash, value: id }, ());
                id
            }
        }
    }

    pub fn ident_str(&self, ident: Ident) -> &str {
        &self.strings[ident.range()]
    }

    pub fn project(&self, ident: &str) -> Option<Ident> {
        self.lookup.get(ident, &self.strings).copied()
    }

    fn clear(&mut self) {
        self.lookup.clear();
        self.strings.clear()
    }
}

#[derive(Default)]
pub struct TypesTmp {
    pub struct_fields: Vec<StructField>,
    pub enum_fields: Vec<EnumField>,
    pub args: Vec<Id>,
}

#[derive(Default)]
pub struct TypeIns {
    pub args: Vec<Id>,
    pub struct_fields: Vec<StructField>,
    pub enum_fields: Vec<EnumField>,
    pub funcs: EntVec<Func, FuncData>,
    pub globals: EntVec<Global, GlobalData>,
    pub consts: EntVec<Const, ConstData>,
    pub structs: EntVec<Struct, StructData>,
    pub enums: EntVec<Enum, EnumData>,
    pub unions: EntVec<Union, UnionData>,
    pub ptrs: EntVec<Ptr, PtrData>,
    pub opts: EntVec<Opt, OptData>,
    pub slices: EntVec<Slice, ArrayData>,
}

pub struct FTask {
    pub file: Module,
    pub id: Func,
    pub ct: bool,
}

pub struct StringRef(pub Global);

impl ctx_map::CtxEntry for StringRef {
    type Ctx = EntVec<Global, GlobalData>;
    type Key<'a> = &'a [u8];

    fn key<'a>(&self, ctx: &'a Self::Ctx) -> Self::Key<'a> {
        &ctx[self.0].data
    }
}

#[derive(Default)]
pub struct Types {
    pub syms: ctx_map::CtxMap<Id>,
    pub names: IdentInterner,
    pub strings: ctx_map::CtxMap<StringRef>,
    pub ins: TypeIns,
    pub tmp: TypesTmp,
    pub tasks: Vec<Option<FTask>>,
}

impl Types {
    pub fn case(&self, ty: Id) -> fn(&str) -> Result<(), &'static str> {
        match ty.expand() {
            Kind::NEVER => |_| Ok(()),
            Kind::Enum(_)
            | Kind::Struct(_)
            | Kind::Union(_)
            | Kind::Builtin(_)
            | Kind::Ptr(_)
            | Kind::Slice(_)
            | Kind::Opt(_) => utils::is_pascal_case,
            Kind::Func(f) if self.ins.funcs[f].returns_type => utils::is_pascal_case,
            Kind::Func(_) | Kind::Global(_) | Kind::Module(_) => utils::is_snake_case,
            Kind::Const(_) => utils::is_screaming_case,
        }
    }

    pub fn pack_args(&mut self, arg_base: usize) -> Option<Tuple> {
        let base = self.ins.args.len();
        self.ins.args.extend(self.tmp.args.drain(arg_base..));
        let needle = &self.ins.args[base..];
        if needle.is_empty() {
            return Some(Tuple::empty());
        }
        let len = needle.len();
        // FIXME: maybe later when this becomes a bottleneck we use more
        // efficient search (SIMD?, indexing?)
        let sp = self.ins.args.windows(needle.len()).position(|val| val == needle).unwrap();
        self.ins.args.truncate((sp + needle.len()).max(base));
        Tuple::new(sp, len)
    }

    pub fn union_fields(&self, union: Union) -> &[StructField] {
        &self.ins.struct_fields[self.union_field_range(union)]
    }

    fn union_field_range(&self, union: Union) -> Range<usize> {
        let start = self.ins.unions[union].field_start as usize;
        let end = self
            .ins
            .unions
            .next(union)
            .map_or(self.ins.struct_fields.len(), |s| s.field_start as usize);
        start..end
    }

    pub fn struct_fields(&self, strct: Struct) -> &[StructField] {
        &self.ins.struct_fields[self.struct_field_range(strct)]
    }

    fn struct_field_range(&self, strct: Struct) -> Range<usize> {
        let start = self.ins.structs[strct].field_start as usize;
        let end = self
            .ins
            .structs
            .next(strct)
            .map_or(self.ins.struct_fields.len(), |s| s.field_start as usize);
        start..end
    }

    pub fn enum_fields(&self, enm: Enum) -> &[EnumField] {
        &self.ins.enum_fields[self.enum_field_range(enm)]
    }

    pub fn enum_field_range(&self, enm: Enum) -> Range<usize> {
        let start = self.ins.enums[enm].field_start as usize;
        let end =
            self.ins.enums.next(enm).map_or(self.ins.enum_fields.len(), |s| s.field_start as usize);
        start..end
    }

    pub fn make_opt(&mut self, base: Id) -> Id {
        self.make_generic_ty(OptData { base }, |ins| &mut ins.opts, |e| SymKey::Optional(e))
    }

    pub fn make_ptr(&mut self, base: Id) -> Id {
        self.make_generic_ty(PtrData { base }, |ins| &mut ins.ptrs, |e| SymKey::Pointer(e))
    }

    pub fn make_array(&mut self, elem: Id, len: ArrayLen) -> Id {
        self.make_generic_ty(ArrayData { elem, len }, |ins| &mut ins.slices, |e| SymKey::Array(e))
    }

    fn make_generic_ty<K: Ent + Into<Id>, T: Copy>(
        &mut self,
        ty: T,
        get_col: fn(&mut TypeIns) -> &mut EntVec<K, T>,
        key: fn(&T) -> SymKey,
    ) -> Id {
        *self.syms.get_or_insert(key(&{ ty }), &mut self.ins, |ins| get_col(ins).push(ty).into())
    }

    pub fn size_of(&self, ty: Id) -> Size {
        match ty.expand() {
            Kind::Slice(arr) => {
                let arr = &self.ins.slices[arr];
                match arr.len {
                    0 => 0,
                    ArrayLen::MAX => 16,
                    len => self.size_of(arr.elem) * len,
                }
            }
            Kind::Struct(stru) => {
                if self.ins.structs[stru].size.get() != 0 {
                    return self.ins.structs[stru].size.get();
                }

                let mut oiter = OffsetIter::new(stru, self);
                while oiter.next(self).is_some() {}
                self.ins.structs[stru].size.set(oiter.offset);
                oiter.offset
            }
            Kind::Union(union) => {
                if self.ins.unions[union].size.get() != 0 {
                    return self.ins.unions[union].size.get();
                }

                let size =
                    self.union_fields(union).iter().map(|f| self.size_of(f.ty)).max().unwrap_or(0);
                self.ins.unions[union].size.set(size);
                size
            }
            Kind::Enum(enm) => (self.enum_field_range(enm).len().ilog2() + 7) / 8,
            Kind::Opt(opt) => {
                let base = self.ins.opts[opt].base;
                if self.nieche_of(base).is_some() {
                    self.size_of(base)
                } else {
                    self.size_of(base) + self.align_of(base)
                }
            }
            _ if let Some(size) = ty.simple_size() => size,
            ty => unimplemented!("size_of: {:?}", ty),
        }
    }

    pub fn align_of(&self, ty: Id) -> Size {
        match ty.expand() {
            Kind::Union(union) => {
                if self.ins.unions[union].align.get() != 0 {
                    return self.ins.unions[union].align.get() as _;
                }
                let align =
                    self.union_fields(union).iter().map(|f| self.align_of(f.ty)).max().unwrap_or(1);
                self.ins.unions[union].align.set(align.try_into().unwrap());
                align
            }
            Kind::Struct(stru) => {
                if self.ins.structs[stru].align.get() != 0 {
                    return self.ins.structs[stru].align.get() as _;
                }
                let align = self.ins.structs[stru].explicit_alignment.map_or_else(
                    || {
                        self.struct_fields(stru)
                            .iter()
                            .map(|&StructField { ty, .. }| self.align_of(ty))
                            .max()
                            .unwrap_or(1)
                    },
                    |a| a as _,
                );
                self.ins.structs[stru].align.set(align.try_into().unwrap());
                align
            }
            Kind::Slice(arr) => {
                let arr = &self.ins.slices[arr];
                match arr.len {
                    ArrayLen::MAX => 8,
                    _ => self.align_of(arr.elem),
                }
            }
            _ => self.size_of(ty).max(1),
        }
    }

    pub fn base_of(&self, ty: Id) -> Option<Id> {
        match ty.expand() {
            Kind::Ptr(p) => Some(self.ins.ptrs[p].base),
            _ => None,
        }
    }

    pub fn inner_of(&self, ty: Id) -> Option<Id> {
        match ty.expand() {
            Kind::Opt(o) => Some(self.ins.opts[o].base),
            _ => None,
        }
    }

    pub fn opt_layout(&self, inner_ty: Id) -> OptLayout {
        match self.nieche_of(inner_ty) {
            Some((_, flag_offset, flag_ty)) => {
                OptLayout { flag_ty, flag_offset, payload_offset: 0 }
            }
            None => OptLayout {
                flag_ty: Id::BOOL,
                flag_offset: 0,
                payload_offset: self.align_of(inner_ty),
            },
        }
    }

    pub fn nieche_of(&self, ty: Id) -> Option<(bool, Offset, Id)> {
        match ty.expand() {
            Kind::Ptr(_) => Some((false, 0, Id::UINT)),
            // TODO: cache this
            Kind::Struct(s) => OffsetIter::new(s, self).into_iter(self).find_map(|(f, off)| {
                self.nieche_of(f.ty).map(|(uninit, o, ty)| (uninit, o + off, ty))
            }),
            _ => None,
        }
    }

    pub fn find_struct_field(&self, s: Struct, name: &str) -> Option<usize> {
        let name = self.names.project(name)?;
        self.struct_fields(s).iter().position(|f| f.name == name)
    }

    pub fn find_union_field(&self, u: Union, name: &str) -> Option<usize> {
        let name = self.names.project(name)?;
        self.union_fields(u).iter().position(|f| f.name == name)
    }

    pub fn clear(&mut self) {
        self.syms.clear();
        self.names.clear();
        self.strings.clear();

        self.ins.funcs.clear();
        self.ins.args.clear();
        self.ins.globals.clear();
        self.ins.structs.clear();
        self.ins.struct_fields.clear();
        self.ins.ptrs.clear();
        self.ins.slices.clear();

        debug_assert_eq!(self.tmp.struct_fields.len(), 0);
        debug_assert_eq!(self.tmp.args.len(), 0);

        debug_assert_eq!(self.tasks.len(), 0);
    }

    fn type_base_of(&self, id: Id) -> Option<&TypeBase> {
        Some(match id.expand() {
            Kind::Struct(s) => &*self.ins.structs[s],
            Kind::Enum(e) => &*self.ins.enums[e],
            Kind::Union(e) => &*self.ins.unions[e],
            Kind::Builtin(_)
            | Kind::Ptr(_)
            | Kind::Slice(_)
            | Kind::Opt(_)
            | Kind::Func(_)
            | Kind::Global(_)
            | Kind::Module(_)
            | Kind::Const(_) => return None,
        })
    }

    pub fn scope_of<'a>(&self, parent: Id, file: &'a parser::Ast) -> Option<&'a [Expr<'a>]> {
        let base = match parent.expand() {
            _ if let Some(base) = self.type_base_of(parent) => base,
            Kind::Module(_) => return Some(file.exprs()),
            _ => return None,
        };

        if let Expr::Struct { fields: [.., CommentOr::Or(Err(scope))], .. }
        | Expr::Union { fields: [.., CommentOr::Or(Err(scope))], .. }
        | Expr::Enum { variants: [.., CommentOr::Or(Err(scope))], .. } = base.ast.get(file)
        {
            Some(scope)
        } else {
            Some(&[])
        }
    }

    pub fn parent_of(&self, ty: Id) -> Option<Id> {
        self.type_base_of(ty).map(|b| b.parent)
    }

    pub fn captures_of<'a>(&self, ty: Id, file: &'a parser::Ast) -> Option<(&'a [Ident], Tuple)> {
        let base = self.type_base_of(ty)?;

        let (Expr::Struct { captured, .. }
        | Expr::Enum { captured, .. }
        | Expr::Union { captured, .. }) = *base.ast.get(file)
        else {
            unreachable!()
        };
        debug_assert_eq!(captured.len(), base.captured.len());
        Some((captured, base.captured))
    }
}

pub struct OptLayout {
    pub flag_ty: Id,
    pub flag_offset: Offset,
    pub payload_offset: Offset,
}

pub struct OffsetIter {
    strct: Struct,
    offset: Offset,
    fields: Range<usize>,
}

impl OffsetIter {
    pub fn new(strct: Struct, tys: &Types) -> Self {
        Self { strct, offset: 0, fields: tys.struct_field_range(strct) }
    }

    pub fn offset_of(tys: &Types, idx: Struct, field: &str) -> Option<(Offset, Id)> {
        let field_id = tys.names.project(field)?;
        OffsetIter::new(idx, tys)
            .into_iter(tys)
            .find(|(f, _)| f.name == field_id)
            .map(|(f, off)| (off, f.ty))
    }

    fn next<'a>(&mut self, tys: &'a Types) -> Option<(&'a StructField, Offset)> {
        let stru = &tys.ins.structs[self.strct];
        let field = &tys.ins.struct_fields[self.fields.next()?];

        let align = stru.explicit_alignment.map_or_else(|| tys.align_of(field.ty), |a| a as u32);
        self.offset = (self.offset + align - 1) & !(align - 1);

        let off = self.offset;
        self.offset += tys.size_of(field.ty);
        Some((field, off))
    }

    pub fn next_ty(&mut self, tys: &Types) -> Option<(Id, Offset)> {
        let (field, off) = self.next(tys)?;
        Some((field.ty, off))
    }

    pub fn into_iter(mut self, tys: &Types) -> impl Iterator<Item = (&StructField, Offset)> {
        core::iter::from_fn(move || self.next(tys))
    }
}