use crate::EmitEffects;
use crate::Planner;
use crate::names::go_name;
use crate::types::native::NativeGoType;
use crate::types::prelude::PreludeType;
use syntax::ast::Annotation;
use syntax::types::Type;

#[derive(Debug, Clone, Default)]
pub(crate) struct GoType {
    pub(crate) code: String,
    pub(crate) needs_stdlib: bool,
    pub(crate) go_imports: Vec<String>,
}

impl GoType {
    pub(crate) fn new(code: impl Into<String>) -> Self {
        Self {
            code: code.into(),
            needs_stdlib: false,
            go_imports: Vec::new(),
        }
    }

    pub(crate) fn stdlib(code: impl Into<String>) -> Self {
        Self {
            code: code.into(),
            needs_stdlib: true,
            go_imports: Vec::new(),
        }
    }

    pub(crate) fn with_go_import(code: impl Into<String>, go_path: String) -> Self {
        Self {
            code: code.into(),
            needs_stdlib: false,
            go_imports: vec![go_path],
        }
    }

    pub(crate) fn merge(&mut self, other: &GoType) {
        self.needs_stdlib = self.needs_stdlib || other.needs_stdlib;
        self.go_imports.extend(other.go_imports.iter().cloned());
    }

    fn merge_all<'a>(&mut self, others: impl IntoIterator<Item = &'a GoType>) {
        for other in others {
            self.merge(other);
        }
    }
}

impl std::fmt::Display for GoType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.code)
    }
}

impl Planner<'_> {
    pub(crate) fn go_type(&self, ty: &Type) -> GoType {
        match ty {
            Type::Nominal { id, params, .. } => self.emit_constructor(id, params, ty),
            Type::Simple(kind) => {
                if matches!(kind, syntax::types::SimpleKind::Unit) {
                    GoType::new("struct{}")
                } else {
                    GoType::new(kind.leaf_name().to_string())
                }
            }
            Type::Compound { kind, args } => self.emit_compound(*kind, args, ty),
            Type::Function(f) => self.emit_function_type(&f.params, &f.return_type),
            Type::Var { .. } => GoType::new("any"),
            Type::Forall { .. } => GoType::new("any"),
            Type::Parameter(name) => GoType::new(name.to_string()),
            Type::Never => GoType::new("struct{}"),
            Type::Error => unreachable!("Type::Error should not reach the emitter"),
            Type::Tuple(elements) => self.emit_tuple_type(elements),
            Type::ImportNamespace(_) => {
                unreachable!("Type::ImportNamespace should not reach the emitter's go_type")
            }
            Type::ReceiverPlaceholder => GoType::new("any"),
        }
    }

    fn emit_compound(&self, kind: syntax::types::CompoundKind, args: &[Type], ty: &Type) -> GoType {
        use syntax::types::CompoundKind;

        if kind == CompoundKind::Ref
            && let Some(inner) = args.first()
        {
            let inner_type = self.go_type(inner);
            let mut result = GoType::new(format!("*{}", inner_type.code));
            result.merge(&inner_type);
            return result;
        }

        if let Some(native) = NativeGoType::from_type(ty) {
            return self.emit_native_type(native, ty);
        }

        let param_types: Vec<GoType> = args.iter().map(|p| self.go_type(p)).collect();
        let type_args = param_types
            .iter()
            .map(|t| t.code.as_str())
            .collect::<Vec<_>>()
            .join(", ");

        if kind == CompoundKind::EnumeratedSlice {
            return build_param_typed(format!("[]{}", type_args), &param_types);
        }
        if kind == CompoundKind::VarArgs {
            return build_param_typed(format!("...{}", type_args), &param_types);
        }
        if args.is_empty() {
            return GoType::new(kind.leaf_name().to_string());
        }
        build_param_typed(format!("{}[{}]", kind.leaf_name(), type_args), &param_types)
    }

    /// Render a type to Go text, recording stdlib + Go-import effects in `fx`.
    pub(crate) fn go_type_string(&self, ty: &Type, fx: &mut EmitEffects) -> String {
        let result = self.go_type(ty);
        fx.merge_from_go_type(&result);
        result.code
    }

    pub(crate) fn format_type_args(&mut self, params: &[Type], fx: &mut EmitEffects) -> String {
        if params.is_empty() {
            return String::new();
        }
        let args: Vec<String> = params.iter().map(|p| self.go_type_string(p, fx)).collect();
        format!("[{}]", args.join(", "))
    }

    fn emit_tuple_type(&self, elements: &[Type]) -> GoType {
        let arity = elements.len();
        let element_types: Vec<GoType> = elements.iter().map(|e| self.go_type(e)).collect();

        let mut result = GoType::stdlib(format!(
            "{}.Tuple{}[{}]",
            go_name::GO_STDLIB_PKG,
            arity,
            element_types
                .iter()
                .map(|t| t.code.as_str())
                .collect::<Vec<_>>()
                .join(", ")
        ));
        result.merge_all(&element_types);
        result
    }

    fn emit_constructor(&self, qualified_name: &str, params: &[Type], ty: &Type) -> GoType {
        let name = self.unqualify_name(qualified_name);

        if ty.is_unit() {
            return GoType::new("struct{}");
        }

        if (name == "Ref" || name.ends_with(".Ref"))
            && let Some(inner) = params.first()
        {
            let inner_type = self.go_type(inner);
            let mut result = GoType::new(format!("*{}", inner_type.code));
            result.merge(&inner_type);
            return result;
        }

        if let Some(native) = NativeGoType::from_type(ty) {
            return self.emit_native_type(native, ty);
        }

        let param_types: Vec<GoType> = params.iter().map(|p| self.go_type(p)).collect();
        let type_args = param_types
            .iter()
            .map(|t| t.code.as_str())
            .collect::<Vec<_>>()
            .join(", ");

        if name == "EnumeratedSlice" {
            return build_param_typed(format!("[]{}", type_args), &param_types);
        }
        if name == "VarArgs" {
            return build_param_typed(format!("...{}", type_args), &param_types);
        }

        if let Some(go_path) = self.resolve_go_import_path(qualified_name) {
            let code = if params.is_empty() {
                name.clone()
            } else {
                format!("{}[{}]", name, type_args)
            };
            return build_go_import_typed(code, go_path, &param_types);
        }

        if let Some(name) = qualified_name.strip_prefix(go_name::PRELUDE_PREFIX)
            && let Some(prelude) = PreludeType::from_name(name)
        {
            let type_arg_vec: Vec<String> = param_types.iter().map(|t| t.code.clone()).collect();
            let mut result = GoType::stdlib(prelude.emit_type(&type_arg_vec));
            result.merge_all(&param_types);
            return result;
        }

        if params.is_empty() {
            return GoType::new(name);
        }
        build_param_typed(format!("{}[{}]", name, type_args), &param_types)
    }

    /// Resolve a Go-import path for a nominal constructor: either an explicit
    /// `lisette/go/...` prefix on `qualified_name`, or an implicit one via a
    /// foreign module mapped to a Go import. Returns `None` for prelude,
    /// stdlib, or local nominal types.
    fn resolve_go_import_path(&self, qualified_name: &str) -> Option<String> {
        if let Some(rest) = qualified_name.strip_prefix(go_name::GO_IMPORT_PREFIX)
            && let Some((go_path, _)) = rest.rsplit_once('.')
        {
            return Some(go_path.to_string());
        }
        let (module, _) = qualified_name.split_once('.')?;
        if self.facts.is_foreign_module(module) && !go_name::is_go_import(module) {
            return Some(self.facts.go_import_path(module));
        }
        None
    }

    fn emit_native_type(&self, native: NativeGoType, ty: &Type) -> GoType {
        if !native.has_type_params() {
            return GoType::new(native.emit_type_syntax(&[]));
        }

        let stripped = ty.strip_refs();
        let args = stripped
            .get_type_params()
            .expect("native type with type params must have type args");

        let arg_types: Vec<GoType> = args.iter().map(|a| self.go_type(a)).collect();
        let type_args: Vec<String> = arg_types.iter().map(|t| t.code.clone()).collect();

        build_param_typed(native.emit_type_syntax(&type_args), &arg_types)
    }

    fn emit_function_type(&self, params: &[Type], return_ty: &Type) -> GoType {
        let param_types: Vec<GoType> = params.iter().map(|p| self.go_type(p)).collect();

        let lowered = self.classify_direct_emission(return_ty);
        let return_type = match &lowered {
            Some(shape) => self.lowered_return_go_type(shape, return_ty),
            None => self.go_type(return_ty),
        };

        let args = param_types
            .iter()
            .map(|t| t.code.as_str())
            .collect::<Vec<_>>()
            .join(", ");

        let is_void = lowered.is_none() && (return_ty.is_unit() || return_type.code == "struct{}");

        let code = if is_void {
            format!("func({})", args)
        } else {
            format!("func({}) {}", args, return_type.code)
        };

        let mut result = GoType::new(code);
        result.merge_all(&param_types);
        if !is_void {
            result.merge(&return_type);
        }
        result
    }

    fn unqualify_name(&self, id: &str) -> String {
        if id == "Unknown" {
            return "any".to_string();
        }
        let (module, unqualified) = if let Some(rest) = id.strip_prefix(go_name::GO_IMPORT_PREFIX) {
            let Some((path, ty)) = rest.rsplit_once('.') else {
                return go_name::escape_keyword(id).into_owned();
            };
            (&id[..go_name::GO_IMPORT_PREFIX.len() + path.len()], ty)
        } else {
            let Some(split) = id.split_once('.') else {
                return go_name::escape_keyword(id).into_owned();
            };
            split
        };

        if unqualified == "Unknown" {
            return "any".to_string();
        }

        let escaped = go_name::escape_keyword(unqualified);

        if self.facts.is_foreign_module(module) {
            let pkg = self.go_pkg_qualifier(module);
            format!("{}.{}", pkg, escaped)
        } else {
            escaped.into_owned()
        }
    }

    pub(crate) fn format_type_args_from_annotations(
        &mut self,
        type_args: &[Annotation],
        fx: &mut EmitEffects,
    ) -> String {
        if type_args.is_empty() {
            return String::new();
        }

        let args: Vec<String> = type_args
            .iter()
            .map(|ta| self.annotation_to_go_type(ta, fx))
            .collect();

        format!("[{}]", args.join(", "))
    }

    /// Prepend the receiver's generic params to the explicit type args (for
    /// native-method and UFCS call sites).
    pub(crate) fn format_type_args_with_receiver(
        &mut self,
        receiver_ty: &Type,
        type_args: &[Annotation],
        fx: &mut EmitEffects,
    ) -> String {
        let mut go_type_strs = Vec::new();
        if let Some(params) = receiver_ty.get_type_params() {
            let params = params.to_vec();
            for param in &params {
                go_type_strs.push(self.go_type_string(param, fx));
            }
        }
        for ta in type_args {
            go_type_strs.push(self.annotation_to_go_type(ta, fx));
        }
        if go_type_strs.is_empty() {
            self.format_type_args_from_annotations(type_args, fx)
        } else {
            format!("[{}]", go_type_strs.join(", "))
        }
    }

    pub(crate) fn zero_value(&self, ty: &Type) -> (String, EmitEffects) {
        let mut effects = EmitEffects::default();
        if self.facts.is_interface(ty) {
            return ("nil".to_string(), effects);
        }

        let go_ty = self.go_type(ty);
        effects.merge_from_go_type(&go_ty);

        let value = match go_ty.code.as_str() {
            "int" | "int8" | "int16" | "int32" | "int64" | "uint" | "uint8" | "uint16"
            | "uint32" | "uint64" | "uintptr" | "byte" | "rune" => "0".to_string(),
            "float32" | "float64" => "0.0".to_string(),
            "bool" => "false".to_string(),
            "string" => "\"\"".to_string(),
            "struct{}" => "struct{}{}".to_string(),
            s if s.starts_with("[]")
                || s.starts_with("map[")
                || s.starts_with("chan ")
                || s.starts_with("chan<-")
                || s.starts_with("<-chan")
                || s.starts_with("*")
                || s.starts_with("func") =>
            {
                "nil".to_string()
            }
            _ => format!("*new({})", go_ty.code),
        };
        (value, effects)
    }

    pub(crate) fn annotation_to_go_type(
        &mut self,
        annotation: &Annotation,
        fx: &mut EmitEffects,
    ) -> String {
        let result = self.go_type_from_annotation(annotation);
        fx.merge_from_go_type(&result);
        result.code
    }

    pub(crate) fn go_type_from_annotation(&self, annotation: &Annotation) -> GoType {
        match annotation {
            Annotation::Constructor { .. } if annotation.is_unit() => GoType::new("struct{}"),
            Annotation::Constructor { name, params, .. } => {
                self.constructor_annotation_to_go(name, params)
            }
            Annotation::Function {
                params,
                return_type,
                ..
            } => {
                let param_types: Vec<GoType> = params
                    .iter()
                    .map(|p| self.go_type_from_annotation(p))
                    .collect();

                let lowered = self.classify_annotation_direct_emission(return_type);
                let (return_go_type, is_void) = match &lowered {
                    Some(shape) => (
                        self.lowered_return_go_type_from_annotation(shape, return_type),
                        false,
                    ),
                    None => {
                        let is_omission = matches!(return_type.as_ref(), Annotation::Unknown);
                        let return_go_type = self.go_type_from_annotation(return_type);
                        let is_void = is_omission || return_go_type.code == "struct{}";
                        (return_go_type, is_void)
                    }
                };

                let args = param_types
                    .iter()
                    .map(|t| t.code.as_str())
                    .collect::<Vec<_>>()
                    .join(", ");

                let code = if is_void {
                    format!("func({})", args)
                } else {
                    format!("func({}) {}", args, return_go_type.code)
                };

                let mut result = GoType::new(code);
                result.merge_all(&param_types);
                if !is_void {
                    result.merge(&return_go_type);
                }
                result
            }
            Annotation::Unknown => GoType::new("any"),
            Annotation::Tuple { elements, .. } => {
                let arity = elements.len();
                let element_types: Vec<GoType> = elements
                    .iter()
                    .map(|e| self.go_type_from_annotation(e))
                    .collect();

                let mut result = GoType::stdlib(format!(
                    "{}.Tuple{}[{}]",
                    go_name::GO_STDLIB_PKG,
                    arity,
                    element_types
                        .iter()
                        .map(|t| t.code.as_str())
                        .collect::<Vec<_>>()
                        .join(", ")
                ));
                result.merge_all(&element_types);
                result
            }
            Annotation::Opaque { .. } => {
                unreachable!("Annotation::Opaque should not be emitted as a Go type")
            }
        }
    }

    /// Lower a non-unit `Annotation::Constructor` into its Go equivalent.
    fn constructor_annotation_to_go(&self, name: &str, params: &[Annotation]) -> GoType {
        let base_name = self.unqualify_name(name);

        if let Some(native_type) = NativeGoType::from_name(&base_name) {
            let (param_types, type_params) = self.lower_annotation_params(params);
            let mut result = GoType::new(native_type.emit_type_syntax(&type_params));
            result.merge_all(&param_types);
            return result;
        }

        if base_name == "Ref" && params.len() == 1 {
            let inner = self.go_type_from_annotation(&params[0]);
            let mut result = GoType::new(format!("*{}", inner.code));
            result.merge(&inner);
            return result;
        }

        if let Some(prelude) = PreludeType::from_name(&base_name) {
            let (param_types, type_params) = self.lower_annotation_params(params);
            let mut result = GoType::stdlib(prelude.emit_type(&type_params));
            result.merge_all(&param_types);
            return result;
        }

        let go_import = self.annotation_go_import(name);

        if params.is_empty() {
            return match go_import {
                Some(path) => GoType::with_go_import(base_name, path),
                None => GoType::new(base_name),
            };
        }
        let (param_types, type_params) = self.lower_annotation_params(params);
        let code = format!("{}[{}]", base_name, type_params.join(", "));
        let mut result = match go_import {
            Some(path) => GoType::with_go_import(code, path),
            None => GoType::new(code),
        };
        result.merge_all(&param_types);
        result
    }

    fn annotation_go_import(&self, name: &str) -> Option<String> {
        if let Some(rest) = name.strip_prefix(go_name::GO_IMPORT_PREFIX) {
            return rest.rsplit_once('.').map(|(path, _)| path.to_string());
        }
        let module = self.facts.module_for_qualified_name(name)?;
        if !self.facts.is_foreign_module(module) {
            return None;
        }
        Some(self.go_import_path_for_module(module))
    }

    /// Lower each annotation param to a `GoType`, returning both the full
    /// `GoType` values (needed for import merging) and their code strings
    /// (needed for type-parameter formatting).
    fn lower_annotation_params(&self, params: &[Annotation]) -> (Vec<GoType>, Vec<String>) {
        let param_types: Vec<GoType> = params
            .iter()
            .map(|p| self.go_type_from_annotation(p))
            .collect();
        let type_params: Vec<String> = param_types.iter().map(|t| t.code.clone()).collect();
        (param_types, type_params)
    }
}

fn build_param_typed(code: String, param_types: &[GoType]) -> GoType {
    let mut result = GoType::new(code);
    result.merge_all(param_types);
    result
}

fn build_go_import_typed(code: String, go_path: String, param_types: &[GoType]) -> GoType {
    let mut result = GoType::with_go_import(code, go_path);
    result.merge_all(param_types);
    result
}
