use super::constructors::gen_constructor_with_renames;
use super::instance::gen_method;
use super::static_methods::gen_static_method;
use crate::codegen::generators::{AdapterBodies, RustBindingConfig};
use crate::codegen::shared::partition_methods;
use crate::codegen::type_mapper::TypeMapper;
use crate::core::ir::TypeDef;
use ahash::AHashSet;

/// Generate a full methods impl block (non-opaque types).
pub fn gen_impl_block(
    typ: &TypeDef,
    mapper: &dyn TypeMapper,
    cfg: &RustBindingConfig,
    adapter_bodies: &AdapterBodies,
    opaque_types: &AHashSet<String>,
) -> String {
    gen_impl_block_with_renames(typ, mapper, cfg, adapter_bodies, opaque_types, None)
}

/// Like `gen_impl_block` but with field renames for keyword escaping in the constructor.
pub fn gen_impl_block_with_renames(
    typ: &TypeDef,
    mapper: &dyn TypeMapper,
    cfg: &RustBindingConfig,
    adapter_bodies: &AdapterBodies,
    opaque_types: &AHashSet<String>,
    field_renames: Option<&std::collections::HashMap<String, String>>,
) -> String {
    let (instance, statics) = partition_methods(&typ.methods);
    // Compute effective (non-sanitized or adapter-overridden) method counts for the early-return
    // check. Sanitized methods without adapters are skipped in the loops below, so they do not
    // contribute real content to the impl block.
    let has_emittable_instance = instance
        .iter()
        .any(|m| !m.sanitized || adapter_bodies.contains_key(&format!("{}.{}", typ.name, m.name)));
    let has_emittable_statics = statics
        .iter()
        .any(|m| !m.sanitized || adapter_bodies.contains_key(&format!("{}.{}", typ.name, m.name)));
    if !has_emittable_instance && !has_emittable_statics && typ.fields.is_empty() {
        return String::new();
    }

    let prefixed_name = format!("{}{}", cfg.type_name_prefix, typ.name);
    let mut out = String::with_capacity(2048);

    // Constructor — suppressed when the backend handles construction via a separate free
    // function (e.g. extendr kwargs constructor), when there are no fields, or when the
    // type already provides an explicit static `new()` method (which will be emitted as
    // `#[staticmethod] pub fn new(...)` and would conflict with a `#[new]` constructor).
    let has_explicit_static_new = typ.methods.iter().any(|m| m.is_static && m.name == "new");
    if !typ.fields.is_empty() && !cfg.skip_impl_constructor && !has_explicit_static_new {
        out.push_str(&gen_constructor_with_renames(typ, mapper, cfg, field_renames));
        out.push_str("\n\n");
    }

    // Instance methods
    let empty_mutex_types: AHashSet<String> = AHashSet::new();
    for m in &instance {
        // Skip sanitized methods that have no adapter override — they cannot be delegated
        // and emitting an unimplemented stub pollutes the public API with dead placeholders.
        // Adapter bodies are explicit overrides and always take precedence.
        let adapter_key = format!("{}.{}", typ.name, m.name);
        if m.sanitized && !adapter_bodies.contains_key(&adapter_key) {
            continue;
        }
        if cfg.skip_methods_when_not_delegatable
            && !adapter_bodies.contains_key(&adapter_key)
            && !crate::codegen::shared::can_auto_delegate(m, opaque_types)
        {
            continue;
        }
        out.push_str(&gen_method(
            m,
            mapper,
            cfg,
            typ,
            false,
            opaque_types,
            &empty_mutex_types,
            adapter_bodies,
        ));
        out.push_str("\n\n");
    }

    // Static methods
    for m in &statics {
        // Skip sanitized static methods that have no adapter override.
        let adapter_key = format!("{}.{}", typ.name, m.name);
        if m.sanitized && !adapter_bodies.contains_key(&adapter_key) {
            continue;
        }
        if cfg.skip_methods_when_not_delegatable
            && !adapter_bodies.contains_key(&adapter_key)
            && !crate::codegen::shared::can_auto_delegate(m, opaque_types)
        {
            continue;
        }
        out.push_str(&gen_static_method(
            m,
            mapper,
            cfg,
            typ,
            adapter_bodies,
            opaque_types,
            &empty_mutex_types,
        ));
        out.push_str("\n\n");
    }

    // Trim trailing newlines inside impl block
    let trimmed = out.trim_end();
    let content = trimmed.to_string();

    crate::codegen::template_env::render(
        "generators/methods/impl_block.jinja",
        minijinja::context! {
            block_attr => cfg.method_block_attr,
            prefixed_name => prefixed_name,
            content => content,
        },
    )
}
