use std::fmt;

use crate::types::Type;

impl Type {
    pub fn stringify(&self) -> String {
        match self {
            Type::Nominal {
                id, params: args, ..
            } => {
                let args_formatted = args
                    .iter()
                    .map(|a| a.stringify())
                    .collect::<Vec<_>>()
                    .join(", ");

                let name = id.last_segment();

                if name == "Unit" {
                    return "()".to_string();
                }

                if name == "bool" {
                    return "bool".to_string();
                }

                if name.starts_with("Tuple") {
                    return format!("({})", args_formatted);
                }

                if name == "Ref" {
                    return format!("Ref<{}>", args_formatted);
                }

                if args.is_empty() {
                    return name.to_string();
                }

                format!("{}<{}>", name, args_formatted)
            }

            Type::Var { id, hint } => match hint {
                Some(name) => format!("?{}", name),
                None => format!("?{}", id.as_u32()),
            },

            Type::Function(f) => {
                let args_formatted = f
                    .params
                    .iter()
                    .enumerate()
                    .map(|(i, a)| {
                        let is_mut = f.param_mutability.get(i).copied().unwrap_or(false);
                        if is_mut {
                            format!("mut {}", a.stringify())
                        } else {
                            a.stringify()
                        }
                    })
                    .collect::<Vec<_>>()
                    .join(", ");

                let ret_formatted = f.return_type.stringify();

                format!("fn ({}) -> {}", args_formatted, ret_formatted)
            }

            Type::Forall { .. } => {
                unreachable!("Forall types are always instantiated before display")
            }

            Type::Parameter(name) => name.to_string(),

            Type::Never => "Never".to_string(),

            Type::Tuple(elements) => {
                let formatted = elements
                    .iter()
                    .map(|e| e.stringify())
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("({})", formatted)
            }

            Type::Error => "<error>".to_string(),

            Type::ImportNamespace(module_id) => {
                let path = module_id.strip_prefix("go:").unwrap_or(module_id);
                path.rsplit('/').next().unwrap_or(module_id).to_string()
            }

            Type::ReceiverPlaceholder => "self".to_string(),

            Type::Simple(kind) => match kind {
                crate::types::SimpleKind::Unit => "()".to_string(),
                _ => kind.leaf_name().to_string(),
            },

            Type::Compound { kind, args } => {
                let args_formatted = args
                    .iter()
                    .map(|a| a.stringify())
                    .collect::<Vec<_>>()
                    .join(", ");
                if args.is_empty() {
                    kind.leaf_name().to_string()
                } else {
                    format!("{}<{}>", kind.leaf_name(), args_formatted)
                }
            }
        }
    }
}

impl fmt::Display for Type {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let (types, _generics) = Self::remove_vars(&[self]);
        write!(f, "{}", types[0].stringify())
    }
}
