use alloc::format;
use alloc::string::ToString;
use alloc::vec::Vec;

use crate::component::*;
use crate::core::resolve::ResolveCoreType;
use crate::core::{self, ValType};
use crate::names::Namespace;
use crate::token::{Id, Index, Span};
use crate::{Error, kw};

/// Resolve the fields of a component and everything nested within it, changing
/// `Index::Id` to `Index::Num` and expanding alias syntax sugar.
pub fn resolve(component: &mut Component<'_>) -> Result<(), Error> {
    let fields = match &mut component.kind {
        ComponentKind::Text(fields) => fields,
        ComponentKind::Binary(_) => return Ok(()),
    };
    let mut resolver = Resolver::default();
    resolver.fields(component.id, fields)
}

impl<'a> From<Alias<'a>> for ComponentField<'a> {
    fn from(a: Alias<'a>) -> Self {
        Self::Alias(a)
    }
}

impl<'a> From<Alias<'a>> for ModuleTypeDecl<'a> {
    fn from(a: Alias<'a>) -> Self {
        Self::Alias(a)
    }
}

impl<'a> From<Alias<'a>> for ComponentTypeDecl<'a> {
    fn from(a: Alias<'a>) -> Self {
        Self::Alias(a)
    }
}

impl<'a> From<Alias<'a>> for InstanceTypeDecl<'a> {
    fn from(a: Alias<'a>) -> Self {
        Self::Alias(a)
    }
}

#[derive(Default)]
struct Resolver<'a> {
    stack: Vec<ComponentState<'a>>,

    // When a name refers to a definition in an outer scope, we'll need to
    // insert an outer alias before it. This collects the aliases to be
    // inserted during resolution.
    aliases_to_insert: Vec<Alias<'a>>,
}

/// Context structure used to perform name resolution.
#[derive(Default)]
struct ComponentState<'a> {
    id: Option<Id<'a>>,

    // Namespaces within each component. Note that each namespace carries
    // with it information about the signature of the item in that namespace.
    // The signature is later used to synthesize the type of a component and
    // inject type annotations if necessary.
    core_funcs: Namespace<'a>,
    core_globals: Namespace<'a>,
    core_tables: Namespace<'a>,
    core_memories: Namespace<'a>,
    core_types: Namespace<'a>,
    core_tags: Namespace<'a>,
    core_instances: Namespace<'a>,
    core_modules: Namespace<'a>,

    funcs: Namespace<'a>,
    types: Namespace<'a>,
    instances: Namespace<'a>,
    components: Namespace<'a>,
    values: Namespace<'a>,
}

impl<'a> ComponentState<'a> {
    fn new(id: Option<Id<'a>>) -> ComponentState<'a> {
        ComponentState {
            id,
            ..ComponentState::default()
        }
    }

    fn register_item_sig(&mut self, sig: &ItemSig<'a>) -> Result<u32, Error> {
        match &sig.kind {
            ItemSigKind::CoreModule(_) => self.core_modules.register(sig.id, "core module"),
            ItemSigKind::Func(_) => self.funcs.register(sig.id, "func"),
            ItemSigKind::Component(_) => self.components.register(sig.id, "component"),
            ItemSigKind::Instance(_) => self.instances.register(sig.id, "instance"),
            ItemSigKind::Value(_) => self.values.register(sig.id, "value"),
            ItemSigKind::Type(_) => self.types.register(sig.id, "type"),
        }
    }
}

impl<'a> Resolver<'a> {
    fn current(&mut self) -> &mut ComponentState<'a> {
        self.stack
            .last_mut()
            .expect("should have at least one component state")
    }

    fn fields(
        &mut self,
        id: Option<Id<'a>>,
        fields: &mut Vec<ComponentField<'a>>,
    ) -> Result<(), Error> {
        self.stack.push(ComponentState::new(id));
        self.resolve_prepending_aliases(fields, Resolver::field, ComponentState::register)?;
        self.stack.pop();
        Ok(())
    }

    fn resolve_prepending_aliases<T>(
        &mut self,
        fields: &mut Vec<T>,
        resolve: fn(&mut Self, &mut T) -> Result<(), Error>,
        register: fn(&mut ComponentState<'a>, &T) -> Result<(), Error>,
    ) -> Result<(), Error>
    where
        T: From<Alias<'a>>,
    {
        assert!(self.aliases_to_insert.is_empty());

        // Iterate through the fields of the component. We use an index
        // instead of an iterator because we'll be inserting aliases
        // as we go.
        let mut i = 0;
        while i < fields.len() {
            // Resolve names within the field.
            resolve(self, &mut fields[i])?;

            // Name resolution may have emitted some aliases. Insert them before
            // the current definition.
            let amt = self.aliases_to_insert.len();
            fields.splice(i..i, self.aliases_to_insert.drain(..).map(T::from));
            i += amt;

            // Definitions can't refer to themselves or to definitions that appear
            // later in the format. Now that we're done resolving this field,
            // assign it an index for later definitions to refer to.
            register(self.current(), &fields[i])?;

            i += 1;
        }

        Ok(())
    }

    fn field(&mut self, field: &mut ComponentField<'a>) -> Result<(), Error> {
        match field {
            ComponentField::CoreModule(m) => self.core_module(m),
            ComponentField::CoreInstance(i) => self.core_instance(i),
            ComponentField::CoreType(t) => self.core_ty(t),
            ComponentField::CoreRec(t) => self.core_rec(t),
            ComponentField::Component(c) => self.component(c),
            ComponentField::Instance(i) => self.instance(i),
            ComponentField::Alias(a) => self.alias(a),
            ComponentField::Type(t) => self.ty(t),
            ComponentField::CanonicalFunc(f) => self.canonical_func(f),
            ComponentField::CoreFunc(_) => unreachable!("should be expanded already"),
            ComponentField::Func(_) => unreachable!("should be expanded already"),
            ComponentField::Start(s) => self.start(s),
            ComponentField::Import(i) => self.item_sig(&mut i.item),
            ComponentField::Export(e) => {
                if let Some(ty) = &mut e.ty {
                    self.item_sig(&mut ty.0)?;
                }
                self.export(&mut e.kind)
            }
            ComponentField::Custom(_) | ComponentField::Producers(_) => Ok(()),
        }
    }

    fn core_module(&mut self, module: &mut CoreModule) -> Result<(), Error> {
        match &mut module.kind {
            CoreModuleKind::Inline { fields } => {
                crate::core::resolve::resolve(fields)?;
            }

            CoreModuleKind::Import { .. } => {
                unreachable!("should be expanded already")
            }
        }

        Ok(())
    }

    fn component(&mut self, component: &mut NestedComponent<'a>) -> Result<(), Error> {
        match &mut component.kind {
            NestedComponentKind::Import { .. } => unreachable!("should be expanded already"),
            NestedComponentKind::Inline(fields) => self.fields(component.id, fields),
        }
    }

    fn core_instance(&mut self, instance: &mut CoreInstance<'a>) -> Result<(), Error> {
        match &mut instance.kind {
            CoreInstanceKind::Instantiate { module, args } => {
                self.component_item_ref(module)?;
                for arg in args {
                    match &mut arg.kind {
                        CoreInstantiationArgKind::Instance(i) => {
                            self.core_item_ref(i)?;
                        }
                        CoreInstantiationArgKind::BundleOfExports(..) => {
                            unreachable!("should be expanded already");
                        }
                    }
                }
            }
            CoreInstanceKind::BundleOfExports(exports) => {
                for export in exports {
                    self.core_item_ref(&mut export.item)?;
                }
            }
        }
        Ok(())
    }

    fn instance(&mut self, instance: &mut Instance<'a>) -> Result<(), Error> {
        match &mut instance.kind {
            InstanceKind::Instantiate { component, args } => {
                self.component_item_ref(component)?;
                for arg in args {
                    match &mut arg.kind {
                        InstantiationArgKind::Item(e) => {
                            self.export(e)?;
                        }
                        InstantiationArgKind::BundleOfExports(..) => {
                            unreachable!("should be expanded already")
                        }
                    }
                }
            }
            InstanceKind::BundleOfExports(exports) => {
                for export in exports {
                    self.export(&mut export.kind)?;
                }
            }
            InstanceKind::Import { .. } => {
                unreachable!("should be expanded already")
            }
        }
        Ok(())
    }

    fn item_sig(&mut self, item: &mut ItemSig<'a>) -> Result<(), Error> {
        match &mut item.kind {
            // Here we must be explicit otherwise the module type reference will
            // be assumed to be in the component type namespace
            ItemSigKind::CoreModule(t) => self.core_type_use(t),
            ItemSigKind::Func(t) => self.component_type_use(t),
            ItemSigKind::Component(t) => self.component_type_use(t),
            ItemSigKind::Instance(t) => self.component_type_use(t),
            ItemSigKind::Value(t) => self.component_val_type(&mut t.0),
            ItemSigKind::Type(b) => match b {
                TypeBounds::Eq(i) => {
                    self.resolve_ns(i, Ns::Type)?;
                    Ok(())
                }
                TypeBounds::SubResource => Ok(()),
            },
        }
    }

    fn export(&mut self, kind: &mut ComponentExportKind<'a>) -> Result<(), Error> {
        match kind {
            // Here we do *not* have to be explicit as the item ref is to a core module
            ComponentExportKind::CoreModule(r) => self.component_item_ref(r),
            ComponentExportKind::Func(r) => self.component_item_ref(r),
            ComponentExportKind::Value(r) => self.component_item_ref(r),
            ComponentExportKind::Type(r) => self.component_item_ref(r),
            ComponentExportKind::Component(r) => self.component_item_ref(r),
            ComponentExportKind::Instance(r) => self.component_item_ref(r),
        }
    }

    fn start(&mut self, start: &mut Start<'a>) -> Result<(), Error> {
        self.resolve_ns(&mut start.func, Ns::Func)?;
        for arg in start.args.iter_mut() {
            self.component_item_ref(arg)?;
        }
        Ok(())
    }

    fn outer_alias<T: Into<Ns>>(
        &mut self,
        outer: &mut Index<'a>,
        index: &mut Index<'a>,
        kind: T,
        span: Span,
    ) -> Result<(), Error> {
        // Short-circuit when both indices are already resolved as this
        // helps to write tests for invalid modules where wasmparser should
        // be the one returning the error.
        if let Index::Num(..) = outer {
            if let Index::Num(..) = index {
                return Ok(());
            }
        }

        // Resolve `outer`, and compute the depth at which to look up
        // `index`.
        let depth = match outer {
            Index::Id(id) => {
                let mut depth = 0;
                for resolver in self.stack.iter().rev() {
                    if resolver.id == Some(*id) {
                        break;
                    }
                    depth += 1;
                }
                if depth as usize == self.stack.len() {
                    return Err(Error::new(
                        span,
                        format!("outer component `{}` not found", id.name()),
                    ));
                }
                depth
            }
            Index::Num(n, _span) => *n,
        };

        if depth as usize >= self.stack.len() {
            return Err(Error::new(
                span,
                format!("outer count of `{}` is too large", depth),
            ));
        }

        *outer = Index::Num(depth, span);

        // Resolve `index` within the computed scope depth.
        let computed = self.stack.len() - 1 - depth as usize;
        self.stack[computed].resolve(kind.into(), index)?;

        Ok(())
    }

    fn alias(&mut self, alias: &mut Alias<'a>) -> Result<(), Error> {
        match &mut alias.target {
            AliasTarget::Export {
                instance,
                name: _,
                kind: _,
            } => {
                self.resolve_ns(instance, Ns::Instance)?;
            }
            AliasTarget::CoreExport {
                instance,
                name: _,
                kind: _,
            } => {
                self.resolve_ns(instance, Ns::CoreInstance)?;
            }
            AliasTarget::Outer { outer, index, kind } => {
                self.outer_alias(outer, index, *kind, alias.span)?;
            }
        }
        Ok(())
    }

    fn canonical_func(&mut self, func: &mut CanonicalFunc<'a>) -> Result<(), Error> {
        match &mut func.kind {
            CanonicalFuncKind::Lift { ty, info } => {
                self.component_type_use(ty)?;
                self.core_item_ref(&mut info.func)?;
                self.canon_opts(&mut info.opts)?;
            }
            CanonicalFuncKind::Core(core) => match core {
                CoreFuncKind::Alias(_) => {
                    panic!("should have been removed during expansion")
                }
                CoreFuncKind::Lower(info) => {
                    self.component_item_ref(&mut info.func)?;
                    self.canon_opts(&mut info.opts)?;
                }
                CoreFuncKind::ResourceNew(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::ResourceRep(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::ResourceDrop(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::ThreadSpawnRef(info) => {
                    self.resolve_ns(&mut info.ty, Ns::CoreType)?;
                }
                CoreFuncKind::ThreadSpawnIndirect(info) => {
                    self.resolve_ns(&mut info.ty, Ns::CoreType)?;
                    self.core_item_ref(&mut info.table)?;
                }
                CoreFuncKind::ThreadAvailableParallelism(_)
                | CoreFuncKind::BackpressureSet
                | CoreFuncKind::Yield(_)
                | CoreFuncKind::SubtaskDrop
                | CoreFuncKind::ErrorContextDrop => {}
                CoreFuncKind::TaskReturn(info) => {
                    if let Some(ty) = &mut info.result {
                        self.component_val_type(ty)?;
                    }
                    self.canon_opts(&mut info.opts)?;
                }
                CoreFuncKind::ContextGet(_) | CoreFuncKind::ContextSet(_) => {}
                CoreFuncKind::StreamNew(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::StreamRead(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                    self.canon_opts(&mut info.opts)?;
                }
                CoreFuncKind::StreamWrite(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                    self.canon_opts(&mut info.opts)?;
                }
                CoreFuncKind::StreamCancelRead(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::StreamCancelWrite(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::StreamCloseReadable(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::StreamCloseWritable(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::FutureNew(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::FutureRead(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                    self.canon_opts(&mut info.opts)?;
                }
                CoreFuncKind::FutureWrite(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                    self.canon_opts(&mut info.opts)?;
                }
                CoreFuncKind::FutureCancelRead(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::FutureCancelWrite(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::FutureCloseReadable(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::FutureCloseWritable(info) => {
                    self.resolve_ns(&mut info.ty, Ns::Type)?;
                }
                CoreFuncKind::ErrorContextNew(info) => {
                    self.canon_opts(&mut info.opts)?;
                }
                CoreFuncKind::ErrorContextDebugMessage(info) => {
                    self.canon_opts(&mut info.opts)?;
                }
                CoreFuncKind::WaitableSetNew => {}
                CoreFuncKind::WaitableSetWait(info) => {
                    self.core_item_ref(&mut info.memory)?;
                }
                CoreFuncKind::WaitableSetPoll(info) => {
                    self.core_item_ref(&mut info.memory)?;
                }
                CoreFuncKind::WaitableSetDrop => {}
                CoreFuncKind::WaitableJoin => {}
            },
        }

        Ok(())
    }

    fn canon_opts(&mut self, opts: &mut [CanonOpt<'a>]) -> Result<(), Error> {
        for opt in opts {
            match opt {
                CanonOpt::StringUtf8
                | CanonOpt::StringUtf16
                | CanonOpt::StringLatin1Utf16
                | CanonOpt::Async => {}
                CanonOpt::Memory(r) => self.core_item_ref(r)?,
                CanonOpt::Realloc(r) | CanonOpt::PostReturn(r) | CanonOpt::Callback(r) => {
                    self.core_item_ref(r)?
                }
            }
        }

        Ok(())
    }

    fn core_type_use<T>(&mut self, ty: &mut CoreTypeUse<'a, T>) -> Result<(), Error> {
        let item = match ty {
            CoreTypeUse::Ref(r) => r,
            CoreTypeUse::Inline(_) => {
                unreachable!("inline type-use should be expanded by now")
            }
        };
        self.core_item_ref(item)
    }

    fn component_type_use<T>(&mut self, ty: &mut ComponentTypeUse<'a, T>) -> Result<(), Error> {
        let item = match ty {
            ComponentTypeUse::Ref(r) => r,
            ComponentTypeUse::Inline(_) => {
                unreachable!("inline type-use should be expanded by now")
            }
        };
        self.component_item_ref(item)
    }

    fn defined_type(&mut self, ty: &mut ComponentDefinedType<'a>) -> Result<(), Error> {
        match ty {
            ComponentDefinedType::Primitive(_) => {}
            ComponentDefinedType::Flags(_) => {}
            ComponentDefinedType::Enum(_) => {}
            ComponentDefinedType::Record(r) => {
                for field in r.fields.iter_mut() {
                    self.component_val_type(&mut field.ty)?;
                }
            }
            ComponentDefinedType::Variant(v) => {
                // Namespace for case identifier resolution
                let mut ns = Namespace::default();
                for case in v.cases.iter_mut() {
                    let index = ns.register(case.id, "variant case")?;

                    if let Some(ty) = &mut case.ty {
                        self.component_val_type(ty)?;
                    }

                    if let Some(refines) = &mut case.refines {
                        if let Refinement::Index(span, idx) = refines {
                            let resolved = ns.resolve(idx, "variant case")?;
                            if resolved == index {
                                return Err(Error::new(
                                    *span,
                                    "variant case cannot refine itself".to_string(),
                                ));
                            }

                            *refines = Refinement::Resolved(resolved);
                        }
                    }
                }
            }
            ComponentDefinedType::List(l) => {
                self.component_val_type(&mut l.element)?;
            }
            ComponentDefinedType::Tuple(t) => {
                for field in t.fields.iter_mut() {
                    self.component_val_type(field)?;
                }
            }
            ComponentDefinedType::Option(o) => {
                self.component_val_type(&mut o.element)?;
            }
            ComponentDefinedType::Result(r) => {
                if let Some(ty) = &mut r.ok {
                    self.component_val_type(ty)?;
                }

                if let Some(ty) = &mut r.err {
                    self.component_val_type(ty)?;
                }
            }
            ComponentDefinedType::Own(t) | ComponentDefinedType::Borrow(t) => {
                self.resolve_ns(t, Ns::Type)?;
            }
            ComponentDefinedType::Stream(s) => {
                if let Some(ty) = &mut s.element {
                    self.component_val_type(ty)?;
                }
            }
            ComponentDefinedType::Future(f) => {
                if let Some(ty) = &mut f.element {
                    self.component_val_type(ty)?;
                }
            }
        }
        Ok(())
    }

    fn component_val_type(&mut self, ty: &mut ComponentValType<'a>) -> Result<(), Error> {
        match ty {
            ComponentValType::Ref(idx) => {
                self.resolve_ns(idx, Ns::Type)?;
                Ok(())
            }
            ComponentValType::Inline(ComponentDefinedType::Primitive(_)) => Ok(()),
            ComponentValType::Inline(_) => unreachable!("should be expanded by now"),
        }
    }

    fn core_ty(&mut self, field: &mut CoreType<'a>) -> Result<(), Error> {
        match &mut field.def {
            CoreTypeDef::Def(ty) => {
                // See comments in `module_type` for why registration of ids happens
                // here for core types early.
                self.current().core_types.register(field.id, "core type")?;
                self.current().resolve_type_def(ty)?;
                assert!(self.aliases_to_insert.is_empty());
            }
            CoreTypeDef::Module(t) => {
                self.stack.push(ComponentState::new(field.id));
                self.module_type(t)?;
                self.stack.pop();
            }
        }
        Ok(())
    }

    fn core_rec(&mut self, rec: &mut core::Rec<'a>) -> Result<(), Error> {
        // See comments in `module_type` for why registration of ids happens
        // here for core types early.
        for ty in rec.types.iter() {
            self.current().core_types.register(ty.id, "core type")?;
        }
        for ty in rec.types.iter_mut() {
            self.current().resolve_type(ty)?;
        }
        assert!(self.aliases_to_insert.is_empty());
        Ok(())
    }

    fn ty(&mut self, field: &mut Type<'a>) -> Result<(), Error> {
        match &mut field.def {
            TypeDef::Defined(t) => {
                self.defined_type(t)?;
            }
            TypeDef::Func(f) => {
                for param in f.params.iter_mut() {
                    self.component_val_type(&mut param.ty)?;
                }

                if let Some(result) = &mut f.result {
                    self.component_val_type(result)?;
                }
            }
            TypeDef::Component(c) => {
                self.stack.push(ComponentState::new(field.id));
                self.component_type(c)?;
                self.stack.pop();
            }
            TypeDef::Instance(i) => {
                self.stack.push(ComponentState::new(field.id));
                self.instance_type(i)?;
                self.stack.pop();
            }
            TypeDef::Resource(r) => {
                match &mut r.rep {
                    ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {}
                    ValType::Ref(r) => match &mut r.heap {
                        core::HeapType::Abstract { .. } => {}
                        core::HeapType::Concrete(id) => {
                            self.resolve_ns(id, Ns::Type)?;
                        }
                    },
                }
                if let Some(dtor) = &mut r.dtor {
                    self.core_item_ref(dtor)?;
                }
            }
        }
        Ok(())
    }

    fn component_type(&mut self, c: &mut ComponentType<'a>) -> Result<(), Error> {
        self.resolve_prepending_aliases(
            &mut c.decls,
            |resolver, decl| match decl {
                ComponentTypeDecl::Alias(alias) => resolver.alias(alias),
                ComponentTypeDecl::CoreType(ty) => resolver.core_ty(ty),
                ComponentTypeDecl::Type(ty) => resolver.ty(ty),
                ComponentTypeDecl::Import(import) => resolver.item_sig(&mut import.item),
                ComponentTypeDecl::Export(export) => resolver.item_sig(&mut export.item),
            },
            |state, decl| {
                match decl {
                    ComponentTypeDecl::Alias(alias) => {
                        state.register_alias(alias)?;
                    }
                    ComponentTypeDecl::CoreType(ty) => {
                        state.core_types.register(ty.id, "core type")?;
                    }
                    ComponentTypeDecl::Type(ty) => {
                        state.types.register(ty.id, "type")?;
                    }
                    ComponentTypeDecl::Export(e) => {
                        state.register_item_sig(&e.item)?;
                    }
                    ComponentTypeDecl::Import(i) => {
                        state.register_item_sig(&i.item)?;
                    }
                }
                Ok(())
            },
        )
    }

    fn instance_type(&mut self, c: &mut InstanceType<'a>) -> Result<(), Error> {
        self.resolve_prepending_aliases(
            &mut c.decls,
            |resolver, decl| match decl {
                InstanceTypeDecl::Alias(alias) => resolver.alias(alias),
                InstanceTypeDecl::CoreType(ty) => resolver.core_ty(ty),
                InstanceTypeDecl::Type(ty) => resolver.ty(ty),
                InstanceTypeDecl::Export(export) => resolver.item_sig(&mut export.item),
            },
            |state, decl| {
                match decl {
                    InstanceTypeDecl::Alias(alias) => {
                        state.register_alias(alias)?;
                    }
                    InstanceTypeDecl::CoreType(ty) => {
                        state.core_types.register(ty.id, "core type")?;
                    }
                    InstanceTypeDecl::Type(ty) => {
                        state.types.register(ty.id, "type")?;
                    }
                    InstanceTypeDecl::Export(export) => {
                        state.register_item_sig(&export.item)?;
                    }
                }
                Ok(())
            },
        )
    }

    fn core_item_ref<K>(&mut self, item: &mut CoreItemRef<'a, K>) -> Result<(), Error>
    where
        K: CoreItem + Copy,
    {
        // Check for not being an instance export reference
        if item.export_name.is_none() {
            self.resolve_ns(&mut item.idx, item.kind.ns())?;
            return Ok(());
        }

        // This is a reference to a core instance export
        let mut index = item.idx;
        self.resolve_ns(&mut index, Ns::CoreInstance)?;

        // Record an alias to reference the export
        let span = item.idx.span();
        let alias = Alias {
            span,
            id: None,
            name: None,
            target: AliasTarget::CoreExport {
                instance: index,
                name: item.export_name.unwrap(),
                kind: item.kind.ns().into(),
            },
        };

        index = Index::Num(self.current().register_alias(&alias)?, span);
        self.aliases_to_insert.push(alias);

        item.idx = index;
        item.export_name = None;

        Ok(())
    }

    fn component_item_ref<K>(&mut self, item: &mut ItemRef<'a, K>) -> Result<(), Error>
    where
        K: ComponentItem + Copy,
    {
        // Check for not being an instance export reference
        if item.export_names.is_empty() {
            self.resolve_ns(&mut item.idx, item.kind.ns())?;
            return Ok(());
        }

        // This is a reference to an instance export
        let mut index = item.idx;
        self.resolve_ns(&mut index, Ns::Instance)?;

        let span = item.idx.span();
        for (pos, export_name) in item.export_names.iter().enumerate() {
            // Record an alias to reference the export
            let alias = Alias {
                span,
                id: None,
                name: None,
                target: AliasTarget::Export {
                    instance: index,
                    name: export_name,
                    kind: if pos == item.export_names.len() - 1 {
                        item.kind.ns().into()
                    } else {
                        ComponentExportAliasKind::Instance
                    },
                },
            };

            index = Index::Num(self.current().register_alias(&alias)?, span);
            self.aliases_to_insert.push(alias);
        }

        item.idx = index;
        item.export_names = Vec::new();

        Ok(())
    }

    fn resolve_ns(&mut self, idx: &mut Index<'a>, ns: Ns) -> Result<u32, Error> {
        // Perform resolution on a local clone walking up the stack of components
        // that we have. Note that a local clone is used since we don't want to use
        // the parent's resolved index if a parent matches, instead we want to use
        // the index of the alias that we will automatically insert.
        let mut idx_clone = *idx;
        for (depth, resolver) in self.stack.iter_mut().rev().enumerate() {
            let depth = depth as u32;
            let found = match resolver.resolve(ns, &mut idx_clone) {
                Ok(idx) => idx,
                // Try the next parent
                Err(_) => continue,
            };

            // If this is the current component then no extra alias is necessary, so
            // return success.
            if depth == 0 {
                *idx = idx_clone;
                return Ok(found);
            }
            let id = match idx {
                Index::Id(id) => *id,
                Index::Num(..) => unreachable!(),
            };

            // When resolution succeeds in a parent then an outer alias is
            // automatically inserted here in this component.
            let span = idx.span();
            let alias = Alias {
                span,
                id: Some(id),
                name: None,
                target: AliasTarget::Outer {
                    outer: Index::Num(depth, span),
                    index: Index::Num(found, span),
                    kind: match ns {
                        Ns::CoreModule => ComponentOuterAliasKind::CoreModule,
                        Ns::CoreType => ComponentOuterAliasKind::CoreType,
                        Ns::Type => ComponentOuterAliasKind::Type,
                        Ns::Component => ComponentOuterAliasKind::Component,
                        _ => {
                            return Err(Error::new(
                                span,
                                format!(
                                    "outer item `{}` is not a module, type, or component",
                                    id.name(),
                                ),
                            ));
                        }
                    },
                },
            };
            let local_index = self.current().register_alias(&alias)?;
            self.aliases_to_insert.push(alias);
            *idx = Index::Num(local_index, span);
            return Ok(local_index);
        }

        // If resolution in any parent failed then simply return the error from our
        // local namespace
        self.current().resolve(ns, idx)?;
        unreachable!()
    }

    fn module_type(&mut self, ty: &mut ModuleType<'a>) -> Result<(), Error> {
        return self.resolve_prepending_aliases(
            &mut ty.decls,
            |resolver, decl| match decl {
                ModuleTypeDecl::Alias(alias) => resolver.alias(alias),

                // For types since the GC proposal to core wasm they're allowed
                // to both refer to themselves and additionally a recursion
                // group can define a set of types that all refer to one
                // another. That means that the type names must be registered
                // first before the type is resolved so the type's own name is
                // in scope for itself.
                //
                // Note though that this means that aliases cannot be injected
                // automatically for references to outer types. We don't know
                // how many aliases are going to be created so we otherwise
                // don't know the type index to register.
                //
                // As a compromise for now core types don't support
                // auto-injection of aliases from outer scopes. They must be
                // explicitly aliased in. Also note that the error message isn't
                // great either. This may be something we want to improve in the
                // future with a better error message or a pass that goes over
                // everything first to inject aliases and then afterwards all
                // other names are registered.
                ModuleTypeDecl::Type(t) => {
                    resolver.current().core_types.register(t.id, "type")?;
                    resolver.current().resolve_type(t)
                }
                ModuleTypeDecl::Rec(t) => {
                    for t in t.types.iter_mut() {
                        resolver.current().core_types.register(t.id, "type")?;
                    }
                    for t in t.types.iter_mut() {
                        resolver.current().resolve_type(t)?;
                    }
                    Ok(())
                }

                ModuleTypeDecl::Import(import) => resolve_item_sig(resolver, &mut import.item),
                ModuleTypeDecl::Export(_, item) => resolve_item_sig(resolver, item),
            },
            |state, decl| {
                match decl {
                    ModuleTypeDecl::Alias(alias) => {
                        state.register_alias(alias)?;
                    }
                    // These were registered above already
                    ModuleTypeDecl::Type(_) | ModuleTypeDecl::Rec(_) => {}
                    // Only the type namespace is populated within the module type
                    // namespace so these are ignored here.
                    ModuleTypeDecl::Import(_) | ModuleTypeDecl::Export(..) => {}
                }
                Ok(())
            },
        );

        fn resolve_item_sig<'a>(
            resolver: &Resolver<'a>,
            sig: &mut core::ItemSig<'a>,
        ) -> Result<(), Error> {
            match &mut sig.kind {
                core::ItemKind::Func(ty) | core::ItemKind::Tag(core::TagType::Exception(ty)) => {
                    let idx = ty.index.as_mut().expect("index should be filled in");
                    resolver
                        .stack
                        .last()
                        .unwrap()
                        .core_types
                        .resolve(idx, "type")?;
                }
                core::ItemKind::Memory(_)
                | core::ItemKind::Global(_)
                | core::ItemKind::Table(_) => {}
            }
            Ok(())
        }
    }
}

impl<'a> ComponentState<'a> {
    fn resolve(&self, ns: Ns, idx: &mut Index<'a>) -> Result<u32, Error> {
        match ns {
            Ns::CoreFunc => self.core_funcs.resolve(idx, "core func"),
            Ns::CoreGlobal => self.core_globals.resolve(idx, "core global"),
            Ns::CoreTable => self.core_tables.resolve(idx, "core table"),
            Ns::CoreMemory => self.core_memories.resolve(idx, "core memory"),
            Ns::CoreType => self.core_types.resolve(idx, "core type"),
            Ns::CoreTag => self.core_tags.resolve(idx, "core tag"),
            Ns::CoreInstance => self.core_instances.resolve(idx, "core instance"),
            Ns::CoreModule => self.core_modules.resolve(idx, "core module"),
            Ns::Func => self.funcs.resolve(idx, "func"),
            Ns::Type => self.types.resolve(idx, "type"),
            Ns::Instance => self.instances.resolve(idx, "instance"),
            Ns::Component => self.components.resolve(idx, "component"),
            Ns::Value => self.values.resolve(idx, "value"),
        }
    }

    /// Assign an index to the given field.
    fn register(&mut self, item: &ComponentField<'a>) -> Result<(), Error> {
        match item {
            ComponentField::CoreModule(m) => self.core_modules.register(m.id, "core module")?,
            ComponentField::CoreInstance(i) => {
                self.core_instances.register(i.id, "core instance")?
            }
            ComponentField::CoreType(ty) => match &ty.def {
                CoreTypeDef::Def(_) => 0, // done above in `core_rec`
                CoreTypeDef::Module(_) => self.core_types.register(ty.id, "core type")?,
            },
            ComponentField::CoreRec(_) => 0, // done above in `core_rec`
            ComponentField::Component(c) => self.components.register(c.id, "component")?,
            ComponentField::Instance(i) => self.instances.register(i.id, "instance")?,
            ComponentField::Alias(a) => self.register_alias(a)?,
            ComponentField::Type(t) => self.types.register(t.id, "type")?,
            ComponentField::CanonicalFunc(f) => match &f.kind {
                CanonicalFuncKind::Lift { .. } => self.funcs.register(f.id, "func")?,
                CanonicalFuncKind::Core(_) => self.core_funcs.register(f.id, "core func")?,
            },
            ComponentField::CoreFunc(_) | ComponentField::Func(_) => {
                unreachable!("should be expanded already")
            }
            ComponentField::Start(s) => {
                for r in &s.results {
                    self.values.register(*r, "value")?;
                }
                return Ok(());
            }
            ComponentField::Import(i) => self.register_item_sig(&i.item)?,
            ComponentField::Export(e) => match &e.kind {
                ComponentExportKind::CoreModule(_) => {
                    self.core_modules.register(e.id, "core module")?
                }
                ComponentExportKind::Func(_) => self.funcs.register(e.id, "func")?,
                ComponentExportKind::Instance(_) => self.instances.register(e.id, "instance")?,
                ComponentExportKind::Value(_) => self.values.register(e.id, "value")?,
                ComponentExportKind::Component(_) => self.components.register(e.id, "component")?,
                ComponentExportKind::Type(_) => self.types.register(e.id, "type")?,
            },
            ComponentField::Custom(_) | ComponentField::Producers(_) => return Ok(()),
        };

        Ok(())
    }

    fn register_alias(&mut self, alias: &Alias<'a>) -> Result<u32, Error> {
        match alias.target {
            AliasTarget::Export { kind, .. } => match kind {
                ComponentExportAliasKind::CoreModule => {
                    self.core_modules.register(alias.id, "core module")
                }
                ComponentExportAliasKind::Func => self.funcs.register(alias.id, "func"),
                ComponentExportAliasKind::Value => self.values.register(alias.id, "value"),
                ComponentExportAliasKind::Type => self.types.register(alias.id, "type"),
                ComponentExportAliasKind::Component => {
                    self.components.register(alias.id, "component")
                }
                ComponentExportAliasKind::Instance => self.instances.register(alias.id, "instance"),
            },
            AliasTarget::CoreExport { kind, .. } => match kind {
                core::ExportKind::Func => self.core_funcs.register(alias.id, "core func"),
                core::ExportKind::Table => self.core_tables.register(alias.id, "core table"),
                core::ExportKind::Memory => self.core_memories.register(alias.id, "core memory"),
                core::ExportKind::Global => self.core_globals.register(alias.id, "core global"),
                core::ExportKind::Tag => self.core_tags.register(alias.id, "core tag"),
            },
            AliasTarget::Outer { kind, .. } => match kind {
                ComponentOuterAliasKind::CoreModule => {
                    self.core_modules.register(alias.id, "core module")
                }
                ComponentOuterAliasKind::CoreType => {
                    self.core_types.register(alias.id, "core type")
                }
                ComponentOuterAliasKind::Type => self.types.register(alias.id, "type"),
                ComponentOuterAliasKind::Component => {
                    self.components.register(alias.id, "component")
                }
            },
        }
    }
}

impl<'a> ResolveCoreType<'a> for ComponentState<'a> {
    fn resolve_type_name(&mut self, name: &mut Index<'a>) -> Result<u32, Error> {
        self.resolve(Ns::CoreType, name)
    }
}

#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
enum Ns {
    CoreFunc,
    CoreGlobal,
    CoreTable,
    CoreMemory,
    CoreType,
    CoreTag,
    CoreInstance,
    CoreModule,
    Func,
    Type,
    Instance,
    Component,
    Value,
}

trait ComponentItem {
    fn ns(&self) -> Ns;
}

trait CoreItem {
    fn ns(&self) -> Ns;
}

macro_rules! component_item {
    ($kw:path, $kind:ident) => {
        impl ComponentItem for $kw {
            fn ns(&self) -> Ns {
                Ns::$kind
            }
        }
    };
}

macro_rules! core_item {
    ($kw:path, $kind:ident) => {
        impl CoreItem for $kw {
            fn ns(&self) -> Ns {
                Ns::$kind
            }
        }
    };
}

component_item!(kw::func, Func);
component_item!(kw::r#type, Type);
component_item!(kw::r#instance, Instance);
component_item!(kw::component, Component);
component_item!(kw::value, Value);
component_item!(kw::module, CoreModule);

core_item!(kw::func, CoreFunc);
core_item!(kw::memory, CoreMemory);
core_item!(kw::table, CoreTable);
core_item!(kw::r#type, CoreType);
core_item!(kw::r#instance, CoreInstance);

impl From<Ns> for ComponentExportAliasKind {
    fn from(ns: Ns) -> Self {
        match ns {
            Ns::CoreModule => Self::CoreModule,
            Ns::Func => Self::Func,
            Ns::Type => Self::Type,
            Ns::Instance => Self::Instance,
            Ns::Component => Self::Component,
            Ns::Value => Self::Value,
            _ => unreachable!("not a component exportable namespace"),
        }
    }
}

impl From<Ns> for core::ExportKind {
    fn from(ns: Ns) -> Self {
        match ns {
            Ns::CoreFunc => Self::Func,
            Ns::CoreTable => Self::Table,
            Ns::CoreGlobal => Self::Global,
            Ns::CoreMemory => Self::Memory,
            Ns::CoreTag => Self::Tag,
            _ => unreachable!("not a core exportable namespace"),
        }
    }
}

impl From<ComponentOuterAliasKind> for Ns {
    fn from(kind: ComponentOuterAliasKind) -> Self {
        match kind {
            ComponentOuterAliasKind::CoreModule => Self::CoreModule,
            ComponentOuterAliasKind::CoreType => Self::CoreType,
            ComponentOuterAliasKind::Type => Self::Type,
            ComponentOuterAliasKind::Component => Self::Component,
        }
    }
}

impl CoreItem for core::ExportKind {
    fn ns(&self) -> Ns {
        match self {
            Self::Func => Ns::CoreFunc,
            Self::Table => Ns::CoreTable,
            Self::Global => Ns::CoreGlobal,
            Self::Memory => Ns::CoreMemory,
            Self::Tag => Ns::CoreTag,
        }
    }
}
