use crate::csharp_ident::ToCSharpIdent;
use crate::function::FunctionBindgen;
use crate::function::ResourceInfo;
use crate::world_generator::CSharp;
use heck::ToLowerCamelCase;
use heck::{ToShoutySnakeCase, ToUpperCamelCase};
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt::Write;
use std::mem;
use std::ops::Deref;
use wit_bindgen_core::abi::LiftLower;
use wit_bindgen_core::{
    Direction, InterfaceGenerator as CoreInterfaceGenerator, abi, uwrite, uwriteln,
};
use wit_parser::Param;
use wit_parser::abi::AbiVariant;
use wit_parser::{
    Docs, Enum, Flags, FlagsRepr, Function, FunctionKind, Handle, Int, InterfaceId, LiveTypes,
    Record, Resolve, Result_, Tuple, Type, TypeDefKind, TypeId, TypeOwner, Variant, WorldKey,
};

const MAX_FLAT_PARAMS: usize = 16;

pub(crate) struct InterfaceFragment {
    pub(crate) csharp_src: String,
    pub(crate) csharp_interop_src: String,
    pub(crate) stub: String,
    pub(crate) direction: Option<Direction>, // Types do not have a direction.
}

pub(crate) struct InterfaceTypeAndFragments {
    pub(crate) is_export: bool,
    pub(crate) interface_fragments: Vec<InterfaceFragment>,
}

impl InterfaceTypeAndFragments {
    pub(crate) fn new(is_export: bool) -> Self {
        InterfaceTypeAndFragments {
            is_export,
            interface_fragments: Vec::<InterfaceFragment>::new(),
        }
    }
}

#[derive(Clone)]
pub(crate) struct FutureInfo {
    pub name: String,
    pub generic_type_name: String,
    pub qualified_generic_type_name: String,
    pub ty: Option<Type>,
}

/// InterfaceGenerator generates the C# code for wit interfaces.
/// It produces types by interface in wit and then generates the interop code
/// by calling out to FunctionGenerator
pub(crate) struct InterfaceGenerator<'a> {
    pub(crate) src: String,
    pub(crate) csharp_interop_src: String,
    pub(crate) stub: String,
    pub(crate) csharp_gen: &'a mut CSharp,
    pub(crate) resolve: &'a Resolve,
    pub(crate) name: &'a str,
    pub(crate) direction: Direction,
    pub(crate) futures: Vec<FutureInfo>,
    pub(crate) streams: Vec<FutureInfo>,
    pub(crate) is_world: bool,
}

impl InterfaceGenerator<'_> {
    pub fn is_async(kind: &FunctionKind) -> bool {
        matches!(
            kind,
            FunctionKind::AsyncFreestanding
                | FunctionKind::AsyncStatic(_)
                | FunctionKind::AsyncMethod(_)
        )
    }

    pub(crate) fn define_interface_types(&mut self, id: InterfaceId) {
        let mut live = LiveTypes::default();
        live.add_interface(self.resolve, id);
        self.define_live_types(live, id);
    }

    //TODO: we probably need this for anonymous types outside of an interface...
    // fn define_function_types(&mut self, funcs: &[(&str, &Function)]) {
    //     let mut live = LiveTypes::default();
    //     for (_, func) in funcs {
    //         live.add_func(self.resolve, func);
    //     }
    //     self.define_live_types(live);
    // }

    fn define_live_types(&mut self, live: LiveTypes, id: InterfaceId) {
        let mut type_names = HashMap::new();

        for ty in live.iter() {
            // just create c# types for wit anonymous types
            let type_def = &self.resolve.types[ty];
            if type_names.contains_key(&ty) || type_def.name.is_some() {
                continue;
            }

            let typedef_name = self.type_name(&Type::Id(ty));

            let prev = type_names.insert(ty, typedef_name.clone());
            assert!(prev.is_none());

            // workaround for owner not set on anonymous types, maintain or own map to the owner
            self.csharp_gen
                .anonymous_type_owners
                .insert(ty, TypeOwner::Interface(id));

            self.define_anonymous_type(ty, &typedef_name)
        }
    }

    fn define_anonymous_type(&mut self, type_id: TypeId, typedef_name: &str) {
        let type_def = &self.resolve().types[type_id];
        let kind = &type_def.kind;

        // TODO Does c# need this exit?
        // // skip `typedef handle_x handle_y` where `handle_x` is the same as `handle_y`
        // if let TypeDefKind::Handle(handle) = kind {
        //     let resource = match handle {
        //         Handle::Borrow(id) | Handle::Own(id) => id,
        //     };
        //     let origin = dealias(self.resolve, *resource);
        //     if origin == *resource {
        //         return;
        //     }
        // }

        //TODO: what other TypeDefKind do we need here?
        match kind {
            TypeDefKind::Tuple(t) => self.type_tuple(type_id, typedef_name, t, &type_def.docs),
            TypeDefKind::Option(t) => self.type_option(type_id, typedef_name, t, &type_def.docs),
            TypeDefKind::Record(t) => self.type_record(type_id, typedef_name, t, &type_def.docs),
            TypeDefKind::List(t) => self.type_list(type_id, typedef_name, t, &type_def.docs),
            TypeDefKind::Map(key, value) => {
                self.type_map(type_id, typedef_name, key, value, &type_def.docs)
            }
            TypeDefKind::Variant(t) => self.type_variant(type_id, typedef_name, t, &type_def.docs),
            TypeDefKind::Result(t) => self.type_result(type_id, typedef_name, t, &type_def.docs),
            TypeDefKind::Handle(_) => {
                // Handles don't require a separate definition beyond what we already define for the corresponding
                // resource types.
            }
            TypeDefKind::Future(t) => self.type_future(type_id, typedef_name, t, &type_def.docs),
            TypeDefKind::Stream(t) => self.type_stream(type_id, typedef_name, t, &type_def.docs),
            _ => unreachable!(),
        }
    }

    pub(crate) fn qualifier(&self, when: bool, ty: &TypeId) -> String {
        let type_def = &self.resolve.types[*ty];

        // anonymous types dont get an owner from wit-parser, so assume they are part of an interface here.
        let owner = if let Some(owner_type) = self.csharp_gen.anonymous_type_owners.get(ty) {
            *owner_type
        } else {
            type_def.owner
        };

        let global_prefix = self.global_if_user_type(&Type::Id(*ty));

        if let TypeOwner::Interface(id) = owner {
            if let Some(name) = self.csharp_gen.interface_names.get(&id) {
                if name != self.name {
                    return format!("{global_prefix}{name}.");
                }
            }
        }

        if when {
            let mut name = self.name;
            let tmp_name: String;
            if self.is_world {
                if CSharp::type_is_bidirectional(self.resolve(), ty) {
                    name = name.rsplitn(2, ".").nth(1).unwrap();
                }
                // World level types, except resources are always imports.
                else if name.ends_with("Exports") && type_def.kind != TypeDefKind::Resource {
                    tmp_name = Self::replace_last(&name, "Exports", "Imports");
                    name = &tmp_name;
                }
            }
            format!("{global_prefix}{name}.")
        } else {
            String::new()
        }
    }

    fn replace_last(haystack: &str, needle: &str, replacement: &str) -> String {
        if let Some(pos) = haystack.rfind(needle) {
            let mut result =
                String::with_capacity(haystack.len() - needle.len() + replacement.len());
            result.push_str(&haystack[..pos]);
            result.push_str(replacement);
            result.push_str(&haystack[pos + needle.len()..]);
            result
        } else {
            haystack.to_string()
        }
    }

    pub(crate) fn add_interface_fragment(self, is_export: bool) {
        self.csharp_gen
            .interface_fragments
            .entry(self.name.to_string())
            .or_insert_with(|| InterfaceTypeAndFragments::new(is_export))
            .interface_fragments
            .push(InterfaceFragment {
                csharp_src: self.src,
                csharp_interop_src: self.csharp_interop_src,
                stub: self.stub,
                direction: Some(self.direction),
            });
    }

    pub(crate) fn add_futures_or_streams(
        &mut self,
        import_module_name: &str,
        is_export: bool,
        is_future: bool,
    ) {
        // Anything for this future/stream list?
        if (is_future && self.futures.is_empty()) || (!is_future && self.streams.is_empty()) {
            return;
        }

        let future_stream_name = if is_future { "Future" } else { "Stream" };
        let future_stream_name_lower = future_stream_name.to_lowercase();
        let mut bool_non_generic_new_added = false;
        let mut bool_generic_new_added = false;
        let mut generated_future_types: HashSet<Option<Type>> = HashSet::new();
        let (_namespace, interface_name) = &CSharp::get_class_name_from_qualified_name(self.name);
        let interop_name = format!("{}Interop", interface_name.strip_prefix("I").unwrap());
        // avoid the immutable self borrow
        let (futures_or_streams, stream_length_param): (Vec<FutureInfo>, &str) = if is_future {
            (self.futures.iter().cloned().collect(), "")
        } else {
            (self.streams.iter().cloned().collect(), ", uint length")
        };

        let mut index = 0;
        let mut size = 0;
        let mut align = 0;
        for future in futures_or_streams {
            // This code originally copied from Rust codegen generate_payload.
            // See the rust codegen for the comment - essentially we canonicalize to one per type.
            let canonical_payload = match future.ty {
                Some(Type::Id(id)) => {
                    let id = self.csharp_gen.types.get_representative_type(id);

                    match self.resolve.types[id].kind {
                        TypeDefKind::Type(t) => Some(t),
                        _ => Some(Type::Id(id)),
                    }
                }
                other => other,
            };
            {
                if generated_future_types.contains(&canonical_payload) {
                    continue;
                }
            }
            let mut lift_func = "null".to_string();
            let mut lower_func = "null".to_string();
            let future_name = &future.name;
            let generic_type_name = &future.generic_type_name;
            let upper_camel_future_type = generic_type_name.to_upper_camel_case();

            if let Some(payload) = canonical_payload {
                //TODO: wasm64
                size = self.csharp_gen.sizes.size(&payload).size_wasm32();
                align = self.csharp_gen.sizes.align(&payload).align_wasm32();

                if needs_ptr(&payload) {
                    lift_func = format!("{future_stream_name}Lift{upper_camel_future_type}");
                    lower_func = format!("{future_stream_name}Lower{upper_camel_future_type}");
                }
            }

            uwrite!(
                self.csharp_interop_src,
                r#"
                internal static {future_stream_name}VTable {future_stream_name}VTable{upper_camel_future_type} = new {future_stream_name}VTable()
                {{
                    New = {future_stream_name}New{upper_camel_future_type},
                    Read = {future_stream_name}Read{upper_camel_future_type},
                    Write = {future_stream_name}Write{upper_camel_future_type},
                    DropReader = {future_stream_name}DropReader{upper_camel_future_type},
                    DropWriter = {future_stream_name}DropWriter{upper_camel_future_type},
                    CancelReadDelegate = {future_stream_name}CancelRead{upper_camel_future_type},   
                    CancelWriteDelegate = {future_stream_name}CancelWrite{upper_camel_future_type},
                    Lift = {lift_func},
                    Lower = {lower_func},
                    Size = {size},
                    Align = {align},
                }};
                "#
            );

            uwrite!(
                self.csharp_interop_src,
                r#"
                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[async-lower][{future_stream_name_lower}-read-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                internal static unsafe extern uint {future_stream_name}Read{upper_camel_future_type}(int readable, IntPtr ptr{stream_length_param});

                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[async-lower][{future_stream_name_lower}-write-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                internal static unsafe extern uint {future_stream_name}Write{upper_camel_future_type}(int writeable, IntPtr buffer{stream_length_param});
        
                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-drop-readable-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                internal static extern void {future_stream_name}DropReader{upper_camel_future_type}(int readable);

                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-drop-writable-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                internal static extern void {future_stream_name}DropWriter{upper_camel_future_type}(int readable);
            "#
            );

            uwrite!(
                self.csharp_interop_src,
                r#"
                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-new-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                internal static extern ulong {future_stream_name}New{upper_camel_future_type}();
                "#
            );

            // TODO: Move this and other type dependent functions out to another function.
            uwrite!(
                self.csharp_interop_src,
                r#"
                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-cancel-read-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                internal static extern uint {future_stream_name}CancelRead{upper_camel_future_type}(int readable);
                "#
            );

            uwrite!(
                self.csharp_interop_src,
                r#"
                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-cancel-write-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                internal static extern uint {future_stream_name}CancelWrite{upper_camel_future_type}(int writeable);
                "#
            );

            uwrite!(
                self.csharp_interop_src,
                r#"
                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "[{future_stream_name_lower}-drop-writeable-{index}]{future_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                internal static extern void {future_stream_name}DropWriteable{upper_camel_future_type}(int writeable);
                "#
            );

            match future.generic_type_name.as_str() {
                "" => {
                    if !bool_non_generic_new_added {
                        self.csharp_gen
                            .interface_fragments
                            .entry(self.name.to_string())
                            .or_insert_with(|| InterfaceTypeAndFragments::new(is_export))
                            .interface_fragments
                            .push(InterfaceFragment {
                                csharp_src: format!(r#"
                                internal static ({future_stream_name}Reader, {future_stream_name}Writer) {future_stream_name}New() 
                                {{
                                        return FutureHelpers.Raw{future_stream_name}New({interop_name}.{future_stream_name}VTable);
                                }}
                                "#).to_string(),
                                csharp_interop_src: "".to_string(),
                                stub: "".to_string(),
                                direction: Some(self.direction),
                            });
                        bool_non_generic_new_added = true;
                    }
                }
                _ => {
                    self.csharp_gen
                        .interface_fragments
                        .entry(self.name.to_string())
                        .or_insert_with(|| InterfaceTypeAndFragments::new(is_export))
                        .interface_fragments
                        .push(InterfaceFragment {
                            csharp_src: format!(r#"
                            public static ({future_stream_name}Reader<{generic_type_name}>, {future_stream_name}Writer<{generic_type_name}>) {future_stream_name}New{upper_camel_future_type}() 
                            {{
                                    return FutureHelpers.Raw{future_stream_name}New<{generic_type_name}>({interop_name}.{future_stream_name}VTable{upper_camel_future_type});
                            }}
                            "#).to_string(),
                            csharp_interop_src: "".to_string(),
                            stub: "".to_string(),
                            direction: Some(self.direction),
                        });

                    if lift_func != "null"
                        && let Some(payload_type) = canonical_payload
                    {
                        let (lift, result) = self.lift_from_memory("buffer", &payload_type);

                        uwrite!(
                            self.csharp_interop_src,
                            r#"
                            public static unsafe Array {future_stream_name}Lift{upper_camel_future_type}(IntPtr buffer, Array resultBuffer) 
                            {{
                                {lift}

                                // TODO: length > 1
                                resultBuffer.SetValue({result}, 0);
                                return resultBuffer;
                            }}
                            "#
                        );
                    }

                    if lower_func != "null"
                        && let Some(payload_type) = canonical_payload
                    {
                        let lower_code = self.lower_to_memory("ptr", "typedToLower", &payload_type);
                        let size_align = self.csharp_gen.sizes.params(Some(&payload_type));
                        // TODO: Wasm64
                        self.csharp_gen.return_area_size = self
                            .csharp_gen
                            .return_area_size
                            .max(size_align.size.size_wasm32());
                        self.csharp_gen.return_area_align = self
                            .csharp_gen
                            .return_area_align
                            .max(size_align.align.align_wasm32());
                        self.csharp_gen.needs_export_return_area = true;
                        let qualified_generic_type_name = &future.qualified_generic_type_name;
                        uwrite!(
                            self.csharp_interop_src,
                            r#"
                            public static unsafe void {future_stream_name}Lower{upper_camel_future_type}(object toLower, List<Action> cleanups) 
                            {{
                                var ptr = InteropReturnArea.returnArea.AddressOfReturnArea();
                                var typedToLower = ({qualified_generic_type_name})toLower;
                                {lower_code}
                            }}
                            "#
                        );
                    }

                    if !bool_generic_new_added {
                        self.csharp_gen
                            .interface_fragments
                            .entry(self.name.to_string())
                            .or_insert_with(|| InterfaceTypeAndFragments::new(is_export))
                            .interface_fragments
                            .push(InterfaceFragment {
                                csharp_src: format!(r#"
                                public static ({future_stream_name}Reader<T>, {future_stream_name}Writer<T>) {future_stream_name}New<T>({future_stream_name}VTable vtable) 
                                {{
                                        return FutureHelpers.Raw{future_stream_name}New<T>(vtable);
                                }}
                                "#).to_string(),
                                csharp_interop_src: "".to_string(),
                                stub: "".to_string(),
                                direction: Some(self.direction),
                            });
                        bool_generic_new_added = true;
                    }
                }
            }

            generated_future_types.insert(canonical_payload);
            index = index + 1;
        }

        self.csharp_gen.needs_async_support = true;

        self.csharp_gen
            .generated_future_types
            .extend(generated_future_types.iter());
    }

    pub(crate) fn add_world_fragment(self, direction: Option<Direction>) {
        self.csharp_gen.world_fragments.push(InterfaceFragment {
            csharp_src: self.src,
            csharp_interop_src: self.csharp_interop_src,
            stub: self.stub,
            direction,
        });
    }

    pub(crate) fn import(&mut self, import_module_name: &str, func: &Function) {
        let camel_name = match &func.kind {
            FunctionKind::Freestanding
            | FunctionKind::Static(_)
            | FunctionKind::AsyncFreestanding
            | FunctionKind::AsyncStatic(_) => func.item_name().to_upper_camel_case(),
            FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => {
                func.item_name().to_upper_camel_case()
            }
            FunctionKind::Constructor(id) => {
                self.csharp_gen.all_resources[id].name.to_upper_camel_case()
            }
        };

        let access = self.csharp_gen.access_modifier();

        let modifiers = modifiers(func, &camel_name, Direction::Import);

        let interop_camel_name = func.item_name().to_upper_camel_case();

        let sig = self.resolve.wasm_signature(AbiVariant::GuestImport, func);

        let is_async = matches!(
            func.kind,
            FunctionKind::AsyncFreestanding
                | FunctionKind::AsyncStatic(_)
                | FunctionKind::AsyncMethod(_)
        );

        let mut wasm_result_type = match &sig.results[..] {
            [] => {
                if is_async {
                    "uint"
                } else {
                    "void"
                }
            }
            [result] => crate::world_generator::wasm_type(*result),
            _ => unreachable!(),
        };

        let (result_type, results) = self.func_payload_and_return_type(func);

        let is_async = InterfaceGenerator::is_async(&func.kind);

        let requires_async_return_buffer_param = is_async && !sig.results.is_empty();
        let sig_unsafe = if requires_async_return_buffer_param {
            "unsafe "
        } else {
            ""
        };

        let wasm_params: String = {
            let param_list = sig
                .params
                .iter()
                .enumerate()
                .map(|(i, param)| {
                    let ty = crate::world_generator::wasm_type(*param);
                    format!("{ty} p{i}")
                })
                .collect::<Vec<_>>()
                .join(", ");

            if requires_async_return_buffer_param {
                if param_list.is_empty() {
                    "nint taskResultBuffer".to_string()
                } else {
                    format!("{param_list}, nint taskResultBuffer")
                }
            } else {
                param_list
            }
        };

        let mut funcs: Vec<(String, String)> = Vec::new();
        funcs.push(self.gen_import_src(func, &results, ParameterType::ABI));

        let include_additional_functions = func
            .params
            .iter()
            .skip(if let FunctionKind::Method(_) = &func.kind {
                1
            } else {
                0
            })
            .any(|param| self.is_primative_list(&param.ty));

        if include_additional_functions {
            funcs.push(self.gen_import_src(func, &results, ParameterType::Span));
            funcs.push(self.gen_import_src(func, &results, ParameterType::Memory));
        }

        let import_name = if is_async {
            wasm_result_type = "uint";
            format!("[async-lower]{}", func.name)
        } else {
            func.name.to_string()
        };

        uwrite!(
            self.csharp_interop_src,
            r#"
            public static class {interop_camel_name}WasmInterop
            {{
                [global::System.Runtime.InteropServices.DllImportAttribute("{import_module_name}", EntryPoint = "{import_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                public static extern {sig_unsafe}{wasm_result_type} wasmImport{interop_camel_name}({wasm_params});
            }}
            "#
        );

        for (src, params) in funcs {
            uwrite!(
                self.src,
                r#"
                    {access} {modifiers} unsafe {result_type} {camel_name}({params})
                    {{
                        {src}
                    }}
                "#
            );
        }
    }

    fn func_payload_and_return_type(&mut self, func: &Function) -> (String, Vec<TypeId>) {
        let return_type = self.func_return_type(func, true);
        let results = if let FunctionKind::Constructor(_) = &func.kind {
            Vec::new()
        } else {
            let payload = match func.result {
                None => Vec::new(),
                Some(ty) => {
                    let (_payload, results) = payload_and_results(
                        self.resolve,
                        ty,
                        self.csharp_gen.opts.with_wit_results,
                    );
                    results
                }
            };

            payload
        };

        (return_type, results)
    }

    fn func_return_type(&mut self, func: &Function, qualifier: bool) -> String {
        let result_type = if let FunctionKind::Constructor(_) = &func.kind {
            String::new()
        } else {
            let base_type = match func.result {
                None => "void".to_string(),
                Some(_ty) => {
                    let result_type = match func.result {
                        None => "void".to_string(),
                        Some(ty) => {
                            let (payload, _results) = payload_and_results(
                                self.resolve,
                                ty,
                                self.csharp_gen.opts.with_wit_results,
                            );
                            if let Some(ty) = payload {
                                self.csharp_gen.needs_result = true;
                                self.type_name_with_qualifier(&ty, qualifier)
                            } else {
                                "void".to_string()
                            }
                        }
                    };

                    result_type
                }
            };

            let asyncified_type = match &func.kind {
                FunctionKind::AsyncFreestanding
                | FunctionKind::AsyncStatic(_)
                | FunctionKind::AsyncMethod(_) => match func.result {
                    None => "Task".to_string(),
                    Some(_ty) => format!("Task<{base_type}>"),
                },
                _ => base_type,
            };

            asyncified_type
        };

        result_type
    }

    fn gen_import_src(
        &mut self,
        func: &Function,
        results: &[TypeId],
        parameter_type: ParameterType,
    ) -> (String, String) {
        let mut bindgen = FunctionBindgen::new(
            self,
            func.item_name(),
            &func.kind,
            func.params
                .iter()
                .enumerate()
                .map(|(i, Param { name, .. })| {
                    if i == 0 && matches!(&func.kind, FunctionKind::Method(_)) {
                        "this".to_owned()
                    } else {
                        name.to_csharp_ident()
                    }
                })
                .collect(),
            results.to_vec(),
            parameter_type,
            func.result,
        );

        let async_ = InterfaceGenerator::is_async(&func.kind);
        let mut src: String;

        if async_ {
            // TODO: Use lower_to_memory as per Go ?
            let sig = bindgen
                .interface_gen
                .resolve
                .wasm_signature(AbiVariant::GuestImportAsync, func);

            // TODO: the result is a tmp so could be result/1/2/..  Maybe this will fallout if we use lower_to_memory.
            let async_status_var = "result"; // String::new();
            let requires_async_return_buffer_param = func.result.is_some();
            let async_return_buffer = if requires_async_return_buffer_param {
                let buffer = bindgen.emit_allocation_for_type(&sig.results);
                uwriteln!(
                    bindgen.src,
                    "//TODO: store somewhere with the TaskCompletionSource, possibly in the state, using Task.AsyncState to retrieve it later."
                );
                Some(buffer)
            } else {
                None
            };

            let csharp_param_names = func
                .params
                .iter()
                .map(|param| param.name.to_lower_camel_case())
                .collect::<Vec<_>>();

            let (lower, wasm_params) = if sig.indirect_params {
                todo!("indirect params not supported for async imports yet");
            } else {
                let wasm_params: Vec<String> = csharp_param_names
                    .iter()
                    .zip(&func.params)
                    .flat_map(|(name, param)| {
                        abi::lower_flat(
                            bindgen.interface_gen.resolve,
                            &mut bindgen,
                            name.clone(),
                            &param.ty,
                        )
                    })
                    .collect();
                (mem::take(&mut bindgen.src), wasm_params)
            };
            let name = func.name.to_upper_camel_case();
            let raw_name = format!("IImportsInterop.{name}WasmInterop.wasmImport{name}");

            let mut wasm_param_refs: Vec<&str> = wasm_params.iter().map(|v| v.as_str()).collect();

            let async_return_buffer_prefixed;

            if func.result.is_some() {
                // Build a new owned string with the prefix
                async_return_buffer_prefixed =
                    Some(format!("(nint){}", async_return_buffer.as_ref().unwrap()));
                // Borrow from that owned string
                wasm_param_refs.push(async_return_buffer_prefixed.as_ref().unwrap().as_str());
            }

            let wasm_params = wasm_param_refs.join(", ");

            // TODO: lift expr
            let code = format!(
                "{lower}
var {async_status_var} = {raw_name}({wasm_params});
"
            );

            src = format!("{code}");

            let bindgen_src = bindgen.src;
            if let Some(buffer) = async_return_buffer {
                let ty = bindgen.result_type.expect("expected a result type");
                let (lift_expr, res) = self.lift_from_memory("address", &ty);
                uwriteln!(src, "{}", bindgen_src);
                let return_type = self.type_name_with_qualifier(&ty, true);

                let lift_func = format!(
                    "() => {{
                        {lift_expr}

                        return {res};
                    }}"
                );
                uwriteln!(
                    src,
                    "
                    var task = AsyncSupport.TaskFromStatus<{return_type}>({async_status_var}, {});",
                    lift_func
                );
                uwriteln!(
                    src,
                    "global::System.Runtime.InteropServices.NativeMemory.Free({});",
                    buffer
                );
                uwriteln!(src, "return task;");
            } else {
                uwriteln!(
                    src,
                    "
                    return AsyncSupport.TaskFromStatus({async_status_var});"
                );
            }
        } else {
            abi::call(
                bindgen.interface_gen.resolve,
                AbiVariant::GuestImport,
                LiftLower::LowerArgsLiftResults,
                func,
                &mut bindgen,
                false,
            );
            src = bindgen.src;
        }

        let params = func
            .params
            .iter()
            .skip(if let FunctionKind::Method(_) = &func.kind {
                1
            } else {
                0
            })
            .map(|param| {
                let ty = self.name_with_qualifier(&param.ty, true, parameter_type);
                let param_name = &param.name;
                let param_name = param_name.to_csharp_ident();
                format!("{ty} {param_name}")
            })
            .collect::<Vec<_>>()
            .join(", ");

        (src, params)
    }

    pub(crate) fn export(&mut self, func: &Function, interface_key: Option<&WorldKey>) {
        let camel_name = match &func.kind {
            FunctionKind::Freestanding
            | FunctionKind::Static(_)
            | FunctionKind::AsyncFreestanding
            | FunctionKind::AsyncStatic(_) => func.item_name().to_upper_camel_case(),
            FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => {
                func.item_name().to_upper_camel_case()
            }
            FunctionKind::Constructor(id) => {
                self.csharp_gen.all_resources[id].name.to_upper_camel_case()
            }
        };

        let modifiers = modifiers(func, &camel_name, Direction::Export);

        let sig = self.resolve.wasm_signature(AbiVariant::GuestExport, func);

        let (result_type, results) = self.func_payload_and_return_type(func);

        let mut bindgen = FunctionBindgen::new(
            self,
            func.item_name(),
            &func.kind,
            (0..sig.params.len()).map(|i| format!("p{i}")).collect(),
            results,
            ParameterType::ABI,
            func.result,
        );

        let async_ = matches!(
            func.kind,
            FunctionKind::AsyncFreestanding
                | FunctionKind::AsyncStatic(_)
                | FunctionKind::AsyncMethod(_)
        );

        abi::call(
            bindgen.interface_gen.resolve,
            AbiVariant::GuestExport,
            LiftLower::LiftArgsLowerResults,
            func,
            &mut bindgen,
            async_,
        );

        let src = bindgen.src;
        let resource_type_name = bindgen.resource_type_name;

        let vars = bindgen
            .resource_drops
            .iter()
            .map(|(t, v)| format!("{t}? {v} = null;"))
            .collect::<Vec<_>>()
            .join(";\n");

        let wasm_result_type = if async_ {
            "int"
        } else {
            match &sig.results[..] {
                [] => "void",
                [result] => crate::world_generator::wasm_type(*result),
                _ => unreachable!(),
            }
        };

        let wasm_params = sig
            .params
            .iter()
            .enumerate()
            .map(|(i, param)| {
                let ty = crate::world_generator::wasm_type(*param);
                format!("{ty} p{i}")
            })
            .collect::<Vec<_>>()
            .join(", ");

        let (_namespace, _interface_name) = &CSharp::get_class_name_from_qualified_name(self.name);

        let params = func
            .params
            .iter()
            .skip(if let FunctionKind::Method(_) = &func.kind {
                1
            } else {
                0
            })
            .map(|Param { name, ty, .. }| {
                let ty = self.type_name_with_qualifier(ty, true);
                let name = name.to_csharp_ident();
                format!("{ty} {name}")
            })
            .collect::<Vec<String>>()
            .join(", ");

        let wasm_func_name = func.name.clone();
        let interop_name = format!("wasmExport{}", wasm_func_name.to_upper_camel_case());
        let core_module_name = interface_key.map(|s| self.resolve.name_world_key(s));
        let export_name = func.legacy_core_export_name(core_module_name.as_deref());

        let export_name = if async_ {
            format!("[async-lift]{export_name}")
        } else {
            export_name.to_string()
        };

        let access = self.csharp_gen.access_modifier();

        uwrite!(
            self.csharp_interop_src,
            r#"
            [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "{export_name}")]
            {access} static unsafe {wasm_result_type} {interop_name}({wasm_params}) {{
                {vars}
                {src}
            }}
            "#
        );

        if async_ {
            uwriteln!(
                self.csharp_interop_src,
                r#"
            [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "[callback]{export_name}")]
            public static unsafe uint {camel_name}Callback(int eventRaw, uint waitable, uint code)
            {{
                EventWaitable e = new EventWaitable((EventCode)eventRaw, waitable, code);

            "#
            );

            uwriteln!(
                self.csharp_interop_src,
                r#"
                    return (uint)AsyncSupport.Callback(e);
                }}
                "#
            );
        }

        if abi::guest_export_needs_post_return(self.resolve, func) {
            let params = sig
                .results
                .iter()
                .enumerate()
                .map(|(i, param)| {
                    let ty = crate::world_generator::wasm_type(*param);
                    format!("{ty} p{i}")
                })
                .collect::<Vec<_>>()
                .join(", ");

            let mut bindgen = FunctionBindgen::new(
                self,
                "INVALID",
                &func.kind,
                (0..sig.results.len()).map(|i| format!("p{i}")).collect(),
                Vec::new(),
                ParameterType::ABI,
                func.result,
            );

            abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen);

            let src = bindgen.src;

            uwrite!(
                self.csharp_interop_src,
                r#"
                [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "cabi_post_{export_name}")]
                {access} static unsafe void cabi_post_{interop_name}({params}) {{
                    {src}
                }}
                "#
            );
        }

        if async_ {
            let (_namespace, interface_name) =
                &CSharp::get_class_name_from_qualified_name(self.name);
            let impl_name = if resource_type_name.is_some() {
                resource_type_name.unwrap()
            } else {
                format!("{}Impl", interface_name.strip_prefix("I").unwrap())
            };

            let (_import_module_prefix, import_module) = match interface_key {
                Some(world_key) => {
                    let interface_world = self.resolve.name_world_key(world_key);
                    (format!("{interface_world}#"), interface_world)
                }
                None => (String::new(), "$root".to_string()),
            };

            let mut interop_class_name = impl_name.replace("Impl", "Interop");
            if self.is_world {
                interop_class_name = interop_class_name.replace("ExportsInterop", "Interop");
                interop_class_name = format!("Exports.{interop_class_name}");
            }

            let resolve = &self.resolve;
            let (task_return_param_sig_vec, task_return_param_vec): (Vec<String>, Vec<String>) =
                func.result
                    .map(|ty| {
                        let mut storage = vec![abi::WasmType::I32; MAX_FLAT_PARAMS];
                        let mut flat = abi::FlatTypes::new(&mut storage);
                        if resolve.push_flat(&ty, &mut flat) {
                            flat.to_vec()
                        } else {
                            vec![abi::WasmType::I32]
                        }
                    })
                    .unwrap_or_default()
                    .into_iter()
                    .enumerate()
                    .map(|(i, ty)| {
                        let ty = crate::world_generator::wasm_type(ty);
                        (format!("{ty} arg{i}"), format!("arg{i}"))
                    })
                    .unzip();

            let task_return_param_sig = task_return_param_sig_vec.join(", ");
            let task_return_param = task_return_param_vec.join(", ");

            let resource_type_name = match func.kind {
                FunctionKind::Method(resource_type_id)
                | FunctionKind::AsyncMethod(resource_type_id)
                | FunctionKind::Static(resource_type_id)
                | FunctionKind::Constructor(resource_type_id) => {
                    format!(
                        "Method{}",
                        self.csharp_gen.all_resources[&resource_type_id]
                            .name
                            .to_upper_camel_case()
                    )
                }
                _ => String::new(),
            };

            uwriteln!(
                self.src,
                r#"
            public static void {camel_name}TaskReturn({task_return_param_sig})
            {{
                {interop_class_name}.{resource_type_name}{camel_name}TaskReturn({task_return_param});
            }}
            "#
            );

            let resource_type_name = match func.kind {
                FunctionKind::Method(resource_type_id)
                | FunctionKind::AsyncMethod(resource_type_id)
                | FunctionKind::Static(resource_type_id)
                | FunctionKind::Constructor(resource_type_id) => {
                    format!(
                        "Method{}",
                        self.csharp_gen.all_resources[&resource_type_id]
                            .name
                            .to_upper_camel_case()
                    )
                }
                _ => String::new(),
            };
            let task_return_name = format!("{resource_type_name}{camel_name}");

            uwriteln!(
                self.csharp_interop_src,
                r#"
            // TODO: The task return function can take up to 16 core parameters.
            [global::System.Runtime.InteropServices.DllImportAttribute("[export]{import_module}", EntryPoint = "[task-return]{wasm_func_name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
            public static extern void {task_return_name}TaskReturn({task_return_param_sig});
            "#
            );
        }

        if !matches!(&func.kind, FunctionKind::Constructor(_)) {
            uwrite!(
                self.src,
                r#"{modifiers} {result_type} {camel_name}({params});

            "#
            );
        }

        if self.csharp_gen.opts.generate_stub {
            let sig: String = self.sig_string(func, true);
            let func_name = func.item_name().to_upper_camel_case();

            uwrite!(
                self.stub,
                r#"
                {sig} 
                {{
                    throw new global::System.NotImplementedException();
                }}

                public static int {func_name}Callback(/* TODO: event arg */)
                {{
                    throw new global::System.NotImplementedException();
                }}
                "#
            );
        }
    }

    fn type_name(&mut self, ty: &Type) -> String {
        self.type_name_with_qualifier(ty, false)
    }

    // We use a global:: prefix to avoid conflicts with namespace clashes on partial namespace matches
    fn global_if_user_type(&self, ty: &Type) -> String {
        match ty {
            Type::Id(id) => {
                let ty = &self.resolve.types[*id];
                match &ty.kind {
                    TypeDefKind::Option(_ty) => "".to_owned(),
                    TypeDefKind::Result(_result) => "".to_owned(),
                    TypeDefKind::List(_list) => "".to_owned(),
                    TypeDefKind::Tuple(_tuple) => "".to_owned(),
                    TypeDefKind::Type(inner_type) => self.global_if_user_type(inner_type),
                    _ => "global::".to_owned(),
                }
            }
            _ => "".to_owned(),
        }
    }

    pub(crate) fn type_name_with_qualifier(&mut self, ty: &Type, qualifier: bool) -> String {
        self.name_with_qualifier(ty, qualifier, ParameterType::ABI)
    }

    fn is_primative_list(&mut self, ty: &Type) -> bool {
        match ty {
            Type::Id(id) => {
                let ty = &self.resolve.types[*id];
                match &ty.kind {
                    TypeDefKind::Type(ty) => self.is_primative_list(ty),
                    TypeDefKind::List(ty) if crate::world_generator::is_primitive(ty) => {
                        return true;
                    }
                    _ => false,
                }
            }
            _ => false,
        }
    }

    pub(crate) fn name_with_qualifier(
        &mut self,
        ty: &Type,
        qualifier: bool,
        parameter_type: ParameterType,
    ) -> String {
        match ty {
            Type::Bool => "bool".to_owned(),
            Type::U8 => "byte".to_owned(),
            Type::U16 => "ushort".to_owned(),
            Type::U32 => "uint".to_owned(),
            Type::U64 => "ulong".to_owned(),
            Type::S8 => "sbyte".to_owned(),
            Type::S16 => "short".to_owned(),
            Type::S32 => "int".to_owned(),
            Type::S64 => "long".to_owned(),
            Type::F32 => "float".to_owned(),
            Type::F64 => "double".to_owned(),
            Type::Char => "uint".to_owned(),
            Type::String => "string".to_owned(),
            Type::ErrorContext => todo!("error context name with qualifier"),
            Type::Id(id) => {
                let ty = &self.resolve.types[*id];
                match &ty.kind {
                    TypeDefKind::Type(ty) => {
                        self.name_with_qualifier(ty, qualifier, parameter_type)
                    }
                    TypeDefKind::List(ty) => {
                        if crate::world_generator::is_primitive(ty)
                            && self.direction == Direction::Import
                            && parameter_type == ParameterType::Span
                        {
                            format!("global::System.Span<{}>", self.type_name(ty))
                        } else if crate::world_generator::is_primitive(ty)
                            && self.direction == Direction::Import
                            && parameter_type == ParameterType::Memory
                        {
                            format!("global::System.Memory<{}>", self.type_name(ty))
                        } else if crate::world_generator::is_primitive(ty) {
                            format!("{}[]", self.type_name(ty))
                        } else {
                            format!(
                                "global::System.Collections.Generic.List<{}>",
                                self.type_name_with_qualifier(ty, qualifier)
                            )
                        }
                    }
                    TypeDefKind::Tuple(tuple) => {
                        let count = tuple.types.len();
                        self.csharp_gen.tuple_counts.insert(count);

                        let params = match count {
                            0 => String::new(),
                            1 => self
                                .type_name_with_qualifier(tuple.types.first().unwrap(), qualifier),
                            _ => format!(
                                "({})",
                                tuple
                                    .types
                                    .iter()
                                    .map(|ty| self.type_name_with_qualifier(ty, qualifier))
                                    .collect::<Vec<_>>()
                                    .join(", ")
                            ),
                        };

                        params
                    }
                    TypeDefKind::Option(base_ty) => {
                        self.csharp_gen.needs_option = true;
                        let nesting = if let Type::Id(id) = base_ty {
                            matches!(&self.resolve.types[*id].kind, TypeDefKind::Option(_))
                        } else {
                            false
                        };
                        let base_ty = self.type_name_with_qualifier(base_ty, qualifier);
                        if nesting {
                            format!("Option<{base_ty}>")
                        } else {
                            format!("{base_ty}?")
                        }
                    }
                    TypeDefKind::Result(result) => {
                        self.csharp_gen.needs_result = true;
                        let mut name = |ty: &Option<Type>| {
                            ty.as_ref()
                                .map(|ty| self.type_name_with_qualifier(ty, qualifier))
                                .unwrap_or_else(|| "None".to_owned())
                        };
                        let ok = name(&result.ok);
                        let err = name(&result.err);

                        format!("Result<{ok}, {err}>")
                    }
                    TypeDefKind::Handle(handle) => {
                        let (Handle::Own(id) | Handle::Borrow(id)) = handle;
                        self.type_name_with_qualifier(&Type::Id(*id), qualifier)
                    }
                    TypeDefKind::Future(ty) => {
                        let generic_type = if let Some(typ) = ty {
                            format!("<{}>", self.type_name_with_qualifier(typ, qualifier))
                        } else {
                            String::new()
                        };
                        return format!("FutureReader{generic_type}").to_owned();
                    }
                    TypeDefKind::Stream(ty) => {
                        let generic_type = if let Some(typ) = ty {
                            format!("<{}>", self.type_name_with_qualifier(typ, qualifier))
                        } else {
                            String::new()
                        };
                        return format!("StreamReader{generic_type}").to_owned();
                    }
                    TypeDefKind::Map(key, value) => {
                        format!(
                            "global::System.Collections.Generic.Dictionary<{}, {}>",
                            self.type_name_with_qualifier(key, qualifier),
                            self.type_name_with_qualifier(value, qualifier)
                        )
                    }
                    _ => {
                        if let Some(name) = &ty.name {
                            format!(
                                "{}{}",
                                self.qualifier(qualifier, id),
                                name.to_upper_camel_case()
                            )
                        } else {
                            unreachable!("todo: {ty:?}")
                        }
                    }
                }
            }
        }
    }

    fn print_docs(&mut self, docs: &Docs) {
        if let Some(docs) = &docs.contents {
            let lines = docs
                .trim()
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("*/", "*") // We can't close the comment, so if the user comment has */ we just remove the /
                .lines()
                .map(|line| format!("* {line}"))
                .collect::<Vec<_>>()
                .join("\n");

            uwrite!(
                self.src,
                "
                /**
                 {lines}
                 */
                "
            )
        }
    }

    pub(crate) fn non_empty_type<'a>(&self, ty: Option<&'a Type>) -> Option<&'a Type> {
        if let Some(ty) = ty {
            let id = match ty {
                Type::Id(id) => *id,
                _ => return Some(ty),
            };
            match &self.resolve.types[id].kind {
                TypeDefKind::Type(t) => self.non_empty_type(Some(t)).map(|_| ty),
                TypeDefKind::Record(r) => (!r.fields.is_empty()).then_some(ty),
                TypeDefKind::Tuple(t) => (!t.types.is_empty()).then_some(ty),
                _ => Some(ty),
            }
        } else {
            None
        }
    }

    pub(crate) fn start_resource(&mut self, id: TypeId, key: Option<&WorldKey>) {
        let access = self.csharp_gen.access_modifier();
        let qualified = self.type_name_with_qualifier(&Type::Id(id), true);
        let info = &self.csharp_gen.all_resources[&id];
        let name = info.name.clone();
        let upper_camel = name.to_upper_camel_case();
        let docs = info.docs.clone();
        self.print_docs(&docs);

        match self.direction {
            Direction::Import => {
                let module_name = key
                    .map(|key| self.resolve.name_world_key(key))
                    .unwrap_or_else(|| "$root".into());

                // As of this writing, we cannot safely drop a handle to an imported resource from a .NET finalizer
                // because it may still have one or more open child resources.  Once WIT has explicit syntax for
                // indicating parent/child relationships, we should be able to use that information to keep track
                // of child resources automatically in generated code, at which point we'll be able to drop them in
                // the correct order from finalizers.
                uwriteln!(
                    self.src,
                    r#"
                    {access} class {upper_camel}: global::System.IDisposable {{
                        internal int Handle {{ get; set; }}

                        {access} readonly record struct THandle(int Handle);

                        {access} {upper_camel}(THandle handle) {{
                            Handle = handle.Handle;
                        }}

                        public void Dispose() {{
                            Dispose(true);
                        }}

                        [global::System.Runtime.InteropServices.DllImportAttribute("{module_name}", EntryPoint = "[resource-drop]{name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                        private static extern void wasmImportResourceDrop(int p0);

                        protected virtual void Dispose(bool disposing) {{
                            if (disposing && Handle != 0) {{
                                wasmImportResourceDrop(Handle);
                                Handle = 0;
                            }}
                        }}
                    "#
                );
            }
            Direction::Export => {
                let prefix = key
                    .map(|s| format!("{}#", self.resolve.name_world_key(s)))
                    .unwrap_or_default();

                uwrite!(
                    self.csharp_interop_src,
                    r#"
                    [global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "{prefix}[dtor]{name}")]
                    {access} static unsafe void wasmExportResourceDtor{upper_camel}(int rep) {{
                        var val = ({qualified}) {qualified}.repTable.Remove(rep);
                        val.Handle = 0;
                        // Note we call `Dispose` here even though the handle has already been disposed in case
                        // the implementation has overridden `Dispose(bool)`.
                        val.Dispose();
                    }}
                    "#
                );

                let module_name = key
                    .map(|key| format!("[export]{}", self.resolve.name_world_key(key)))
                    .unwrap_or_else(|| "[export]$root".into());

                // The ergonomics of exported resources are not ideal, currently. Implementing such a resource
                // requires both extending a class and implementing an interface. The reason for the class is to
                // allow implementers to inherit code which tracks and disposes of the resource handle; the reason
                // for the interface is to express the API contract which the implementation must fulfill,
                // including static functions.
                //
                // We could remove the need for the class (and its `IDisposable` implementation) entirely if we
                // were to dispose of the handle immediately when lifting an owned handle, in which case we would
                // be left with nothing to keep track of or dispose later. However, we keep the handle alive in
                // case we want to give ownership back to the host again, in which case we'll be able to reuse the
                // same handle instead of calling `[resource-new]` to allocate a new one. Whether this optimization
                // is worth the trouble is open to debate, but we currently consider it a worthwhile tradeoff.
                //
                // Note that applications which export resources are relatively rare compared to those which only
                // import them, so in practice most developers won't encounter any of this anyway.
                uwriteln!(
                    self.src,
                    r#"
                    {access} abstract class {upper_camel}: global::System.IDisposable {{
                        internal static RepTable<{upper_camel}> repTable = new ();
                        internal int Handle {{ get; set; }}

                        public void Dispose() {{
                            Dispose(true);
                            GC.SuppressFinalize(this);
                        }}

                        internal static class WasmInterop {{
                            [global::System.Runtime.InteropServices.DllImportAttribute("{module_name}", EntryPoint = "[resource-drop]{name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                            internal static extern void wasmImportResourceDrop(int p0);

                            [global::System.Runtime.InteropServices.DllImportAttribute("{module_name}", EntryPoint = "[resource-new]{name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                            internal static extern int wasmImportResourceNew(int p0);

                            [global::System.Runtime.InteropServices.DllImportAttribute("{module_name}", EntryPoint = "[resource-rep]{name}"), global::System.Runtime.InteropServices.WasmImportLinkageAttribute]
                            internal static extern int wasmImportResourceRep(int p0);
                        }}

                        protected virtual void Dispose(bool disposing) {{
                            if (Handle != 0) {{
                                var handle = Handle;
                                Handle = 0;
                                WasmInterop.wasmImportResourceDrop(handle);
                            }}
                        }}

                        ~{upper_camel}() {{
                            Dispose(false);
                        }}
                    }}

                    {access} interface I{upper_camel} {{
                    "#
                );
                self.csharp_gen.needs_rep_table = true;

                if self.csharp_gen.opts.generate_stub {
                    let super_ = self.type_name_with_qualifier(&Type::Id(id), true);
                    let interface = {
                        let split = super_.split('.').collect::<Vec<_>>();
                        split
                            .iter()
                            .map(|&v| v.to_owned())
                            .take(split.len() - 1)
                            .chain(split.last().map(|v| format!("I{v}")))
                            .collect::<Vec<_>>()
                            .join(".")
                    };

                    uwriteln!(
                        self.stub,
                        r#"
                        {access} class {upper_camel}: {super_}, {interface} {{
                        "#
                    );
                }
            }
        };

        uwrite!(
            self.csharp_interop_src,
            r#"
            internal static class {upper_camel}
            {{
            "#
        );
    }

    pub(crate) fn end_resource(&mut self) {
        if self.direction == Direction::Export && self.csharp_gen.opts.generate_stub {
            uwriteln!(
                self.stub,
                "
                }}
                "
            );
        }

        uwriteln!(
            self.src,
            "
            }}
            "
        );

        uwrite!(
            self.csharp_interop_src,
            "
            }}
            "
        );
    }

    fn sig_string(&mut self, func: &Function, qualifier: bool) -> String {
        let result_type = self.func_return_type(&func, qualifier);

        let params = func
            .params
            .iter()
            .skip(if let FunctionKind::Method(_) = &func.kind {
                1
            } else {
                0
            })
            .map(|Param { name, ty, .. }| {
                let ty = self.type_name_with_qualifier(ty, qualifier);
                let name = name.to_csharp_ident();
                format!("{ty} {name}")
            })
            .collect::<Vec<_>>()
            .join(", ");

        let (camel_name, modifiers) = match &func.kind {
            FunctionKind::Freestanding
            | FunctionKind::AsyncFreestanding
            | FunctionKind::Static(_)
            | FunctionKind::AsyncStatic(_) => (func.item_name().to_upper_camel_case(), "static"),

            FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => {
                (func.item_name().to_upper_camel_case(), "")
            }
            FunctionKind::Constructor(id) => (
                self.csharp_gen.all_resources[id].name.to_upper_camel_case(),
                "",
            ),
        };

        let access = self.csharp_gen.access_modifier();

        format!("{access} {modifiers} {result_type} {camel_name}({params})")
    }

    pub(crate) fn add_future(
        &mut self,
        func_name: &str,
        generic_type_name: &str,
        qualified_generic_type_name: &str,
        ty: Option<Type>,
    ) {
        self.futures.push(FutureInfo {
            name: func_name.to_string(),
            generic_type_name: generic_type_name.to_string(),
            qualified_generic_type_name: qualified_generic_type_name.to_string(),
            ty: ty,
        });
    }

    pub(crate) fn add_stream(
        &mut self,
        func_name: &str,
        generic_type_name: &str,
        qualified_generic_type_name: &str,
        ty: Option<Type>,
    ) {
        self.streams.push(FutureInfo {
            name: func_name.to_string(),
            generic_type_name: generic_type_name.to_string(),
            qualified_generic_type_name: qualified_generic_type_name.to_string(),
            ty: ty,
        });
    }

    fn lift_from_memory(&mut self, address: &str, ty: &Type) -> (String, String) {
        let boxed: Box<[String]> = Vec::new().into_boxed_slice();

        let mut f = FunctionBindgen::new(
            self,
            "",
            &FunctionKind::AsyncFreestanding,
            boxed,
            vec![],
            ParameterType::ABI,
            None,
        );
        let result = abi::lift_from_memory(f.interface_gen.resolve, &mut f, address.into(), ty);

        (f.src, result)
    }

    fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type) -> String {
        let boxed: Box<[String]> = Vec::new().into_boxed_slice();

        let mut f = FunctionBindgen::new(
            self,
            "",
            &FunctionKind::AsyncFreestanding,
            boxed,
            vec![],
            ParameterType::ABI,
            None,
        );
        abi::lower_to_memory(
            f.interface_gen.resolve,
            &mut f,
            address.into(),
            value.into(),
            ty,
        );

        f.src
    }
}

impl<'a> CoreInterfaceGenerator<'a> for InterfaceGenerator<'a> {
    fn resolve(&self) -> &'a Resolve {
        self.resolve
    }

    fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) {
        let access = self.csharp_gen.access_modifier();

        self.print_docs(docs);

        let name = name.to_upper_camel_case();

        let parameters = record
            .fields
            .iter()
            .map(|field| {
                format!(
                    "{} {}",
                    self.type_name(&field.ty),
                    field.name.to_csharp_ident()
                )
            })
            .collect::<Vec<_>>()
            .join(", ");

        let assignments = record
            .fields
            .iter()
            .map(|field| {
                let name = field.name.to_csharp_ident();
                format!("this.{name} = {name};")
            })
            .collect::<Vec<_>>()
            .join("\n");

        let fields = if record.fields.is_empty() {
            format!("{access} const {name} INSTANCE = new {name}();")
        } else {
            record
                .fields
                .iter()
                .map(|field| {
                    format!(
                        "{access} {} {};",
                        self.type_name(&field.ty),
                        field.name.to_csharp_ident()
                    )
                })
                .collect::<Vec<_>>()
                .join("\n")
        };

        uwrite!(
            self.src,
            "
            {access} struct {name} {{
                {fields}

                {access} {name}({parameters}) {{
                    {assignments}
                }}
            }}
            "
        );
    }

    fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) {
        self.print_docs(docs);

        let name = name.to_upper_camel_case();

        let enum_elements = flags
            .flags
            .iter()
            .enumerate()
            .map(|(i, flag)| {
                let flag_name = flag.name.to_shouty_snake_case();
                let suffix = if matches!(flags.repr(), FlagsRepr::U32(2)) {
                    "UL"
                } else {
                    ""
                };
                format!("{flag_name} = 1{suffix} << {i},")
            })
            .collect::<Vec<_>>()
            .join("\n");

        let enum_type = match flags.repr() {
            FlagsRepr::U32(2) => ": ulong",
            FlagsRepr::U16 => ": ushort",
            FlagsRepr::U8 => ": byte",
            _ => "",
        };

        let access = self.csharp_gen.access_modifier();

        uwrite!(
            self.src,
            "
            {access} enum {name} {enum_type} {{
                {enum_elements}
            }}
            "
        );
    }

    fn type_tuple(&mut self, id: TypeId, _name: &str, _tuple: &Tuple, _docs: &Docs) {
        self.type_name(&Type::Id(id));
    }

    fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, docs: &Docs) {
        self.print_docs(docs);

        let name = name.to_upper_camel_case();
        let tag_type = int_type(variant.tag());
        let access = self.csharp_gen.access_modifier();

        let constructors = variant
            .cases
            .iter()
            .map(|case| {
                let case_name = case.name.to_csharp_ident();
                let tag = case.name.to_csharp_ident_upper();
                let method_name = variant_new_func_name(&name, &tag);
                let (parameter, argument) = if let Some(ty) = self.non_empty_type(case.ty.as_ref())
                {
                    (
                        format!("{} {case_name}", self.type_name(ty)),
                        case_name.deref(),
                    )
                } else {
                    (String::new(), "null")
                };

                format!(
                    "{access} static {name} {method_name}({parameter}) {{
                         return new {name}(Tags.{tag}, {argument});
                     }}
                    "
                )
            })
            .collect::<Vec<_>>()
            .join("\n");

        let accessors = variant
            .cases
            .iter()
            .filter_map(|case| {
                self.non_empty_type(case.ty.as_ref()).map(|ty| {
                    let case_name = case.name.to_upper_camel_case();
                    let tag = case.name.to_csharp_ident_upper();
                    let ty = self.type_name(ty);
                    format!(
                        r#"{access} {ty} As{case_name}
                        {{
                            get
                            {{
                                if (Tag == Tags.{tag})
                                    return ({ty})value!;
                                else
                                    throw new global::System.ArgumentException("expected {tag}, got " + Tag);
                            }}
                        }}
                        "#
                    )
                })
            })
            .collect::<Vec<_>>()
            .join("\n");

        let tags = variant
            .cases
            .iter()
            .enumerate()
            .map(|(i, case)| {
                let tag = case.name.to_csharp_ident_upper();
                format!("{access} const {tag_type} {tag} = {i};")
            })
            .collect::<Vec<_>>()
            .join("\n");

        uwrite!(
            self.src,
            "
            {access} class {name} {{
                {access} readonly {tag_type} Tag;
                private readonly object? value;

                private {name}({tag_type} tag, object? value) {{
                    this.Tag = tag;
                    this.value = value;
                }}

                {constructors}
                {accessors}

                {access} class Tags {{
                    {tags}
                }}
            }}
            "
        );
    }

    fn type_option(&mut self, id: TypeId, _name: &str, _payload: &Type, _docs: &Docs) {
        self.type_name(&Type::Id(id));
    }

    fn type_result(&mut self, id: TypeId, _name: &str, _result: &Result_, _docs: &Docs) {
        self.type_name(&Type::Id(id));
    }

    fn type_enum(&mut self, ty: TypeId, name: &str, enum_: &Enum, docs: &Docs) {
        self.print_docs(docs);

        let name = name.to_upper_camel_case();

        let cases = enum_
            .cases
            .iter()
            .map(|case| case.name.to_shouty_snake_case())
            .collect::<Vec<_>>()
            .join(", ");

        let access = self.csharp_gen.access_modifier();

        // Write the top level bidirectional types to csharp_gen.  Could also create a class to gather the interface level bidrectional types.
        match &self.resolve.types[ty].owner {
            TypeOwner::World(_id) => {
                self.csharp_gen.bidirectional_types_src.insert(format!(
                    "
                    {access} enum {name} {{
                        {cases}
                    }}
                    "
                ));
            }
            _ => {
                uwrite!(
                    self.src,
                    "
                    {access} enum {name} {{
                        {cases}
                    }}
                    "
                );
            }
        };
    }

    fn type_alias(&mut self, id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
        self.type_name(&Type::Id(id));
    }

    fn type_list(&mut self, id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
        self.type_name(&Type::Id(id));
    }

    fn type_fixed_length_list(
        &mut self,
        _id: TypeId,
        _name: &str,
        _ty: &Type,
        _size: u32,
        _docs: &Docs,
    ) {
        todo!("named fixed-length list types are not yet supported in the C# backend")
    }

    fn type_map(&mut self, id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
        self.type_name(&Type::Id(id));
    }

    fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
        unimplemented!();
    }

    fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) {
        // Here we just record information about the resource; we don't actually emit any code until we're ready to
        // visit any functions associated with the resource (e.g. in CSharp::import_interface, etc.).
        self.csharp_gen
            .all_resources
            .entry(id)
            .or_insert_with(|| ResourceInfo {
                module: self.name.to_owned(),
                name: name.to_owned(),
                docs: docs.clone(),
                direction: Direction::Import,
            })
            .direction = self.direction;
    }

    fn type_future(&mut self, id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
        self.type_name(&Type::Id(id));
    }

    fn type_stream(&mut self, id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
        self.type_name(&Type::Id(id));
    }
}

// TODO: Is this not publicly available elsewhere, a function that says if a type needs to be returned as a pointer
fn needs_ptr(ty: &Type) -> bool {
    // TODO: This list is not complete, e.g. handles should be here.
    match *ty {
        Type::Bool
        | Type::S8
        | Type::U8
        | Type::S16
        | Type::U16
        | Type::S32
        | Type::U32
        | Type::S64
        | Type::U64
        | Type::Char
        | Type::F32
        | Type::F64 => false,
        _ => true,
    }
}

// Handles the tag being the same name as the variant, which would cause a method with the same name as the type in C# which is not valid.
pub fn variant_new_func_name(variant_name: &String, tag: &String) -> String {
    if *tag == *variant_name {
        format!("{tag}_") // Underscores are not valid in wit identifiers so this should be safe.
    } else {
        tag.clone()
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum ParameterType {
    ABI,
    Span,
    Memory,
}

fn payload_and_results(
    resolve: &Resolve,
    ty: Type,
    with_wit_results: bool,
) -> (Option<Type>, Vec<TypeId>) {
    if with_wit_results {
        return (Some(ty), Vec::new());
    }

    fn recurse(resolve: &Resolve, ty: Type, results: &mut Vec<TypeId>) -> Option<Type> {
        if let Type::Id(id) = ty {
            if let TypeDefKind::Result(result) = &resolve.types[id].kind {
                results.push(id);
                if let Some(ty) = result.ok {
                    recurse(resolve, ty, results)
                } else {
                    None
                }
            } else {
                Some(ty)
            }
        } else {
            Some(ty)
        }
    }

    let mut results = Vec::new();
    let payload = recurse(resolve, ty, &mut results);
    (payload, results)
}

fn modifiers(func: &Function, name: &str, direction: Direction) -> String {
    let new_modifier = match &func.kind {
        // Avoid warnings about name clashes.
        //
        // TODO: add other `object` method names here
        FunctionKind::Method(_) if name == "GetType" => " new",
        _ => "",
    };

    let static_modifiers = match &func.kind {
        FunctionKind::Freestanding
        | FunctionKind::Static(_)
        | FunctionKind::AsyncFreestanding
        | FunctionKind::AsyncStatic(_) => "static",
        _ => "",
    };

    let abstract_modifier = match (direction, &func.kind) {
        (Direction::Export, _) => " abstract",
        _ => "",
    };

    let async_modifier = match &func.kind {
        FunctionKind::AsyncMethod(_) | FunctionKind::AsyncStatic(_) if abstract_modifier == "" => {
            " async"
        }
        _ => "",
    };

    format!("{static_modifiers}{abstract_modifier}{async_modifier}{new_modifier}")
}

fn int_type(int: Int) -> &'static str {
    match int {
        Int::U8 => "byte",
        Int::U16 => "ushort",
        Int::U32 => "uint",
        Int::U64 => "ulong",
    }
}
