use crate::EmitEffects;
use crate::Planner;
use crate::expressions::top_items::emit_doc;
use crate::names::go_name;
use syntax::ast::{Expression, Generic, Pattern, Visibility};
use syntax::types::Type;

struct ImplContext<'a> {
    receiver_name: &'a str,
    ty: &'a Type,
    generics: &'a [Generic],
    qualified_type: String,
}

impl Planner<'_> {
    pub(crate) fn emit_impl_block(
        &mut self,
        receiver_name: &str,
        ty: &Type,
        methods: &[Expression],
        generics: &[Generic],
        fx: &mut EmitEffects,
    ) -> String {
        let ctx = ImplContext {
            receiver_name,
            ty,
            generics,
            qualified_type: self.facts.qualified_current(receiver_name),
        };

        methods
            .iter()
            .filter_map(|method| self.emit_impl_method(method, &ctx, fx))
            .collect::<Vec<_>>()
            .join("\n\n")
    }

    /// Emit one impl method as a receiver method or a UFCS free function.
    fn emit_impl_method(
        &mut self,
        method: &Expression,
        ctx: &ImplContext<'_>,
        fx: &mut EmitEffects,
    ) -> Option<String> {
        let Expression::Function {
            doc,
            visibility,
            name_span,
            ..
        } = method
        else {
            return None;
        };
        if self.facts.is_unused_definition(name_span) {
            return None;
        }

        self.scope.reset_for_top_level();

        let function = method.to_function_definition();
        let is_public = matches!(visibility, Visibility::Public);

        let has_self = function.params.first().is_some_and(|p| {
            matches!(p.pattern, Pattern::Identifier { ref identifier, .. } if identifier == "self")
        });
        let is_ufcs = self
            .facts
            .is_ufcs_method(&ctx.qualified_type, &function.name);
        let should_export = is_public || self.method_needs_export(&function.name);
        let is_free_function = !has_self || is_ufcs;

        let code = if is_free_function {
            let mut free_function = function.clone();
            let method_name = if should_export {
                go_name::snake_to_camel(&function.name)
            } else {
                function.name.to_string()
            };
            free_function.name = format!("{}_{}", ctx.receiver_name, method_name).into();
            let mut combined_generics = ctx.generics.to_vec();
            combined_generics.extend(free_function.generics.iter().cloned());
            free_function.generics = combined_generics;
            self.emit_function(&free_function, None, false, fx)
        } else {
            self.emit_function(
                &function,
                Some((ctx.receiver_name.to_string(), ctx.ty.clone())),
                should_export,
                fx,
            )
        };

        if code.is_empty() {
            return None;
        }
        let method_doc_comment = emit_doc(doc);
        Some(format!("{}{}", method_doc_comment, code))
    }
}
