use std::collections::HashMap;

use heck::{ToSnakeCase, ToUpperCamelCase};
use wit_bindgen_core::{
    Ns, dealias,
    wit_parser::{
        Function, FunctionKind, Handle, InterfaceId, Param, Resolve, Type, TypeDef, TypeDefKind,
        TypeOwner, WorldId, WorldKey,
    },
};

pub(crate) const ASYNC_CORE_DIR: &str = "async-core";

#[derive(Default)]
pub(crate) struct Imports {
    pub packages: HashMap<String, String>,
    ns: Ns,
}

pub(crate) struct MoonbitSignature {
    pub name: String,
    pub params: Vec<(String, Type)>,
    pub result_type: Option<Type>,
}

#[derive(Default)]
pub(crate) struct PkgResolver {
    pub resolve: Resolve,
    // Packages imported by each package
    pub package_import: HashMap<String, Imports>,
    pub import_interface_names: HashMap<InterfaceId, String>,
    pub export_interface_names: HashMap<InterfaceId, String>,
}

impl PkgResolver {
    pub(crate) fn qualify_package(&mut self, this: &str, name: &str) -> String {
        if name != this {
            let imports = self.package_import.entry(this.to_string()).or_default();
            if let Some(alias) = imports.packages.get(name) {
                format!("@{alias}.")
            } else {
                let alias = imports.ns.tmp(name.split(".").last().unwrap());
                imports
                    .packages
                    .entry(name.to_string())
                    .or_insert(alias.clone());
                format!("@{alias}.")
            }
        } else {
            "".into()
        }
    }

    pub(crate) fn qualifier(&mut self, this: &str, ty: &TypeDef) -> String {
        if let TypeOwner::Interface(id) = &ty.owner {
            if let Some(name) = self.export_interface_names.get(id) {
                if name != this {
                    return self.qualify_package(this, &name.clone());
                }
            } else if let Some(name) = self.import_interface_names.get(id) {
                if name != this {
                    return self.qualify_package(this, &name.clone());
                }
            }
        } else if let TypeOwner::World(id) = &ty.owner {
            let name = PkgResolver::world_name(&self.resolve, *id);
            if name != this {
                return self.qualify_package(this, &name.clone());
            }
        }

        String::new()
    }

    pub(crate) fn func_call(
        &mut self,
        this: &str,
        func: &Function,
        func_interface: &str,
    ) -> String {
        match func.kind {
            FunctionKind::Freestanding => {
                format!(
                    "{}{}",
                    self.qualify_package(this, func_interface),
                    func.name.to_moonbit_ident()
                )
            }
            FunctionKind::AsyncFreestanding => {
                format!(
                    "{}{}",
                    self.qualify_package(this, func_interface),
                    func.name.to_moonbit_ident()
                )
            }
            FunctionKind::Constructor(ty) => {
                let name = self.type_constructor(this, &Type::Id(ty));
                format!(
                    "{}::{}",
                    name,
                    func.name.replace("[constructor]", "").to_moonbit_ident()
                )
            }
            FunctionKind::Method(ty)
            | FunctionKind::Static(ty)
            | FunctionKind::AsyncMethod(ty)
            | FunctionKind::AsyncStatic(ty) => {
                let name = self.type_constructor(this, &Type::Id(ty));
                format!(
                    "{}::{}",
                    name,
                    func.name.split(".").last().unwrap().to_moonbit_ident()
                )
            }
        }
    }

    pub(crate) fn type_constructor(&mut self, this: &str, ty: &Type) -> String {
        match ty {
            Type::ErrorContext => unimplemented!("moonbit error context type name"),
            Type::Id(id) => {
                let ty = self.resolve.types[dealias(&self.resolve, *id)].clone();
                match ty.kind {
                    TypeDefKind::Type(ty) => self.type_constructor(this, &ty),
                    TypeDefKind::Handle(handle) => {
                        let ty = match handle {
                            Handle::Own(ty) => ty,
                            Handle::Borrow(ty) => ty,
                        };
                        let ty = self.resolve.types[dealias(&self.resolve, ty)].clone();
                        if let Some(name) = &ty.name {
                            format!(
                                "{}{}",
                                self.qualifier(this, &ty),
                                name.to_moonbit_type_ident()
                            )
                        } else {
                            unreachable!()
                        }
                    }
                    TypeDefKind::Enum(_)
                    | TypeDefKind::Resource
                    | TypeDefKind::Flags(_)
                    | TypeDefKind::Variant(_)
                    | TypeDefKind::Record(_) => {
                        if let Some(name) = &ty.name {
                            format!(
                                "{}{}",
                                self.qualifier(this, &ty),
                                name.to_moonbit_type_ident()
                            )
                        } else {
                            unreachable!()
                        }
                    }
                    TypeDefKind::Result(_) => "Result".into(),
                    TypeDefKind::Option(_) => "Option".into(),
                    _ => {
                        unreachable!(
                            "Should not call constructor or method for builtin type: {:?}",
                            ty
                        )
                    }
                }
            }
            _ => {
                unreachable!(
                    "Should not call constructor or method for primitive types: {:?}",
                    ty
                )
            }
        }
    }

    pub(crate) fn type_name(&mut self, this: &str, ty: &Type) -> String {
        match ty {
            Type::Bool => "Bool".into(),
            Type::U8 => "Byte".into(),
            Type::S32 | Type::S8 | Type::S16 => "Int".into(),
            Type::U16 | Type::U32 => "UInt".into(),
            Type::Char => "Char".into(),
            Type::U64 => "UInt64".into(),
            Type::S64 => "Int64".into(),
            Type::F32 => "Float".into(),
            Type::F64 => "Double".into(),
            Type::String => "String".into(),
            Type::ErrorContext => todo!("moonbit error context type name"),
            Type::Id(id) => {
                let ty = self.resolve.types[dealias(&self.resolve, *id)].clone();
                match ty.kind {
                    TypeDefKind::Type(ty) => self.type_name(this, &ty),
                    TypeDefKind::List(ty) => match ty {
                        Type::U8
                        | Type::U32
                        | Type::U64
                        | Type::S32
                        | Type::S64
                        | Type::F32
                        | Type::F64 => {
                            format!("FixedArray[{}]", self.type_name(this, &ty))
                        }
                        _ => format!("Array[{}]", self.type_name(this, &ty)),
                    },
                    TypeDefKind::FixedLengthList(ty, _size) => {
                        format!("FixedArray[{}]", self.type_name(this, &ty))
                    }
                    TypeDefKind::Tuple(tuple) => {
                        format!(
                            "({})",
                            tuple
                                .types
                                .iter()
                                .map(|ty| self.type_name(this, ty))
                                .collect::<Vec<_>>()
                                .join(", ")
                        )
                    }
                    TypeDefKind::Option(ty) => {
                        format!("{}?", self.type_name(this, &ty))
                    }
                    TypeDefKind::Result(result) => {
                        let mut name = |ty: &Option<Type>| {
                            ty.as_ref()
                                .map(|ty| self.type_name(this, ty))
                                .unwrap_or_else(|| "Unit".into())
                        };
                        let ok = name(&result.ok);
                        let err = name(&result.err);

                        format!("Result[{ok}, {err}]")
                    }
                    TypeDefKind::Handle(handle) => {
                        let ty = match handle {
                            Handle::Own(ty) => ty,
                            Handle::Borrow(ty) => ty,
                        };
                        let ty = self.resolve.types[dealias(&self.resolve, ty)].clone();
                        if let Some(name) = &ty.name {
                            format!(
                                "{}{}",
                                self.qualifier(this, &ty),
                                name.to_moonbit_type_ident()
                            )
                        } else {
                            unreachable!()
                        }
                    }

                    TypeDefKind::Future(ty) => {
                        let qualifier = self.qualify_package(this, ASYNC_CORE_DIR);
                        format!(
                            "{}Future[{}]",
                            qualifier,
                            ty.as_ref()
                                .map(|t| self.type_name(this, t))
                                .unwrap_or_else(|| "Unit".into())
                        )
                    }

                    TypeDefKind::Stream(ty) => {
                        let qualifier = self.qualify_package(this, ASYNC_CORE_DIR);
                        format!(
                            "{}Stream[{}]",
                            qualifier,
                            ty.as_ref()
                                .map(|t| self.type_name(this, t))
                                .unwrap_or_else(|| "Unit".into())
                        )
                    }

                    TypeDefKind::Map(key, value) => {
                        format!(
                            "Map[{}, {}]",
                            self.type_name(this, &key),
                            self.type_name(this, &value)
                        )
                    }

                    _ => {
                        if let Some(name) = &ty.name {
                            format!(
                                "{}{}",
                                self.qualifier(this, &ty),
                                name.to_moonbit_type_ident()
                            )
                        } else {
                            unreachable!()
                        }
                    }
                }
            }
        }
    }

    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 mbt_sig(
        &mut self,
        this: &str,
        func: &Function,
        ignore_param: bool,
    ) -> MoonbitSignature {
        let name = match func.kind {
            FunctionKind::Freestanding => func.name.to_moonbit_ident(),
            FunctionKind::Constructor(_) => {
                func.name.replace("[constructor]", "").to_moonbit_ident()
            }
            _ => func.name.split(".").last().unwrap().to_moonbit_ident(),
        };
        let type_name = match func.kind.resource() {
            Some(ty) => {
                format!("{}::", self.type_constructor(this, &Type::Id(ty)))
            }
            None => "".into(),
        };

        let params = func
            .params
            .iter()
            .map(|Param { name, ty, .. }| {
                let name = if ignore_param {
                    format!("_{}", name.to_moonbit_ident())
                } else {
                    name.to_moonbit_ident()
                };
                (name, *ty)
            })
            .collect::<Vec<_>>();

        MoonbitSignature {
            name: format!("{type_name}{name}"),
            params,
            result_type: func.result,
        }
    }

    pub(crate) fn world_name(resolve: &Resolve, world: WorldId) -> String {
        format!("world.{}", resolve.worlds[world].name)
    }

    pub(crate) fn interface_name(resolve: &Resolve, name: &WorldKey) -> 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 = match name {
            WorldKey::Name(name) => name,
            WorldKey::Interface(id) => resolve.interfaces[*id].name.as_ref().unwrap(),
        };

        format!(
            "interface.{}{name}",
            if let Some(name) = &pkg {
                format!("{}.{}.", name.namespace, name.name)
            } else {
                String::new()
            }
        )
    }
}

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

    #[test]
    fn package_names_and_aliases_preserve_kebab_case() {
        let mut resolve = Resolve::default();
        let package = resolve
            .push_str(
                "test.wit",
                r#"
                package my:test;

                interface leaf-interface {}

                world http-proxy {
                    import leaf-interface;
                }
                "#,
            )
            .unwrap();
        let world = resolve
            .select_world(&[package], Some("http-proxy"))
            .unwrap();
        let (key, _) = resolve.worlds[world].imports.iter().next().unwrap();

        let interface = PkgResolver::interface_name(&resolve, key);
        assert_eq!(interface, "interface.my.test.leaf-interface");
        assert_eq!(PkgResolver::world_name(&resolve, world), "world.http-proxy");

        let mut packages = PkgResolver::default();
        assert_eq!(
            packages.qualify_package("world.http-proxy", &interface),
            "@leaf-interface."
        );
        assert_eq!(
            packages.package_import["world.http-proxy"].packages[&interface],
            "leaf-interface"
        );
    }
}

pub(crate) trait ToMoonBitIdent: ToOwned {
    fn to_moonbit_ident(&self) -> Self::Owned;
}

impl ToMoonBitIdent for str {
    fn to_moonbit_ident(&self) -> String {
        // Escape MoonBit keywords and reserved keywords
        match self {
            // Keywords
            "as" | "else" | "extern" | "fn" | "fnalias" | "if" | "let" | "const" | "match" | "using"
            | "mut" | "type" | "typealias" | "struct" | "enum" | "trait" | "traitalias" | "derive"
            | "while" | "break" | "continue" | "import" | "return" | "throw" | "raise" | "try" | "catch"
            | "pub" | "priv" | "readonly" | "true" | "false" | "_" | "test" | "loop" | "for" | "in" | "impl"
            | "with" | "guard" | "async" | "is" | "suberror" | "and" | "letrec" | "enumview" | "noraise" 
            | "defer" | "init" | "main"
            // Reserved keywords
            | "module" | "move" | "ref" | "static" | "super" | "unsafe" | "use" | "where" | "await"
            | "dyn" | "abstract" | "do" | "final" | "macro" | "override" | "typeof" | "virtual" | "yield"
            | "local" | "method" | "alias" | "assert" | "package" | "recur" | "isnot" | "define" | "downcast"
            | "inherit" | "member" | "namespace" | "upcast" | "void" | "lazy" | "include" | "mixin"
            | "protected" | "sealed" | "constructor" | "atomic" | "volatile" | "anyframe" | "anytype"
            | "asm" | "comptime" | "errdefer" | "export" | "opaque" | "orelse" | "resume" | "threadlocal"
            | "unreachable" | "dynclass" | "dynobj" | "dynrec" | "var" | "finally" | "noasync" => {
                format!("{self}_")
            }
            _ => self.strip_prefix("[async]").unwrap_or(self).to_snake_case(),
        }
    }
}

pub(crate) trait ToMoonBitTypeIdent: ToOwned {
    fn to_moonbit_type_ident(&self) -> Self::Owned;
}

impl ToMoonBitTypeIdent for str {
    fn to_moonbit_type_ident(&self) -> String {
        // Escape MoonBit builtin types
        match self.to_upper_camel_case().as_str() {
            type_name @ ("Bool" | "Byte" | "Int16" | "UInt16" | "Int" | "Int64" | "UInt"
            | "UInt64" | "Float" | "Double" | "Error" | "Bytes" | "ReadonlyArray"
            | "Array" | "FixedArray" | "Map" | "String" | "StringBuilder"
            | "Option" | "Result" | "Char" | "Json") => {
                format!("{type_name}_")
            }
            type_name => type_name.to_owned(),
        }
    }
}
