use crate::Wat;
#[cfg(feature = "component-model")]
use crate::component::Component;
use crate::core::*;
use crate::encode::Encode;
use crate::token::*;
use std::borrow::Cow;
use std::marker;
#[cfg(feature = "dwarf")]
use std::path::Path;

/// Options that can be specified when encoding a component or a module to
/// customize what the final binary looks like.
///
/// Methods such as [`Module::encode`], [`Wat::encode`], and
/// [`Component::encode`] will use the default options.
#[derive(Default)]
pub struct EncodeOptions<'a> {
    #[cfg(feature = "dwarf")]
    dwarf_info: Option<(&'a Path, &'a str, GenerateDwarf)>,

    _marker: marker::PhantomData<&'a str>,
}

#[cfg(feature = "dwarf")]
mod dwarf;

#[cfg(not(feature = "dwarf"))]
mod dwarf_disabled;
#[cfg(not(feature = "dwarf"))]
use self::dwarf_disabled as dwarf;

/// Configuration of how DWARF debugging information may be generated.
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub enum GenerateDwarf {
    /// Only generate line tables to map binary offsets back to source
    /// locations.
    Lines,

    /// Generate full debugging information for both line numbers and
    /// variables/locals/operands.
    Full,
}

impl<'a> EncodeOptions<'a> {
    /// Creates a new set of default encoding options.
    pub fn new() -> EncodeOptions<'a> {
        EncodeOptions::default()
    }

    /// Enables emission of DWARF debugging information in the final binary.
    ///
    /// This method will use the `file` specified as the source file for the
    /// `*.wat` file whose `contents` must also be supplied here. These are
    /// used to calculate filenames/line numbers and are referenced from the
    /// generated DWARF.
    #[cfg(feature = "dwarf")]
    pub fn dwarf(&mut self, file: &'a Path, contents: &'a str, style: GenerateDwarf) -> &mut Self {
        self.dwarf_info = Some((file, contents, style));
        self
    }

    /// Encodes the given [`Module`] with these options.
    ///
    /// For more information see [`Module::encode`].
    pub fn encode_module(
        &self,
        module: &mut Module<'_>,
    ) -> std::result::Result<Vec<u8>, crate::Error> {
        module.resolve()?;
        Ok(match &module.kind {
            ModuleKind::Text(fields) => encode(&module.id, &module.name, fields, self),
            ModuleKind::Binary(blobs) => blobs.iter().flat_map(|b| b.iter().cloned()).collect(),
        })
    }

    /// Encodes the given [`Component`] with these options.
    ///
    /// For more information see [`Component::encode`].
    #[cfg(feature = "component-model")]
    pub fn encode_component(
        &self,
        component: &mut Component<'_>,
    ) -> std::result::Result<Vec<u8>, crate::Error> {
        component.resolve()?;
        Ok(crate::component::binary::encode(component, self))
    }

    /// Encodes the given [`Wat`] with these options.
    ///
    /// For more information see [`Wat::encode`].
    pub fn encode_wat(&self, wat: &mut Wat<'_>) -> std::result::Result<Vec<u8>, crate::Error> {
        match wat {
            Wat::Module(m) => self.encode_module(m),
            #[cfg(feature = "component-model")]
            Wat::Component(c) => self.encode_component(c),
            #[cfg(not(feature = "component-model"))]
            Wat::Component(_) => unreachable!(),
        }
    }
}

pub(crate) fn encode(
    module_id: &Option<Id<'_>>,
    module_name: &Option<NameAnnotation<'_>>,
    fields: &[ModuleField<'_>],
    opts: &EncodeOptions,
) -> Vec<u8> {
    use CustomPlace::*;
    use CustomPlaceAnchor::*;

    let mut types = Vec::new();
    let mut imports = Vec::new();
    let mut funcs = Vec::new();
    let mut tables = Vec::new();
    let mut memories = Vec::new();
    let mut globals = Vec::new();
    let mut exports = Vec::new();
    let mut start = Vec::new();
    let mut elem = Vec::new();
    let mut data = Vec::new();
    let mut tags = Vec::new();
    let mut customs = Vec::new();
    for field in fields {
        match field {
            ModuleField::Type(i) => types.push(RecOrType::Type(i)),
            ModuleField::Rec(i) => types.push(RecOrType::Rec(i)),
            ModuleField::Import(i) => imports.push(i),
            ModuleField::Func(i) => funcs.push(i),
            ModuleField::Table(i) => tables.push(i),
            ModuleField::Memory(i) => memories.push(i),
            ModuleField::Global(i) => globals.push(i),
            ModuleField::Export(i) => exports.push(i),
            ModuleField::Start(i) => start.push(i),
            ModuleField::Elem(i) => elem.push(i),
            ModuleField::Data(i) => data.push(i),
            ModuleField::Tag(i) => tags.push(i),
            ModuleField::Custom(i) => customs.push(i),
        }
    }

    let mut e = Encoder {
        wasm: wasm_encoder::Module::new(),
        customs: &customs,
    };

    e.custom_sections(BeforeFirst);

    e.typed_section(&types);
    e.typed_section(&imports);

    let functys = funcs
        .iter()
        .map(|f| FuncSectionTy(&f.ty))
        .collect::<Vec<_>>();
    e.typed_section(&functys);
    e.typed_section(&tables);
    e.typed_section(&memories);
    e.typed_section(&tags);
    e.typed_section(&globals);
    e.typed_section(&exports);
    e.custom_sections(Before(Start));
    for start in start {
        e.wasm.section(&wasm_encoder::StartSection {
            function_index: start.unwrap_u32(),
        });
    }
    e.custom_sections(After(Start));
    e.typed_section(&elem);
    if needs_data_count(&funcs) {
        e.wasm.section(&wasm_encoder::DataCountSection {
            count: data.len().try_into().unwrap(),
        });
    }

    // Prepare to and emit the code section. This is where DWARF may optionally
    // be emitted depending on configuration settings. Note that `code_section`
    // will internally emit the branch hints section if necessary.
    let names = find_names(module_id, module_name, fields, &types);
    let num_import_funcs = imports
        .iter()
        .flat_map(|group| group.item_sigs())
        .filter(|sig| matches!(sig.kind, ItemKind::Func(..)))
        .count() as u32;
    let mut dwarf = dwarf::Dwarf::new(num_import_funcs, opts, &names, &types);
    e.code_section(&funcs, num_import_funcs, dwarf.as_mut());

    e.typed_section(&data);

    if !names.is_empty() {
        e.wasm.section(&names.to_name_section());
    }
    e.custom_sections(AfterLast);
    if let Some(dwarf) = &mut dwarf {
        dwarf.emit(&mut e);
    }

    return e.wasm.finish();

    fn needs_data_count(funcs: &[&crate::core::Func<'_>]) -> bool {
        funcs
            .iter()
            .filter_map(|f| match &f.kind {
                FuncKind::Inline { expression, .. } => Some(expression),
                _ => None,
            })
            .flat_map(|e| e.instrs.iter())
            .any(|i| i.needs_data_count())
    }
}

fn func_type<'a>(types: &'a [RecOrType<'a>], ty: u32) -> Option<&'a FunctionType<'a>> {
    // Iterate through `self.types` which is what was encoded into the
    // module and find the function type which gives access to the
    // parameters which gives access to their types.
    let ty = types
        .iter()
        .flat_map(|t| match t {
            RecOrType::Type(t) => std::slice::from_ref(*t),
            RecOrType::Rec(r) => &r.types,
        })
        .nth(ty as usize)?;
    match &ty.def.kind {
        InnerTypeKind::Func(ty) => Some(ty),
        _ => None,
    }
}

struct Encoder<'a> {
    wasm: wasm_encoder::Module,
    customs: &'a [&'a Custom<'a>],
}

impl Encoder<'_> {
    fn custom_sections(&mut self, place: CustomPlace) {
        for entry in self.customs.iter() {
            if entry.place() == place {
                entry.encode(&mut self.wasm);
            }
        }
    }

    fn typed_section<T>(&mut self, list: &[T])
    where
        T: SectionItem,
    {
        self.custom_sections(CustomPlace::Before(T::ANCHOR));
        if !list.is_empty() {
            let mut section = T::Section::default();
            for item in list {
                item.encode(&mut section);
            }
            self.wasm.section(&section);
        }
        self.custom_sections(CustomPlace::After(T::ANCHOR));
    }

    /// Encodes the code section of a wasm module module while additionally
    /// handling the branch hinting proposal.
    ///
    /// The branch hinting proposal requires to encode the offsets of the
    /// instructions relative from the beginning of the function. Here we encode
    /// each instruction and we save its offset. If needed, we use this
    /// information to build the branch hint section and insert it before the
    /// code section.
    ///
    /// The `list` provided is the list of functions that are emitted into the
    /// code section. The `func_index` provided is the initial index of defined
    /// functions, so it's the count of imported functions. The `dwarf` field is
    /// optionally used to track debugging information.
    fn code_section<'a>(
        &'a mut self,
        list: &[&'a Func<'_>],
        mut func_index: u32,
        mut dwarf: Option<&mut dwarf::Dwarf>,
    ) {
        self.custom_sections(CustomPlace::Before(CustomPlaceAnchor::Code));

        if !list.is_empty() {
            let mut branch_hints = wasm_encoder::BranchHints::new();
            let mut code_section = wasm_encoder::CodeSection::new();

            for func in list.iter() {
                let hints = func.encode(&mut code_section, dwarf.as_deref_mut());
                if !hints.is_empty() {
                    branch_hints.function_hints(func_index, hints.into_iter());
                }
                func_index += 1;
            }

            // Branch hints section has to be inserted before the Code section
            // Insert the section only if we have some hints
            if !branch_hints.is_empty() {
                self.wasm.section(&branch_hints);
            }

            // Finally, insert the Code section from the tmp buffer
            self.wasm.section(&code_section);

            if let Some(dwarf) = &mut dwarf {
                dwarf.set_code_section_size(code_section.byte_len());
            }
        }
        self.custom_sections(CustomPlace::After(CustomPlaceAnchor::Code));
    }
}

trait SectionItem {
    type Section: wasm_encoder::Section + Default;
    const ANCHOR: CustomPlaceAnchor;

    fn encode(&self, section: &mut Self::Section);
}

impl<T> SectionItem for &T
where
    T: SectionItem,
{
    type Section = T::Section;
    const ANCHOR: CustomPlaceAnchor = T::ANCHOR;

    fn encode(&self, section: &mut Self::Section) {
        T::encode(self, section)
    }
}

impl From<&FunctionType<'_>> for wasm_encoder::FuncType {
    fn from(ft: &FunctionType) -> Self {
        wasm_encoder::FuncType::new(
            ft.params.iter().map(|(_, _, ty)| (*ty).into()),
            ft.results.iter().map(|ty| (*ty).into()),
        )
    }
}

impl From<&StructType<'_>> for wasm_encoder::StructType {
    fn from(st: &StructType) -> wasm_encoder::StructType {
        wasm_encoder::StructType {
            fields: st.fields.iter().map(|f| f.into()).collect(),
        }
    }
}

impl From<&StructField<'_>> for wasm_encoder::FieldType {
    fn from(f: &StructField) -> wasm_encoder::FieldType {
        wasm_encoder::FieldType {
            element_type: f.ty.into(),
            mutable: f.mutable,
        }
    }
}

impl From<&ArrayType<'_>> for wasm_encoder::ArrayType {
    fn from(at: &ArrayType) -> Self {
        let field = wasm_encoder::FieldType {
            element_type: at.ty.into(),
            mutable: at.mutable,
        };
        wasm_encoder::ArrayType(field)
    }
}

impl From<&ContType<'_>> for wasm_encoder::ContType {
    fn from(at: &ContType) -> Self {
        wasm_encoder::ContType(at.0.into())
    }
}

enum RecOrType<'a> {
    Type(&'a Type<'a>),
    Rec(&'a Rec<'a>),
}

impl SectionItem for RecOrType<'_> {
    type Section = wasm_encoder::TypeSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Type;

    fn encode(&self, types: &mut wasm_encoder::TypeSection) {
        match self {
            RecOrType::Type(ty) => types.ty().subtype(&ty.to_subtype()),
            RecOrType::Rec(rec) => types.ty().rec(rec.types.iter().map(|t| t.to_subtype())),
        }
    }
}

impl Type<'_> {
    pub(crate) fn to_subtype(&self) -> wasm_encoder::SubType {
        self.def.to_subtype()
    }
}

impl TypeDef<'_> {
    pub(crate) fn to_subtype(&self) -> wasm_encoder::SubType {
        use wasm_encoder::CompositeInnerType::*;
        let composite_type = wasm_encoder::CompositeType {
            inner: match &self.kind {
                InnerTypeKind::Func(ft) => Func(ft.into()),
                InnerTypeKind::Struct(st) => Struct(st.into()),
                InnerTypeKind::Array(at) => Array(at.into()),
                InnerTypeKind::Cont(ct) => Cont(ct.into()),
            },
            shared: self.shared,
            descriptor: self.descriptor.map(|i| i.unwrap_u32()),
            describes: self.describes.map(|i| i.unwrap_u32()),
        };
        wasm_encoder::SubType {
            composite_type,
            is_final: self.final_type.unwrap_or(true),
            supertype_idx: self.parent.map(|i| i.unwrap_u32()),
        }
    }
}

impl From<ValType<'_>> for wasm_encoder::ValType {
    fn from(ty: ValType) -> Self {
        match ty {
            ValType::I32 => Self::I32,
            ValType::I64 => Self::I64,
            ValType::F32 => Self::F32,
            ValType::F64 => Self::F64,
            ValType::V128 => Self::V128,
            ValType::Ref(r) => Self::Ref(r.into()),
        }
    }
}

impl From<RefType<'_>> for wasm_encoder::RefType {
    fn from(r: RefType<'_>) -> Self {
        wasm_encoder::RefType {
            nullable: r.nullable,
            heap_type: r.heap.into(),
        }
    }
}

impl From<HeapType<'_>> for wasm_encoder::HeapType {
    fn from(r: HeapType<'_>) -> Self {
        use wasm_encoder::AbstractHeapType::*;
        match r {
            HeapType::Abstract { shared, ty } => {
                let ty = match ty {
                    AbstractHeapType::Func => Func,
                    AbstractHeapType::Extern => Extern,
                    AbstractHeapType::Exn => Exn,
                    AbstractHeapType::NoExn => NoExn,
                    AbstractHeapType::Any => Any,
                    AbstractHeapType::Eq => Eq,
                    AbstractHeapType::Struct => Struct,
                    AbstractHeapType::Array => Array,
                    AbstractHeapType::NoFunc => NoFunc,
                    AbstractHeapType::NoExtern => NoExtern,
                    AbstractHeapType::None => None,
                    AbstractHeapType::I31 => I31,
                    AbstractHeapType::Cont => Cont,
                    AbstractHeapType::NoCont => NoCont,
                };
                Self::Abstract { shared, ty }
            }
            HeapType::Concrete(i) => Self::Concrete(i.unwrap_u32()),
            HeapType::Exact(i) => Self::Exact(i.unwrap_u32()),
        }
    }
}

impl Encode for Option<Id<'_>> {
    fn encode(&self, _e: &mut Vec<u8>) {
        // used for parameters in the tuple impl as well as instruction labels
    }
}

impl<'a> Encode for ValType<'a> {
    fn encode(&self, e: &mut Vec<u8>) {
        wasm_encoder::Encode::encode(&wasm_encoder::ValType::from(*self), e)
    }
}

impl<'a> Encode for HeapType<'a> {
    fn encode(&self, e: &mut Vec<u8>) {
        wasm_encoder::Encode::encode(&wasm_encoder::HeapType::from(*self), e)
    }
}

impl From<StorageType<'_>> for wasm_encoder::StorageType {
    fn from(st: StorageType) -> Self {
        use wasm_encoder::StorageType::*;
        match st {
            StorageType::I8 => I8,
            StorageType::I16 => I16,
            StorageType::Val(vt) => Val(vt.into()),
        }
    }
}

impl SectionItem for Imports<'_> {
    type Section = wasm_encoder::ImportSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Import;

    fn encode(&self, section: &mut wasm_encoder::ImportSection) {
        section.imports(match &self.items {
            ImportItems::Single { module, name, sig } => {
                wasm_encoder::Imports::Single(wasm_encoder::Import {
                    module: module,
                    name,
                    ty: sig.to_entity_type(),
                })
            }
            ImportItems::Group1 { module, items } => wasm_encoder::Imports::Compact1 {
                module,
                items: items
                    .into_iter()
                    .map(|foo| wasm_encoder::ImportCompact {
                        name: foo.name,
                        ty: foo.sig.to_entity_type(),
                    })
                    .collect(),
            },
            ImportItems::Group2 { module, sig, items } => wasm_encoder::Imports::Compact2 {
                module,
                ty: sig.to_entity_type(),
                names: items.into_iter().map(|item| item.name).collect(),
            },
        });
    }
}

impl ItemSig<'_> {
    pub(crate) fn to_entity_type(&self) -> wasm_encoder::EntityType {
        self.kind.to_entity_type()
    }
}

impl ItemKind<'_> {
    fn to_entity_type(&self) -> wasm_encoder::EntityType {
        use wasm_encoder::EntityType as ET;
        match self {
            ItemKind::Func(t) => ET::Function(t.unwrap_u32()),
            ItemKind::FuncExact(t) => ET::FunctionExact(t.unwrap_u32()),
            ItemKind::Table(t) => ET::Table(t.to_table_type()),
            ItemKind::Memory(t) => ET::Memory(t.to_memory_type()),
            ItemKind::Global(t) => ET::Global(t.to_global_type()),
            ItemKind::Tag(t) => ET::Tag(t.to_tag_type()),
        }
    }
}

impl TableType<'_> {
    fn to_table_type(&self) -> wasm_encoder::TableType {
        wasm_encoder::TableType {
            element_type: self.elem.into(),
            minimum: self.limits.min,
            maximum: self.limits.max,
            table64: self.limits.is64,
            shared: self.shared,
        }
    }
}

impl MemoryType {
    fn to_memory_type(&self) -> wasm_encoder::MemoryType {
        wasm_encoder::MemoryType {
            minimum: self.limits.min,
            maximum: self.limits.max,
            memory64: self.limits.is64,
            shared: self.shared,
            page_size_log2: self.page_size_log2,
        }
    }
}

impl GlobalType<'_> {
    fn to_global_type(&self) -> wasm_encoder::GlobalType {
        wasm_encoder::GlobalType {
            val_type: self.ty.into(),
            mutable: self.mutable,
            shared: self.shared,
        }
    }
}

impl TagType<'_> {
    fn to_tag_type(&self) -> wasm_encoder::TagType {
        match self {
            TagType::Exception(r) => wasm_encoder::TagType {
                kind: wasm_encoder::TagKind::Exception,
                func_type_idx: r.unwrap_u32(),
            },
        }
    }
}

impl<T> TypeUse<'_, T> {
    fn unwrap_u32(&self) -> u32 {
        self.index
            .as_ref()
            .expect("TypeUse should be filled in by this point")
            .unwrap_u32()
    }
}

struct FuncSectionTy<'a>(&'a TypeUse<'a, FunctionType<'a>>);

impl SectionItem for FuncSectionTy<'_> {
    type Section = wasm_encoder::FunctionSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Func;

    fn encode(&self, section: &mut wasm_encoder::FunctionSection) {
        section.function(self.0.unwrap_u32());
    }
}

impl Encode for Index<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.unwrap_u32().encode(e)
    }
}

impl Index<'_> {
    fn unwrap_u32(&self) -> u32 {
        match self {
            Index::Num(n, _) => *n,
            Index::Id(n) => panic!("unresolved index in emission: {n:?}"),
        }
    }
}

impl From<Index<'_>> for u32 {
    fn from(i: Index<'_>) -> Self {
        match i {
            Index::Num(i, _) => i,
            Index::Id(_) => unreachable!("unresolved index in encoding: {:?}", i),
        }
    }
}

impl SectionItem for Table<'_> {
    type Section = wasm_encoder::TableSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Table;

    fn encode(&self, section: &mut wasm_encoder::TableSection) {
        assert!(self.exports.names.is_empty());
        match &self.kind {
            TableKind::Normal {
                ty,
                init_expr: None,
            } => {
                section.table(ty.to_table_type());
            }
            TableKind::Normal {
                ty,
                init_expr: Some(init_expr),
            } => {
                section.table_with_init(ty.to_table_type(), &init_expr.to_const_expr());
            }
            _ => panic!("TableKind should be normal during encoding"),
        }
    }
}

impl SectionItem for Memory<'_> {
    type Section = wasm_encoder::MemorySection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Memory;

    fn encode(&self, section: &mut wasm_encoder::MemorySection) {
        assert!(self.exports.names.is_empty());
        match &self.kind {
            MemoryKind::Normal(t) => {
                section.memory(t.to_memory_type());
            }
            _ => panic!("MemoryKind should be normal during encoding"),
        }
    }
}

impl SectionItem for Global<'_> {
    type Section = wasm_encoder::GlobalSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Global;

    fn encode(&self, section: &mut wasm_encoder::GlobalSection) {
        assert!(self.exports.names.is_empty());
        let init = match &self.kind {
            GlobalKind::Inline(expr) => expr.to_const_expr(),
            _ => panic!("GlobalKind should be inline during encoding"),
        };
        section.global(self.ty.to_global_type(), &init);
    }
}

impl SectionItem for Export<'_> {
    type Section = wasm_encoder::ExportSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Export;

    fn encode(&self, section: &mut wasm_encoder::ExportSection) {
        section.export(self.name, self.kind.into(), self.item.unwrap_u32());
    }
}

impl From<ExportKind> for wasm_encoder::ExportKind {
    fn from(kind: ExportKind) -> Self {
        match kind {
            ExportKind::Func => Self::Func,
            ExportKind::Table => Self::Table,
            ExportKind::Memory => Self::Memory,
            ExportKind::Global => Self::Global,
            ExportKind::Tag => Self::Tag,
        }
    }
}

impl SectionItem for Elem<'_> {
    type Section = wasm_encoder::ElementSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Elem;

    fn encode(&self, section: &mut wasm_encoder::ElementSection) {
        use wasm_encoder::Elements;

        let elements = match &self.payload {
            ElemPayload::Indices(v) => {
                Elements::Functions(Cow::Owned(v.iter().map(|i| i.unwrap_u32()).collect()))
            }
            ElemPayload::Exprs { exprs, ty } => Elements::Expressions(
                (*ty).into(),
                Cow::Owned(exprs.iter().map(|e| e.to_const_expr()).collect()),
            ),
        };
        match &self.kind {
            ElemKind::Active { table, offset } => {
                section.active(
                    table.map(|t| t.unwrap_u32()),
                    &offset.to_const_expr(),
                    elements,
                );
            }
            ElemKind::Passive => {
                section.passive(elements);
            }
            ElemKind::Declared => {
                section.declared(elements);
            }
        }
    }
}

impl SectionItem for Data<'_> {
    type Section = wasm_encoder::DataSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Data;

    fn encode(&self, section: &mut wasm_encoder::DataSection) {
        let mut data = Vec::new();
        for val in self.data.iter() {
            val.push_onto(&mut data);
        }
        match &self.kind {
            DataKind::Passive => {
                section.passive(data);
            }
            DataKind::Active { memory, offset } => {
                section.active(memory.unwrap_u32(), &offset.to_const_expr(), data);
            }
        }
    }
}

impl Func<'_> {
    /// Encodes the function into `e` while returning all branch hints with
    /// known relative offsets after encoding.
    ///
    /// The `dwarf` field is optional and used to track debugging information
    /// for each instruction.
    fn encode(
        &self,
        section: &mut wasm_encoder::CodeSection,
        mut dwarf: Option<&mut dwarf::Dwarf>,
    ) -> Vec<wasm_encoder::BranchHint> {
        assert!(self.exports.names.is_empty());
        let (expr, locals) = match &self.kind {
            FuncKind::Inline {
                expression, locals, ..
            } => (expression, locals),
            _ => panic!("should only have inline functions in emission"),
        };

        if let Some(dwarf) = &mut dwarf {
            let index = match self.ty.index.as_ref().unwrap() {
                Index::Num(n, _) => *n,
                _ => unreachable!(),
            };
            dwarf.start_func(self.span, index, locals);
        }

        // Encode the function into a temporary vector because functions are
        // prefixed with their length. The temporary vector, when encoded,
        // encodes its length first then the body.
        let mut func =
            wasm_encoder::Function::new_with_locals_types(locals.iter().map(|t| t.ty.into()));
        let branch_hints = expr.encode(&mut func, dwarf.as_deref_mut());
        let func_size = func.byte_len();
        section.function(&func);

        if let Some(dwarf) = &mut dwarf {
            dwarf.end_func(func_size, section.byte_len());
        }

        branch_hints
    }
}

impl Expression<'_> {
    /// Encodes this expression into `e` and optionally tracks debugging
    /// information for each instruction in `dwarf`.
    ///
    /// Returns all branch hints, if any, found while parsing this function.
    fn encode(
        &self,
        func: &mut wasm_encoder::Function,
        mut dwarf: Option<&mut dwarf::Dwarf>,
    ) -> Vec<wasm_encoder::BranchHint> {
        let mut hints = Vec::with_capacity(self.branch_hints.len());
        let mut next_hint = self.branch_hints.iter().peekable();
        let mut tmp = Vec::new();

        for (i, instr) in self.instrs.iter().enumerate() {
            // Branch hints are stored in order of increasing `instr_index` so
            // check to see if the next branch hint matches this instruction's
            // index.
            if let Some(hint) = next_hint.next_if(|h| h.instr_index == i) {
                hints.push(wasm_encoder::BranchHint {
                    branch_func_offset: u32::try_from(func.byte_len() + tmp.len()).unwrap(),
                    branch_hint_value: hint.value,
                });
            }

            // If DWARF is enabled then track this instruction's binary offset
            // and source location.
            if let Some(dwarf) = &mut dwarf {
                if let Some(span) = self.instr_spans.as_ref().map(|s| s[i]) {
                    dwarf.instr(func.byte_len() + tmp.len(), span);
                }
            }

            // Finally emit the instruction and move to the next.
            instr.encode(&mut tmp);
        }
        func.raw(tmp.iter().copied());
        func.instructions().end();

        hints
    }

    fn to_const_expr(&self) -> wasm_encoder::ConstExpr {
        let mut tmp = Vec::new();
        for instr in self.instrs.iter() {
            instr.encode(&mut tmp);
        }
        wasm_encoder::ConstExpr::raw(tmp)
    }
}

impl Encode for BlockType<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        // block types using an index are encoded as an sleb, not a uleb
        if let Some(Index::Num(n, _)) = &self.ty.index {
            return i64::from(*n).encode(e);
        }
        let ty = self
            .ty
            .inline
            .as_ref()
            .expect("function type not filled in");
        if ty.params.is_empty() && ty.results.is_empty() {
            return e.push(0x40);
        }
        if ty.params.is_empty() && ty.results.len() == 1 {
            return ty.results[0].encode(e);
        }
        panic!("multi-value block types should have an index");
    }
}

impl Encode for LaneArg {
    fn encode(&self, e: &mut Vec<u8>) {
        self.lane.encode(e);
    }
}

impl Encode for MemArg<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        match &self.memory {
            Index::Num(0, _) => {
                self.align.trailing_zeros().encode(e);
                self.offset.encode(e);
            }
            _ => {
                (self.align.trailing_zeros() | (1 << 6)).encode(e);
                self.memory.encode(e);
                self.offset.encode(e);
            }
        }
    }
}

impl Encode for Ordering {
    fn encode(&self, buf: &mut Vec<u8>) {
        let flag: u8 = match self {
            Ordering::SeqCst => 0,
            Ordering::AcqRel => 1,
        };
        flag.encode(buf);
    }
}

impl<T> Encode for Ordered<T>
where
    T: Encode,
{
    fn encode(&self, buf: &mut Vec<u8>) {
        self.ordering.encode(buf);
        self.inner.encode(buf);
    }
}

impl Encode for LoadOrStoreLane<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.memarg.encode(e);
        self.lane.encode(e);
    }
}

impl Encode for CallIndirect<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.ty.unwrap_u32().encode(e);
        self.table.encode(e);
    }
}

impl Encode for TableInit<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.elem.encode(e);
        self.table.encode(e);
    }
}

impl Encode for TableCopy<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.dst.encode(e);
        self.src.encode(e);
    }
}

impl Encode for TableArg<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.dst.encode(e);
    }
}

impl Encode for MemoryArg<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.mem.encode(e);
    }
}

impl Encode for MemoryInit<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.data.encode(e);
        self.mem.encode(e);
    }
}

impl Encode for MemoryCopy<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.dst.encode(e);
        self.src.encode(e);
    }
}

impl Encode for BrTableIndices<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.labels.encode(e);
        self.default.encode(e);
    }
}

impl Encode for F32 {
    fn encode(&self, e: &mut Vec<u8>) {
        e.extend_from_slice(&self.bits.to_le_bytes());
    }
}

impl Encode for F64 {
    fn encode(&self, e: &mut Vec<u8>) {
        e.extend_from_slice(&self.bits.to_le_bytes());
    }
}

#[derive(Default)]
struct Names<'a> {
    module: Option<&'a str>,
    funcs: Vec<(u32, &'a str)>,
    func_idx: u32,
    locals: Vec<(u32, Vec<(u32, &'a str)>)>,
    labels: Vec<(u32, Vec<(u32, &'a str)>)>,
    globals: Vec<(u32, &'a str)>,
    global_idx: u32,
    memories: Vec<(u32, &'a str)>,
    memory_idx: u32,
    tables: Vec<(u32, &'a str)>,
    table_idx: u32,
    tags: Vec<(u32, &'a str)>,
    tag_idx: u32,
    types: Vec<(u32, &'a str)>,
    type_idx: u32,
    data: Vec<(u32, &'a str)>,
    data_idx: u32,
    elems: Vec<(u32, &'a str)>,
    elem_idx: u32,
    fields: Vec<(u32, Vec<(u32, &'a str)>)>,
}

fn find_names<'a>(
    module_id: &Option<Id<'a>>,
    module_name: &Option<NameAnnotation<'a>>,
    fields: &[ModuleField<'a>],
    types: &'a [RecOrType<'a>],
) -> Names<'a> {
    fn get_name<'a>(id: &Option<Id<'a>>, name: &Option<NameAnnotation<'a>>) -> Option<&'a str> {
        name.as_ref().map(|n| n.name).or(id.and_then(|id| {
            if id.is_gensym() {
                None
            } else {
                Some(id.name())
            }
        }))
    }

    enum Name {
        Type,
        Global,
        Func,
        Memory,
        Table,
        Tag,
        Elem,
        Data,
    }

    let mut ret = Names::default();
    ret.module = get_name(module_id, module_name);
    let mut names = Vec::new();
    for field in fields {
        // Extract the kind/id/name from whatever kind of field this is...
        let (kind, id, name) = match field {
            ModuleField::Import(imports) => {
                for sig in imports.item_sigs() {
                    let name = match sig.kind {
                        ItemKind::Func(_) | ItemKind::FuncExact(_) => Name::Func,
                        ItemKind::Table(_) => Name::Table,
                        ItemKind::Memory(_) => Name::Memory,
                        ItemKind::Global(_) => Name::Global,
                        ItemKind::Tag(_) => Name::Tag,
                    };
                    names.push((name, &sig.id, &sig.name, field));
                }
                continue;
            }
            ModuleField::Global(g) => (Name::Global, &g.id, &g.name),
            ModuleField::Table(t) => (Name::Table, &t.id, &t.name),
            ModuleField::Memory(m) => (Name::Memory, &m.id, &m.name),
            ModuleField::Tag(t) => (Name::Tag, &t.id, &t.name),
            ModuleField::Type(t) => (Name::Type, &t.id, &t.name),
            ModuleField::Rec(r) => {
                for ty in &r.types {
                    names.push((Name::Type, &ty.id, &ty.name, field));
                }
                continue;
            }
            ModuleField::Elem(e) => (Name::Elem, &e.id, &e.name),
            ModuleField::Data(d) => (Name::Data, &d.id, &d.name),
            ModuleField::Func(f) => (Name::Func, &f.id, &f.name),
            ModuleField::Export(_) | ModuleField::Start(_) | ModuleField::Custom(_) => continue,
        };
        names.push((kind, id, name, field));
    }

    for (kind, id, name, field) in names {
        // .. and using the kind we can figure out where to place this name
        let (list, idx) = match kind {
            Name::Func => (&mut ret.funcs, &mut ret.func_idx),
            Name::Table => (&mut ret.tables, &mut ret.table_idx),
            Name::Memory => (&mut ret.memories, &mut ret.memory_idx),
            Name::Global => (&mut ret.globals, &mut ret.global_idx),
            Name::Tag => (&mut ret.tags, &mut ret.tag_idx),
            Name::Type => (&mut ret.types, &mut ret.type_idx),
            Name::Elem => (&mut ret.elems, &mut ret.elem_idx),
            Name::Data => (&mut ret.data, &mut ret.data_idx),
        };
        if let Some(name) = get_name(id, name) {
            list.push((*idx, name));
        }

        // Handle module locals separately from above
        if let ModuleField::Func(f) = field {
            let mut local_names = Vec::new();
            let mut label_names = Vec::new();
            let mut local_idx = 0;
            let mut label_idx = 0;
            let mut discard_locals = false;

            if let Some(ty) = &f.ty.inline {
                // Consult the inline type listed for local names of parameters.
                // This is specifically preserved during the name resolution
                // pass, but only for functions, so here we can look at the
                // original source's names.
                for (id, name, _) in ty.params.iter() {
                    if let Some(name) = get_name(id, name) {
                        local_names.push((local_idx, name));
                    }
                    local_idx += 1;
                }
            } else {
                // If the inline type isn't listed then it's either not present
                // (e.g. no params or results) or it was referenced by index.
                // Either way we've got the index here, so look it up in the
                // list of types and see how many parameters this function's
                // type has.
                let index = match f.ty.index.as_ref().unwrap() {
                    Index::Num(n, _) => *n,
                    _ => unreachable!(),
                };

                match func_type(types, index) {
                    Some(ft) => local_idx = ft.params.len() as u32,
                    // If the function type index is invalid then skip
                    // preserving names since we don't know how many parameters
                    // this function will have so we don't know where to start
                    // indexing at.
                    None => discard_locals = true,
                }
            }

            if let FuncKind::Inline {
                locals, expression, ..
            } = &f.kind
            {
                for local in locals.iter() {
                    if let Some(name) = get_name(&local.id, &local.name) {
                        local_names.push((local_idx, name));
                    }
                    local_idx += 1;
                }

                for i in expression.instrs.iter() {
                    match i {
                        Instruction::If(block)
                        | Instruction::Block(block)
                        | Instruction::Loop(block)
                        | Instruction::Try(block)
                        | Instruction::TryTable(TryTable { block, .. }) => {
                            if let Some(name) = get_name(&block.label, &block.label_name) {
                                label_names.push((label_idx, name));
                            }
                            label_idx += 1;
                        }
                        _ => {}
                    }
                }
            }
            if !discard_locals && local_names.len() > 0 {
                ret.locals.push((*idx, local_names));
            }
            if label_names.len() > 0 {
                ret.labels.push((*idx, label_names));
            }
        }

        // Handle struct fields separately from above
        if let ModuleField::Type(ty) = field {
            let mut field_names = vec![];
            match &ty.def.kind {
                InnerTypeKind::Func(_) | InnerTypeKind::Array(_) | InnerTypeKind::Cont(_) => {}
                InnerTypeKind::Struct(ty_struct) => {
                    for (idx, field) in ty_struct.fields.iter().enumerate() {
                        if let Some(name) = get_name(&field.id, &None) {
                            field_names.push((idx as u32, name))
                        }
                    }
                }
            }
            if field_names.len() > 0 {
                ret.fields.push((*idx, field_names))
            }
        }

        *idx += 1;
    }

    return ret;
}

impl Names<'_> {
    fn is_empty(&self) -> bool {
        self.module.is_none()
            && self.funcs.is_empty()
            && self.locals.is_empty()
            && self.labels.is_empty()
            && self.globals.is_empty()
            && self.memories.is_empty()
            && self.tables.is_empty()
            && self.types.is_empty()
            && self.elems.is_empty()
            && self.data.is_empty()
            && self.fields.is_empty()
            && self.tags.is_empty()
    }
}

impl Names<'_> {
    fn to_name_section(&self) -> wasm_encoder::NameSection {
        let mut names = wasm_encoder::NameSection::default();

        if let Some(id) = self.module {
            names.module(id);
        }
        let name_map = |indices: &[(u32, &str)]| {
            if indices.is_empty() {
                return None;
            }
            let mut map = wasm_encoder::NameMap::default();
            for (idx, name) in indices {
                map.append(*idx, *name);
            }
            Some(map)
        };
        let indirect_name_map = |indices: &[(u32, Vec<(u32, &str)>)]| {
            if indices.is_empty() {
                return None;
            }
            let mut map = wasm_encoder::IndirectNameMap::default();
            for (idx, names) in indices {
                if let Some(names) = name_map(names) {
                    map.append(*idx, &names);
                }
            }
            Some(map)
        };
        if let Some(map) = name_map(&self.funcs) {
            names.functions(&map);
        }
        if let Some(map) = indirect_name_map(&self.locals) {
            names.locals(&map);
        }
        if let Some(map) = indirect_name_map(&self.labels) {
            names.labels(&map);
        }
        if let Some(map) = name_map(&self.types) {
            names.types(&map);
        }
        if let Some(map) = name_map(&self.tables) {
            names.tables(&map);
        }
        if let Some(map) = name_map(&self.memories) {
            names.memories(&map);
        }
        if let Some(map) = name_map(&self.globals) {
            names.globals(&map);
        }
        if let Some(map) = name_map(&self.elems) {
            names.elements(&map);
        }
        if let Some(map) = name_map(&self.data) {
            names.data(&map);
        }
        if let Some(map) = indirect_name_map(&self.fields) {
            names.fields(&map);
        }
        if let Some(map) = name_map(&self.tags) {
            names.tags(&map);
        }
        names
    }
}

impl Encode for Id<'_> {
    fn encode(&self, dst: &mut Vec<u8>) {
        assert!(!self.is_gensym());
        self.name().encode(dst);
    }
}

impl<'a> Encode for TryTable<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        self.block.encode(dst);
        self.catches.encode(dst);
    }
}

impl<'a> Encode for TryTableCatch<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        let flag_byte: u8 = match self.kind {
            TryTableCatchKind::Catch(..) => 0,
            TryTableCatchKind::CatchRef(..) => 1,
            TryTableCatchKind::CatchAll => 2,
            TryTableCatchKind::CatchAllRef => 3,
        };
        flag_byte.encode(dst);
        match self.kind {
            TryTableCatchKind::Catch(tag) | TryTableCatchKind::CatchRef(tag) => {
                tag.encode(dst);
            }
            TryTableCatchKind::CatchAll | TryTableCatchKind::CatchAllRef => {}
        }
        self.label.encode(dst);
    }
}

impl<'a> Encode for ContBind<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        self.argument_index.encode(dst);
        self.result_index.encode(dst);
    }
}

impl<'a> Encode for Resume<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        self.type_index.encode(dst);
        self.table.encode(dst);
    }
}

impl<'a> Encode for ResumeThrow<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        self.type_index.encode(dst);
        self.tag_index.encode(dst);
        self.table.encode(dst);
    }
}

impl<'a> Encode for ResumeThrowRef<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        self.type_index.encode(dst);
        self.table.encode(dst);
    }
}

impl<'a> Encode for ResumeTable<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        self.handlers.encode(dst);
    }
}

impl<'a> Encode for Handle<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        match self {
            Handle::OnLabel { tag, label } => {
                dst.push(0x00);
                tag.encode(dst);
                label.encode(dst);
            }
            Handle::OnSwitch { tag } => {
                dst.push(0x01);
                tag.encode(dst);
            }
        }
    }
}

impl<'a> Encode for Switch<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        self.type_index.encode(dst);
        self.tag_index.encode(dst);
    }
}

impl Encode for V128Const {
    fn encode(&self, dst: &mut Vec<u8>) {
        dst.extend_from_slice(&self.to_le_bytes());
    }
}

impl Encode for I8x16Shuffle {
    fn encode(&self, dst: &mut Vec<u8>) {
        dst.extend_from_slice(&self.lanes);
    }
}

impl<'a> Encode for SelectTypes<'a> {
    fn encode(&self, dst: &mut Vec<u8>) {
        match &self.tys {
            Some(list) => {
                dst.push(0x1c);
                list.encode(dst);
            }
            None => dst.push(0x1b),
        }
    }
}

impl Custom<'_> {
    fn encode(&self, module: &mut wasm_encoder::Module) {
        match self {
            Custom::Raw(r) => {
                module.section(&r.to_section());
            }
            Custom::Producers(p) => {
                module.section(&p.to_section());
            }
            Custom::Dylink0(p) => {
                module.section(&p.to_section());
            }
        }
    }
}

impl RawCustomSection<'_> {
    fn to_section(&self) -> wasm_encoder::CustomSection<'_> {
        let mut ret = Vec::new();
        for list in self.data.iter() {
            ret.extend_from_slice(list);
        }
        wasm_encoder::CustomSection {
            name: self.name.into(),
            data: ret.into(),
        }
    }
}

impl Producers<'_> {
    pub(crate) fn to_section(&self) -> wasm_encoder::ProducersSection {
        let mut ret = wasm_encoder::ProducersSection::default();
        for (name, fields) in self.fields.iter() {
            let mut field = wasm_encoder::ProducersField::new();
            for (key, value) in fields {
                field.value(key, value);
            }
            ret.field(name, &field);
        }
        ret
    }
}

impl Dylink0<'_> {
    fn to_section(&self) -> wasm_encoder::CustomSection<'_> {
        let mut e = Vec::new();
        for section in self.subsections.iter() {
            e.push(section.id());
            let mut tmp = Vec::new();
            section.encode(&mut tmp);
            tmp.encode(&mut e);
        }
        wasm_encoder::CustomSection {
            name: "dylink.0".into(),
            data: e.into(),
        }
    }
}

impl Encode for Dylink0Subsection<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        match self {
            Dylink0Subsection::MemInfo {
                memory_size,
                memory_align,
                table_size,
                table_align,
            } => {
                memory_size.encode(e);
                memory_align.encode(e);
                table_size.encode(e);
                table_align.encode(e);
            }
            Dylink0Subsection::Needed(libs) => libs.encode(e),
            Dylink0Subsection::ExportInfo(list) => list.encode(e),
            Dylink0Subsection::ImportInfo(list) => list.encode(e),
            Dylink0Subsection::RuntimePath(list) => list.encode(e),
        }
    }
}

impl SectionItem for Tag<'_> {
    type Section = wasm_encoder::TagSection;
    const ANCHOR: CustomPlaceAnchor = CustomPlaceAnchor::Tag;

    fn encode(&self, section: &mut wasm_encoder::TagSection) {
        section.tag(self.ty.to_tag_type());
        match &self.kind {
            TagKind::Inline() => {}
            _ => panic!("TagKind should be inline during encoding"),
        }
    }
}

impl Encode for StructAccess<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.r#struct.encode(e);
        self.field.encode(e);
    }
}

impl Encode for ArrayFill<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.array.encode(e);
    }
}

impl Encode for ArrayCopy<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.dest_array.encode(e);
        self.src_array.encode(e);
    }
}

impl Encode for ArrayInit<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.array.encode(e);
        self.segment.encode(e);
    }
}

impl Encode for ArrayNewFixed<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.array.encode(e);
        self.length.encode(e);
    }
}

impl Encode for ArrayNewData<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.array.encode(e);
        self.data_idx.encode(e);
    }
}

impl Encode for ArrayNewElem<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        self.array.encode(e);
        self.elem_idx.encode(e);
    }
}

impl Encode for RefTest<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        e.push(0xfb);
        if self.r#type.nullable {
            e.push(0x15);
        } else {
            e.push(0x14);
        }
        self.r#type.heap.encode(e);
    }
}

impl Encode for RefCast<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        e.push(0xfb);
        if self.r#type.nullable {
            e.push(0x17);
        } else {
            e.push(0x16);
        }
        self.r#type.heap.encode(e);
    }
}

fn br_on_cast_flags(from_nullable: bool, to_nullable: bool) -> u8 {
    let mut flag = 0;
    if from_nullable {
        flag |= 1 << 0;
    }
    if to_nullable {
        flag |= 1 << 1;
    }
    flag
}

impl Encode for BrOnCast<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        e.push(0xfb);
        e.push(0x18);
        e.push(br_on_cast_flags(
            self.from_type.nullable,
            self.to_type.nullable,
        ));
        self.label.encode(e);
        self.from_type.heap.encode(e);
        self.to_type.heap.encode(e);
    }
}

impl Encode for BrOnCastFail<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        e.push(0xfb);
        e.push(0x19);
        e.push(br_on_cast_flags(
            self.from_type.nullable,
            self.to_type.nullable,
        ));
        self.label.encode(e);
        self.from_type.heap.encode(e);
        self.to_type.heap.encode(e);
    }
}

impl Encode for RefCastDescEq<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        e.push(0xfb);
        if self.r#type.nullable {
            e.push(0x24);
        } else {
            e.push(0x23);
        }
        self.r#type.heap.encode(e);
    }
}

impl Encode for BrOnCastDescEq<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        e.push(0xfb);
        e.push(0x25);
        e.push(br_on_cast_flags(
            self.from_type.nullable,
            self.to_type.nullable,
        ));
        self.label.encode(e);
        self.from_type.heap.encode(e);
        self.to_type.heap.encode(e);
    }
}

impl Encode for BrOnCastDescEqFail<'_> {
    fn encode(&self, e: &mut Vec<u8>) {
        e.push(0xfb);
        e.push(0x26);
        e.push(br_on_cast_flags(
            self.from_type.nullable,
            self.to_type.nullable,
        ));
        self.label.encode(e);
        self.from_type.heap.encode(e);
        self.to_type.heap.encode(e);
    }
}
