use crate::Emitter;
use crate::go::names::go_name;
use syntax::program::Definition;
use syntax::types::Type;

impl Emitter<'_> {
    pub(crate) fn resolve_go_name(&mut self, name: &str) -> String {
        if let Some(go_call) = self.try_resolve_cross_module_static_method(name) {
            return go_call;
        }

        let name = if let Some((type_part, method)) = name.split_once('.')
            && !type_part.contains('.')
            && let Some(real_type) = self.resolve_alias_type_name(type_part)
        {
            format!("{}.{}", real_type, method)
        } else {
            name.to_string()
        };

        let name = if let Some((type_part, _method)) = name.split_once('.')
            && !type_part.contains('.')
            && !name.starts_with(go_name::PRELUDE_PREFIX)
            && self
                .ctx
                .definitions
                .contains_key(format!("{}.{}", go_name::PRELUDE_MODULE, type_part).as_str())
        {
            format!("{}.{}", go_name::PRELUDE_MODULE, name)
        } else {
            name
        };

        let resolved = go_name::resolve(&name);
        if resolved.needs_stdlib {
            self.flags.needs_stdlib = true;
        }
        resolved.name
    }

    pub(crate) fn resolve_alias_type_name(&self, type_part: &str) -> Option<String> {
        let qualified = format!("{}.{}", self.current_module, type_part);
        if let Some(Definition::TypeAlias { ty, .. }) = self.ctx.definitions.get(qualified.as_str())
            && let Type::Constructor { id, .. } = &ty.resolve()
        {
            let type_module = id.split('.').next().unwrap_or("");
            if type_module == self.current_module {
                return Some(id.split('.').next_back()?.to_string());
            }
            return Some(id.to_string());
        }
        None
    }

    pub(crate) fn capitalize_static_method_if_public(&self, name: &str) -> String {
        let Some((type_part, method_part)) = name.split_once('.') else {
            return name.to_string();
        };

        if method_part.contains('.') {
            return name.to_string();
        }

        let method_key = format!("{}.{}.{}", self.current_module, type_part, method_part);
        let found = self.ctx.definitions.get(method_key.as_str()).or_else(|| {
            let real_type = self.resolve_alias_type_name(type_part)?;
            let alias_key = format!("{}.{}.{}", self.current_module, real_type, method_part);
            self.ctx.definitions.get(alias_key.as_str())
        });
        let is_public = if let Some(d) = found {
            d.visibility().is_public() || self.method_needs_export(method_part)
        } else {
            self.method_needs_export(method_part)
        };

        if is_public {
            format!("{}.{}", type_part, go_name::capitalize_first(method_part))
        } else {
            name.to_string()
        }
    }

    pub(crate) fn resolve_alias_to_module<'b>(&'b self, name: &'b str) -> &'b str {
        self.module
            .reverse_module_aliases
            .get(name)
            .map(|s| s.as_str())
            .unwrap_or(name)
    }

    pub(crate) fn require_module_import(&mut self, module: &str) -> String {
        self.ensure_imported
            .insert(format!("{}/{}", self.ctx.go_module, module));
        self.go_pkg_qualifier(module)
    }

    pub(crate) fn go_pkg_qualifier(&self, module: &str) -> String {
        if let Some(alias) = self.module.module_aliases.get(module) {
            return alias.clone();
        }
        let path = module
            .strip_prefix(go_name::GO_IMPORT_PREFIX)
            .unwrap_or(module);
        go_name::go_package_name(path).to_string()
    }

    pub(crate) fn qualify_method_call(
        &mut self,
        type_id: &str,
        method: &str,
        is_public: bool,
    ) -> String {
        let module = type_id.split_once('.').map(|(m, _)| m);
        let computed_alias = match module {
            Some(m) if m != self.current_module && m != go_name::PRELUDE_MODULE => {
                Some(self.go_pkg_qualifier(m))
            }
            _ => None,
        };
        let resolved = go_name::qualify_method(
            type_id,
            method,
            &self.current_module,
            is_public,
            computed_alias.as_deref(),
        );
        if resolved.needs_stdlib {
            self.flags.needs_stdlib = true;
        }
        resolved.name
    }

    pub(crate) fn resolve_variant(&mut self, identifier: &str, enum_id: &str) -> String {
        let enum_module = enum_id.split('.').next().unwrap_or("");
        let computed_alias =
            if enum_module != self.current_module && enum_module != go_name::PRELUDE_MODULE {
                Some(self.go_pkg_qualifier(enum_module))
            } else {
                None
            };
        let resolved = go_name::variant_by_id(
            identifier,
            enum_id,
            &self.current_module,
            computed_alias.as_deref(),
        );
        if resolved.needs_stdlib {
            self.flags.needs_stdlib = true;
        }
        resolved.name
    }
}
