use crate::csharp_ident::ToCSharpIdent;
use crate::function::ResourceInfo;
use crate::interface::{InterfaceFragment, InterfaceGenerator, InterfaceTypeAndFragments};
use crate::{CSharpRuntime, Opts};
use heck::ToUpperCamelCase;
use indexmap::IndexMap;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::ops::Deref;
use std::{iter, mem};
use wit_bindgen_core::{Direction, Files, InterfaceGenerator as _, Types, WorldGenerator, uwrite};
use wit_component::WitPrinter;
use wit_parser::abi::WasmType;
use wit_parser::{
    Function, InterfaceId, Resolve, SizeAlign, Type, TypeDefKind, TypeId, TypeOwner, WorldId,
    WorldItem, WorldKey,
};

/// CSharp is the world generator for wit files. It coordinates all the generated code.
/// It will call out to interfaceGenerator (and in turn FunctionGenerator)
/// to get the source to put in each file. It will then assemble those files
/// adding boilerplate, c# import decorations, and the source generated by
/// the Interface and Function generators.
#[derive(Default)]
pub struct CSharp {
    pub(crate) opts: Opts,
    pub(crate) types: Types,
    pub(crate) name: String,
    pub(crate) return_area_size: usize,
    pub(crate) return_area_align: usize,
    pub(crate) tuple_counts: HashSet<usize>,
    pub(crate) needs_result: bool,
    pub(crate) needs_option: bool,
    pub(crate) needs_export_return_area: bool,
    pub(crate) needs_rep_table: bool,
    pub(crate) needs_wit_exception: bool,
    pub(crate) needs_async_support: bool,
    pub(crate) needs_align_stack_ptr: bool,
    pub(crate) interface_fragments: HashMap<String, InterfaceTypeAndFragments>,
    pub(crate) world_fragments: Vec<InterfaceFragment>,
    pub(crate) sizes: SizeAlign,
    pub(crate) interface_names: HashMap<InterfaceId, String>,
    pub(crate) anonymous_type_owners: HashMap<TypeId, TypeOwner>,
    pub(crate) all_resources: HashMap<TypeId, ResourceInfo>,
    pub(crate) world_resources: HashMap<TypeId, ResourceInfo>,
    pub(crate) import_funcs_called: bool,
    pub(crate) generated_future_types: HashSet<Option<Type>>,
    // Top level types that are bidirectional like enums, to save code size and not duplicate whene unnecessary.
    pub(crate) bidirectional_types_src: HashSet<String>,
}

impl CSharp {
    pub(crate) fn access_modifier(&self) -> &'static str {
        if self.opts.internal {
            "internal"
        } else {
            "public"
        }
    }

    pub(crate) fn qualifier(&self) -> String {
        let world = self.name.to_upper_camel_case();
        format!("{world}World.")
    }

    fn interface<'a>(
        &'a mut self,
        resolve: &'a Resolve,
        name: &'a str,
        direction: Direction,
        is_world: bool,
    ) -> InterfaceGenerator<'a> {
        InterfaceGenerator {
            src: String::new(),
            csharp_interop_src: String::new(),
            stub: String::new(),
            csharp_gen: self,
            resolve,
            name,
            direction,
            futures: Vec::new(),
            streams: Vec::new(),
            is_world,
        }
    }

    // returns the qualifier and last part
    pub(crate) fn get_class_name_from_qualified_name(qualified_type: &str) -> (String, String) {
        let parts: Vec<&str> = qualified_type.split('.').collect();
        if let Some(last_part) = parts.last() {
            let mut qualifier = qualified_type.strip_suffix(last_part);
            if qualifier.is_some() {
                qualifier = qualifier.unwrap().strip_suffix(".");
            }
            (qualifier.unwrap_or("").to_string(), last_part.to_string())
        } else {
            (String::new(), String::new())
        }
    }

    // We can share some types to save some code size and allow a more intuitive user experience.
    pub(crate) fn type_is_bidirectional(resolve: &Resolve, ty: &TypeId) -> bool {
        let type_def = &resolve.types[*ty];
        let kind = &type_def.kind;

        return match kind {
            TypeDefKind::Flags(_) => true,
            TypeDefKind::Enum(_) => true,
            _ => false,
        };
    }

    fn write_world_fragments(
        &mut self,
        direction: Direction,
        direction_name: &str,
        src: &mut String,
        access: &str,
        name: &String,
    ) {
        if self
            .world_fragments
            .iter()
            .any(|f| f.direction == Some(direction))
        {
            uwrite!(
                src,
                "
                {access} interface I{name}World{direction_name}{{
                "
            );
            src.push_str(
                &self
                    .world_fragments
                    .iter()
                    .filter(|f| f.direction == Some(direction))
                    .map(|f| f.csharp_src.deref())
                    .collect::<Vec<_>>()
                    .join("\n"),
            );
            src.push_str("}\n");
        }
    }
}

impl WorldGenerator for CSharp {
    fn preprocess(&mut self, resolve: &Resolve, world: WorldId) -> anyhow::Result<()> {
        let name = &resolve.worlds[world].name;
        self.types.analyze(resolve);
        self.types.collect_equal_types(resolve, world, &|a| {
            match resolve.types[a].kind {
                // We'll start with the same split as Rust and change as required.
                TypeDefKind::Type(_)
                | TypeDefKind::Handle(_)
                | TypeDefKind::List(_)
                | TypeDefKind::Tuple(_)
                | TypeDefKind::Option(_)
                | TypeDefKind::Result(_)
                | TypeDefKind::Future(_)
                | TypeDefKind::Stream(_)
                | TypeDefKind::Map(..)
                | TypeDefKind::FixedLengthList(..) => true,

                TypeDefKind::Record(_)
                | TypeDefKind::Variant(_)
                | TypeDefKind::Enum(_)
                | TypeDefKind::Flags(_)
                | TypeDefKind::Resource
                | TypeDefKind::Unknown => false,
            }
        });
        self.name = name.to_string();
        self.sizes.fill(resolve);
        Ok(())
    }

    fn import_interface(
        &mut self,
        resolve: &Resolve,
        key: &WorldKey,
        id: InterfaceId,
        _files: &mut Files,
    ) -> anyhow::Result<()> {
        let name = interface_name(self, resolve, key, Direction::Import);
        self.interface_names.insert(id, name.clone());
        let mut r#gen = self.interface(resolve, &name, Direction::Import, false);

        let mut old_resources = mem::take(&mut r#gen.csharp_gen.all_resources);
        r#gen.types(id);
        let new_resources = mem::take(&mut r#gen.csharp_gen.all_resources);
        old_resources.extend(new_resources.clone());
        r#gen.csharp_gen.all_resources = old_resources;
        let import_module_name = &resolve.name_world_key(key);
        for (resource, funcs) in by_resource(
            resolve.interfaces[id]
                .functions
                .iter()
                .map(|(k, v)| (k.as_str(), v)),
            new_resources.keys().copied(),
        ) {
            if let Some(resource) = resource {
                r#gen.start_resource(resource, Some(key));
            }

            for func in funcs {
                r#gen.import(import_module_name, func);
            }

            if resource.is_some() {
                r#gen.end_resource();
            }
        }

        // for anonymous types
        r#gen.define_interface_types(id);

        r#gen.add_futures_or_streams(import_module_name, false, true);

        r#gen.add_futures_or_streams(import_module_name, false, false);

        r#gen.add_interface_fragment(false);

        Ok(())
    }

    fn import_funcs(
        &mut self,
        resolve: &Resolve,
        world: WorldId,
        funcs: &[(&str, &Function)],
        _files: &mut Files,
    ) {
        self.import_funcs_called = true;

        let name = &format!("{}-world", resolve.worlds[world].name).to_upper_camel_case();
        let name = &format!("{name}.I{name}Imports");
        let mut r#gen = self.interface(resolve, name, Direction::Import, true);

        //TODO: This generates resource types for imports even when not used, i.e. no imported functions.
        for (resource, funcs) in by_resource(
            funcs.iter().copied(),
            r#gen.csharp_gen.world_resources.keys().copied(),
        ) {
            if let Some(resource) = resource {
                r#gen.start_resource(resource, None);
            }

            for func in funcs {
                r#gen.import("$root", func);
            }

            if resource.is_some() {
                r#gen.end_resource();
            }
        }

        r#gen.add_world_fragment(Some(Direction::Import));
    }

    fn export_interface(
        &mut self,
        resolve: &Resolve,
        key: &WorldKey,
        id: InterfaceId,
        _files: &mut Files,
    ) -> anyhow::Result<()> {
        let name = interface_name(self, resolve, key, Direction::Export);
        self.interface_names.insert(id, name.clone());
        let mut r#gen = self.interface(resolve, &name, Direction::Export, false);

        let mut old_resources = mem::take(&mut r#gen.csharp_gen.all_resources);
        r#gen.types(id);
        let new_resources = mem::take(&mut r#gen.csharp_gen.all_resources);
        old_resources.extend(new_resources.clone());
        r#gen.csharp_gen.all_resources = old_resources;
        for (resource, funcs) in by_resource(
            resolve.interfaces[id]
                .functions
                .iter()
                .map(|(k, v)| (k.as_str(), v)),
            new_resources.keys().copied(),
        ) {
            if let Some(resource) = resource {
                r#gen.start_resource(resource, Some(key));
            }

            for func in funcs {
                r#gen.export(func, Some(key));
            }

            if resource.is_some() {
                r#gen.end_resource();
            }
        }

        // for anonymous types
        r#gen.define_interface_types(id);

        let import_module_name = &resolve.name_world_key(key);
        r#gen.add_futures_or_streams(&format!("[export]{import_module_name}"), true, true);

        r#gen.add_futures_or_streams(&format!("[export]{import_module_name}"), true, false);

        r#gen.add_interface_fragment(true);
        Ok(())
    }

    fn export_funcs(
        &mut self,
        resolve: &Resolve,
        world_id: WorldId,
        funcs: &[(&str, &Function)],
        _files: &mut Files,
    ) -> anyhow::Result<()> {
        let name = &format!("{}-world", resolve.worlds[world_id].name).to_upper_camel_case();
        let name = &format!("{name}.I{name}Exports");
        let mut r#gen = self.interface(resolve, name, Direction::Export, true);

        // Write the export types used by the functions.
        let world = &resolve.worlds[world_id];
        let mut types = Vec::new();
        for (name, export) in world.exports.iter() {
            match name {
                WorldKey::Name(name) => match export {
                    WorldItem::Type { id, .. } => {
                        if !CSharp::type_is_bidirectional(resolve, &id) {
                            types.push((name.as_str(), *id))
                        }
                    }
                    _ => {}
                },
                WorldKey::Interface(_) => {}
            }
        }

        if !types.is_empty() {
            export_types(&mut r#gen, &types);
        }

        for (resource, funcs) in by_resource(
            funcs.iter().copied(),
            r#gen.csharp_gen.world_resources.keys().copied(),
        ) {
            if let Some(resource) = resource {
                r#gen.start_resource(resource, None);
            }

            for func in funcs {
                r#gen.export(func, None);
            }

            if resource.is_some() {
                r#gen.end_resource();
            }
        }

        r#gen.add_world_fragment(Some(Direction::Export));
        Ok(())
    }

    fn import_types(
        &mut self,
        resolve: &Resolve,
        world: WorldId,
        types: &[(&str, TypeId)],
        _files: &mut Files,
    ) {
        let name = &format!("{}-world", resolve.worlds[world].name).to_upper_camel_case();
        let name = &format!("{name}.I{name}Imports");
        let mut r#gen = self.interface(resolve, name, Direction::Import, false);

        let mut old_resources = mem::take(&mut r#gen.csharp_gen.all_resources);
        for (ty_name, ty) in types {
            r#gen.define_type(ty_name, *ty);
        }
        let new_resources = mem::take(&mut r#gen.csharp_gen.all_resources);
        old_resources.extend(new_resources.clone());
        r#gen.csharp_gen.all_resources = old_resources;
        r#gen.csharp_gen.world_resources = new_resources;

        r#gen.add_world_fragment(Some(Direction::Import));
    }

    fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> anyhow::Result<()> {
        if !self.import_funcs_called {
            // Ensure that we emit type declarations for any top-level imported resource types:
            self.import_funcs(resolve, id, &[], files);
        }

        let world = &resolve.worlds[id];
        let world_namespace = self.qualifier();
        let world_namespace = world_namespace.strip_suffix(".").unwrap();
        let namespace = world_namespace;
        let name = world.name.to_upper_camel_case();

        let version = env!("CARGO_PKG_VERSION");
        let header = format!(
            "\
            // Generated by `wit-bindgen` {version}. DO NOT EDIT!
            // <auto-generated />
            #nullable enable
            "
        );
        let mut src = String::new();
        src.push_str(&header);

        let mut producers = wasm_metadata::Producers::empty();
        producers.add(
            "processed-by",
            env!("CARGO_PKG_NAME"),
            env!("CARGO_PKG_VERSION"),
        );

        let access = self.access_modifier();

        if self.needs_async_support {
            uwrite!(
                src,
                "
                    using System.Runtime.CompilerServices;

                "
            );
        }

        uwrite!(
            src,
            "
                using System;
                using System.Runtime.InteropServices;
                using System.Collections.Concurrent;
                using System.Threading;
                using System.Threading.Tasks;
            "
        );
        uwrite!(
            src,
            "
             namespace {world_namespace} {{

            "
        );

        src.push_str(
            &self
                .world_fragments
                .iter()
                .filter(|f| f.direction.is_none())
                .map(|f| f.csharp_src.deref())
                .collect::<Vec<_>>()
                .join("\n"),
        );

        src.push_str(
            self.bidirectional_types_src
                .iter()
                .cloned()
                .collect::<Vec<_>>()
                .join("\n")
                .as_str(),
        );
        self.write_world_fragments(Direction::Import, "Imports", &mut src, access, &name);
        self.write_world_fragments(Direction::Export, "Exports", &mut src, access, &name);

        if self.needs_result {
            uwrite!(
                src,
                r#"

                {access} readonly struct None {{}}

                [global::System.Runtime.InteropServices.StructLayoutAttribute(global::System.Runtime.InteropServices.LayoutKind.Sequential)]
                {access} readonly struct Result<TOk, TErr>
                {{
                    {access} readonly byte Tag;
                    private readonly object value;

                    private Result(byte tag, object value)
                    {{
                        Tag = tag;
                        this.value = value;
                    }}

                    {access} static Result<TOk, TErr> Ok(TOk ok)
                    {{
                        return new Result<TOk, TErr>(Tags.Ok, ok!);
                    }}

                    {access} static Result<TOk, TErr> Err(TErr err)
                    {{
                        return new Result<TOk, TErr>(Tags.Err, err!);
                    }}

                    {access} bool IsOk => Tag == Tags.Ok;
                    {access} bool IsErr => Tag == Tags.Err;

                    {access} TOk AsOk
                    {{
                        get
                        {{
                            if (Tag == Tags.Ok)
                            {{
                                return (TOk)value;
                            }}

                            throw new global::System.ArgumentException("expected k, got " + Tag);
                        }}
                    }}

                    {access} TErr AsErr
                    {{
                        get
                        {{
                            if (Tag == Tags.Err)
                            {{
                                return (TErr)value;
                            }}

                            throw new global::System.ArgumentException("expected Err, got " + Tag);
                        }}
                    }}

                    {access} class Tags
                    {{
                        {access} const byte Ok = 0;
                        {access} const byte Err = 1;
                    }}
                }}
                "#,
            )
        }

        if self.needs_option {
            uwrite!(
                src,
                r#"

                {access} class Option<T> {{
                    private static Option<T> none = new ();

                    private Option()
                    {{
                        HasValue = false;
                    }}

                    {access} Option(T v)
                    {{
                        HasValue = true;
                        Value = v;
                    }}

                    {access} static Option<T> None => none;

                    [global::System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute(true, nameof(Value))]
                    {access} bool HasValue {{ get; }}

                    {access} T? Value {{ get; }}
                }}
                "#,
            )
        }

        if self.needs_wit_exception {
            uwrite!(
                src,
                r#"
                {access} class WitException: global::System.Exception {{
                    {access} object Value {{ get; }}
                    {access} uint NestingLevel {{ get; }}

                    {access} WitException(object v, uint level)
                    {{
                        Value = v;
                        NestingLevel = level;
                    }}
                }}

                {access} class WitException<T>: WitException {{
                    {access} T TypedValue {{ get {{ return (T)this.Value;}} }}

                    {access} WitException(T v, uint level) : base(v!, level)
                    {{
                    }}
                }}
                "#,
            )
        }

        if self.needs_async_support {
            self.needs_export_return_area = true;
        }

        // Declare a statically-allocated return area, if needed. We only do
        // this for export bindings, because import bindings allocate their
        // return-area on the stack.
        if self.needs_export_return_area {
            let mut ret_area_str = String::new();

            //TODO: only generate if used.  Currently we need this for any async function, even if it returns void.
            let (array_size, element_type) = if self.return_area_size == 0 {
                (1, "byte".to_owned())
            } else {
                crate::world_generator::dotnet_aligned_array(
                    self.return_area_size,
                    self.return_area_align,
                )
            };

            uwrite!(
                ret_area_str,
                "
                {access} static class InteropReturnArea
                {{
                    [global::System.Runtime.CompilerServices.InlineArrayAttribute({0})]
                    [global::System.Runtime.InteropServices.StructLayoutAttribute(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = {1})]
                    internal struct ReturnArea
                    {{
                        private {2} buffer;

                        internal unsafe nint AddressOfReturnArea()
                        {{
                            return (nint)global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref buffer);
                        }}
                    }}

                    [global::System.ThreadStaticAttribute]
                    [global::System.Runtime.CompilerServices.FixedAddressValueTypeAttribute]
                    internal static ReturnArea returnArea = default;
                }}
                ",
                array_size,
                self.return_area_align,
                element_type
            );

            src.push_str(&ret_area_str);
        }

        if self.needs_align_stack_ptr {
            uwrite!(
                src,
                "
                {access} static class MemoryHelper
                {{
                    {access} static unsafe void* AlignStackPtr(void* stackAddress, uint alignment)
                    {{
                        return (void*)(((nint)stackAddress) + ((int)alignment - 1) & -(int)alignment);
                    }}
                }}
                "
            );
        }

        if self.needs_rep_table {
            src.push('\n');
            src.push_str(include_str!("RepTable.cs"));
        }

        if !&self.world_fragments.is_empty() {
            src.push('\n');

            if self
                .world_fragments
                .iter()
                .any(|f| f.direction == Some(Direction::Import))
            {
                src.push_str("\n");

                src.push_str("namespace Imports {\n");

                src.push_str(&format!(
                    "{access} partial class {name}WorldImportsInterop : I{name}WorldImports\n"
                ));
                src.push_str("{");

                for fragment in self
                    .world_fragments
                    .iter()
                    .filter(|f| f.direction == Some(Direction::Import))
                {
                    if !fragment.csharp_interop_src.is_empty() {
                        src.push_str("\n");

                        src.push_str(&fragment.csharp_interop_src);
                    }
                }
                src.push_str("}\n");
                src.push_str("}\n");
            }

            if self
                .world_fragments
                .iter()
                .any(|f| f.direction == Some(Direction::Export))
            {
                src.push_str("namespace Exports {\n");

                src.push_str(&format!("{access} static class {name}WorldInterop\n"));
                src.push_str("{");

                for fragment in self
                    .world_fragments
                    .iter()
                    .filter(|f| f.direction == Some(Direction::Export))
                {
                    src.push_str("\n");

                    src.push_str(&fragment.csharp_interop_src);
                }
                src.push_str("}\n");
                src.push_str("}\n");
            }
        }

        if !self.generated_future_types.is_empty() {
            src.push_str("\n");
            src.push_str(
                r#"public static class WitFuture
            {
                internal static (FutureReader, FutureWriter) FutureNew(FutureVTable table)
                {
                    return FutureHelpers.RawFutureNew(table);
                }

            "#,
            );

            src.push_str(
                r#"
            }
            "#,
            );
        }

        if self.needs_async_support {
            src.push_str("\n");
            src.push_str(include_str!("AsyncSupport.cs"));

            src.push_str("\n");
            src.push_str(&include_str!("FutureCommonSupport.cs"));
        }

        src.push('\n');

        src.push_str("}\n");

        files.push(&format!("{name}.cs"), indent(&src).as_bytes());

        let generate_stub = |name: String, files: &mut Files, stubs: Stubs| {
            let (stub_namespace, interface_or_class_name) =
                CSharp::get_class_name_from_qualified_name(&name);

            let stub_class_name = format!(
                "{}Impl",
                match interface_or_class_name.starts_with("I") {
                    true => interface_or_class_name
                        .strip_prefix("I")
                        .unwrap()
                        .to_string(),
                    false => interface_or_class_name.clone(),
                }
            );

            let stub_file_name = match stub_namespace.len() {
                0 => stub_class_name.clone(),
                _ => format!("{stub_namespace}.{stub_class_name}"),
            };

            let (fragments, fully_qualified_namespace) = match stubs {
                Stubs::World(fragments) => {
                    let fully_qualified_namespace = namespace.to_string();
                    (fragments, fully_qualified_namespace)
                }
                Stubs::Interface(fragments) => {
                    let fully_qualified_namespace = stub_namespace.clone();
                    (fragments, fully_qualified_namespace)
                }
            };

            if fragments.iter().all(|f| f.stub.is_empty()) {
                return;
            }

            let body = fragments
                .iter()
                .map(|f| f.stub.deref())
                .collect::<Vec<_>>()
                .join("\n");

            let body = format!(
                "{header}

                 namespace {fully_qualified_namespace};

                 {access} partial class {stub_class_name} : {interface_or_class_name} {{
                    {body}
                 }}
                "
            );

            files.push(&format!("{stub_file_name}.cs"), indent(&body).as_bytes());
        };

        if self.opts.generate_stub {
            generate_stub(
                format!("I{name}WorldExports"),
                files,
                Stubs::World(&self.world_fragments),
            );
        }

        if !self.opts.skip_support_files {
            //TODO: This is currently needed for mono even if it's built as a library.
            if self.opts.runtime == CSharpRuntime::Mono {
                files.push(
                    "MonoEntrypoint.cs",
                    indent(&format!(
                        r#"
                        {access} class MonoEntrypoint() {{
                            {access} static void Main() {{
                            }}
                        }}
                        "#
                    ))
                    .as_bytes(),
                );
            }

            {
                // When generating a WIT file, we first round-trip through the
                // binary encoding.  This has the effect of flattening any
                // `include`d worlds into the specified world and excluding
                // unrelated worlds, ensuring the output WIT contains no extra
                // information beyond what the binary representation contains.
                //
                // This is important because including more than one world in
                // the output would make it ambigious, and since this file is
                // intended to be used non-interactively at link time, the
                // linker will have no additional information to resolve such
                // ambiguity.
                let (resolve, world) = wit_parser::decoding::decode_world(
                    &wit_component::metadata::encode(resolve, id, self.opts.string_encoding, None)?,
                )?;
                let pkg = resolve.worlds[world].package.unwrap();

                let mut printer = WitPrinter::default();
                printer.emit_docs(false);
                printer.print(
                    &resolve,
                    pkg,
                    &resolve
                        .packages
                        .iter()
                        .filter_map(|(id, _)| if id == pkg { None } else { Some(id) })
                        .collect::<Vec<_>>(),
                )?;
                files.push(
                    &format!("{world_namespace}_component_type.wit"),
                    String::from(printer.output).as_bytes(),
                );
            }

            // TODO: remove when we switch to dotnet 9
            let mut wasm_import_linakge_src = String::new();

            uwrite!(
                wasm_import_linakge_src,
                r#"{header}
                #if !NET9_0_OR_GREATER
                // temporarily add this attribute until it is available in dotnet 9
                namespace System.Runtime.InteropServices
                {{
                    internal partial class WasmImportLinkageAttribute : global::System.Attribute {{}}
                }}
                #endif
                "#,
            );
            files.push(
                &format!("{world_namespace}_wasm_import_linkage_attribute.cs"),
                indent(&wasm_import_linakge_src).as_bytes(),
            );
        }

        for (full_name, interface_type_and_fragments) in &self.interface_fragments {
            let fragments = &interface_type_and_fragments.interface_fragments;

            let (namespace, interface_name) =
                &CSharp::get_class_name_from_qualified_name(full_name);

            // C#
            let body = fragments
                .iter()
                .map(|f| f.csharp_src.deref())
                .collect::<Vec<_>>()
                .join("\n");

            if !body.is_empty() {
                let body = format!(
                    "{header}

                    namespace {namespace};

                    {access} interface {interface_name} {{
                        {body}
                    }}
                    ",
                );

                files.push(&format!("{full_name}.cs"), indent(&body).as_bytes());
            }

            // C# Interop
            let body = fragments
                .iter()
                .map(|f| f.csharp_interop_src.deref())
                .collect::<Vec<_>>()
                .join("\n");

            let class_name = interface_name.strip_prefix("I").unwrap();
            let body = format!(
                "{header}

                using System;

                namespace {namespace}
                {{
                  {access} static class {class_name}Interop {{
                      {body}
                  }}
                }}
                ",
            );

            files.push(
                &format!("{namespace}.{class_name}Interop.cs"),
                indent(&body).as_bytes(),
            );

            if interface_type_and_fragments.is_export && self.opts.generate_stub {
                generate_stub(full_name.to_string(), files, Stubs::Interface(fragments));
            }
        }

        Ok(())
    }
}

enum Stubs<'a> {
    World(&'a Vec<InterfaceFragment>),
    Interface(&'a Vec<InterfaceFragment>),
}

fn export_types(r#gen: &mut InterfaceGenerator, types: &[(&str, TypeId)]) {
    for (ty_name, ty) in types {
        r#gen.define_type(ty_name, *ty);
    }
}

// We cant use "StructLayout.Pack" as dotnet will use the minimum of the type and the "Pack" field,
// so for byte it would always use 1 regardless of the "Pack".
pub fn dotnet_aligned_array(array_size: usize, required_alignment: usize) -> (usize, String) {
    let num_elements = array_size.div_ceil(required_alignment);
    match required_alignment {
        1 => (num_elements, "byte".to_owned()),
        2 => (num_elements, "ushort".to_owned()),
        4 => (num_elements, "uint".to_owned()),
        8 => (num_elements, "ulong".to_owned()),
        _ => todo!("unsupported return_area_align {}", required_alignment),
    }
}

pub fn wasm_type(ty: WasmType) -> &'static str {
    match ty {
        WasmType::I32 => "int",
        WasmType::I64 => "long",
        WasmType::F32 => "float",
        WasmType::F64 => "double",
        WasmType::Pointer => "nint",
        WasmType::PointerOrI64 => "long",
        WasmType::Length => "int",
    }
}

fn indent(code: &str) -> String {
    let mut indented = String::with_capacity(code.len());
    let mut indent = 0;
    let mut was_empty = false;
    for line in code.trim().lines() {
        let trimmed = line.trim();
        if trimmed.is_empty() {
            if was_empty {
                continue;
            }
            was_empty = true;
        } else {
            was_empty = false;
        }

        if trimmed.starts_with('}') {
            indent -= 1;
        }
        if !trimmed.is_empty() {
            indented.extend(iter::repeat(' ').take(indent * 4));
            indented.push_str(trimmed);
        }
        if trimmed.ends_with('{') {
            indent += 1;
        }
        indented.push('\n');
    }
    indented
}

fn interface_name(
    csharp: &mut CSharp,
    resolve: &Resolve,
    name: &WorldKey,
    direction: Direction,
) -> String {
    let pkg = match name {
        WorldKey::Name(_) => None,
        WorldKey::Interface(id) => {
            let pkg = resolve.interfaces[*id].package.unwrap();
            Some(resolve.packages[pkg].name.clone())
        }
    };

    let name = format!(
        "{}{}",
        match name {
            WorldKey::Name(name) => name.to_upper_camel_case(),
            WorldKey::Interface(id) => resolve.interfaces[*id]
                .name
                .as_ref()
                .unwrap()
                .to_upper_camel_case(),
        },
        match direction {
            Direction::Import => "Imports",
            Direction::Export => "Exports",
        }
    );

    let namespace = match &pkg {
        Some(name) => {
            let mut ns = format!(
                "{}.{}.",
                name.namespace.to_csharp_ident(),
                name.name.to_csharp_ident()
            );

            if let Some(version) = &name.version {
                // Only include the version segment when the same package name exists at
                // multiple versions in this Resolve; omit it when unambiguous.
                // Mirrors the equivalent guard in the C generator (crates/c/src/lib.rs).
                let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| {
                    p.name.namespace == name.namespace
                        && p.name.name == name.name
                        && p.name.version != name.version
                });
                if pkg_has_multiple_versions {
                    let v = version.to_string().replace(['.', '-', '+'], "_");
                    ns = format!("{}v{}.", ns, &v);
                }
            }
            ns
        }
        None => String::new(),
    };

    let world_namespace = &csharp.qualifier();

    format!(
        "{}wit.{}.{}I{name}",
        world_namespace,
        match direction {
            Direction::Import => "Imports",
            Direction::Export => "Exports",
        },
        namespace
    )
}

pub fn is_primitive(ty: &Type) -> bool {
    matches!(
        ty,
        Type::U8
            | Type::S8
            | Type::U16
            | Type::S16
            | Type::U32
            | Type::S32
            | Type::U64
            | Type::S64
            | Type::F32
            | Type::F64
    )
}

/// Group the specified functions by resource (or `None` for freestanding functions).
///
/// The returned map is constructed by iterating over `funcs`, then iterating over `all_resources`, thereby
/// ensuring that even resources with no associated functions will be represented in the result.
fn by_resource<'a>(
    funcs: impl Iterator<Item = (&'a str, &'a Function)>,
    all_resources: impl Iterator<Item = TypeId>,
) -> IndexMap<Option<TypeId>, Vec<&'a Function>> {
    let mut by_resource = IndexMap::<_, Vec<_>>::new();
    for (_, func) in funcs {
        by_resource
            .entry(func.kind.resource())
            .or_default()
            .push(func);
    }
    for id in all_resources {
        by_resource.entry(Some(id)).or_default();
    }
    by_resource
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Build a `Resolve` from inline WIT and return the generated C# namespace
    /// for every imported interface in `world`.
    fn imported_interface_namespaces(wit: &[(&str, &str)], world: &str) -> Vec<String> {
        let mut resolve = Resolve::default();
        for (path, contents) in wit {
            resolve.push_str(path, contents).unwrap();
        }
        let (world_id, _) = resolve
            .worlds
            .iter()
            .find(|(_, w)| w.name == world)
            .unwrap();
        let keys: Vec<WorldKey> = resolve.worlds[world_id]
            .imports
            .keys()
            .filter(|k| matches!(k, WorldKey::Interface(_)))
            .cloned()
            .collect();
        let mut csharp = CSharp::default();
        keys.iter()
            .map(|k| interface_name(&mut csharp, &resolve, k, Direction::Import))
            .collect()
    }

    #[test]
    fn version_segment_omitted_when_package_is_unambiguous() {
        let names = imported_interface_namespaces(
            &[
                (
                    "dep.wit",
                    "package my:dep@0.1.0;\ninterface a { x: func(); }",
                ),
                (
                    "root.wit",
                    "package foo:bar;\nworld the-world { import my:dep/a@0.1.0; }",
                ),
            ],
            "the-world",
        );
        assert!(!names.is_empty());
        for n in &names {
            assert!(n.contains("my.dep"), "expected package segment: {n}");
            assert!(
                !n.contains("v0_1_0"),
                "version segment should be omitted: {n}"
            );
        }
    }

    #[test]
    fn version_segment_kept_when_multiple_versions_coexist() {
        let names = imported_interface_namespaces(
            &[
                (
                    "v1.wit",
                    "package my:dep@0.1.0;\ninterface a { x: func(); }",
                ),
                (
                    "v2.wit",
                    "package my:dep@0.2.0;\ninterface a { x: func(); }",
                ),
                (
                    "root.wit",
                    "package foo:bar;\nworld the-world { import my:dep/a@0.1.0; import my:dep/a@0.2.0; }",
                ),
            ],
            "the-world",
        );
        assert!(
            names.iter().any(|n| n.contains("v0_1_0")),
            "expected a v0_1_0 segment: {names:?}"
        );
        assert!(
            names.iter().any(|n| n.contains("v0_2_0")),
            "expected a v0_2_0 segment: {names:?}"
        );
    }
}
