use crate::codegen::conversions::helpers::{core_enum_path_remapped, is_tuple_variant};
use crate::core::ir::{EnumDef, EnumVariant};

pub(super) fn gen_from_binding_to_core(enum_def: &EnumDef, core_import: &str) -> String {
    let config = crate::codegen::conversions::ConversionConfig::default();
    let core_path = core_enum_path_remapped(enum_def, core_import, config.source_crate_remaps);
    let binding_name = enum_def.name.as_str();
    let arms: Vec<String> = enum_def
        .variants
        .iter()
        .map(|variant| {
            let pattern = binding_pattern(binding_name, variant);
            let expression = core_expression(variant);
            crate::backends::extendr::template_env::render(
                "enum_from_binding_to_core_arm.jinja",
                minijinja::context! {
                    pattern => &pattern,
                    expression => &expression,
                },
            )
        })
        .collect();

    let catch_all = catch_all(enum_def).then(|| {
        crate::backends::extendr::template_env::render(
            "enum_from_binding_to_core_catch_all.jinja",
            minijinja::context! {},
        )
    });

    crate::backends::extendr::template_env::render(
        "enum_from_binding_to_core_impl.jinja",
        minijinja::context! {
            binding_name => binding_name,
            core_path => core_path,
            arms => arms,
            catch_all => catch_all,
        },
    )
}

pub(super) fn gen_from_core_to_binding(enum_def: &EnumDef, core_import: &str) -> String {
    let config = crate::codegen::conversions::ConversionConfig::default();
    let core_path = core_enum_path_remapped(enum_def, core_import, config.source_crate_remaps);
    let binding_name = enum_def.name.as_str();
    let arms: Vec<String> = enum_def
        .variants
        .iter()
        .map(|variant| {
            let pattern = core_pattern(&core_path, variant);
            let expression = binding_expression(variant);
            crate::backends::extendr::template_env::render(
                "enum_from_core_to_binding_arm.jinja",
                minijinja::context! {
                    pattern => &pattern,
                    expression => &expression,
                },
            )
        })
        .collect();

    let catch_all = catch_all(enum_def).then(|| {
        crate::backends::extendr::template_env::render(
            "enum_from_core_to_binding_catch_all.jinja",
            minijinja::context! {},
        )
    });

    crate::backends::extendr::template_env::render(
        "enum_from_core_to_binding_impl.jinja",
        minijinja::context! {
            binding_name => binding_name,
            core_path => core_path,
            arms => arms,
            catch_all => catch_all,
        },
    )
}

fn catch_all(enum_def: &EnumDef) -> bool {
    // Always emit catch-all for binding→core conversion to handle forward compatibility
    // and cfg-gated variants. For core→binding, this ensures robustness against future
    // enum extensions.
    let has_excluded_variants = !enum_def.excluded_variants.is_empty();
    let core_has_struct_variants = enum_def
        .variants
        .iter()
        .any(|variant| !variant.fields.is_empty() && !variant.is_tuple);
    let has_any_data_variants = enum_def.variants.iter().any(|v| !v.fields.is_empty());

    // Add catch-all if: excluded variants, struct variants, or any data variants at all
    has_excluded_variants || core_has_struct_variants || has_any_data_variants
}

fn binding_pattern(binding_name: &str, variant: &EnumVariant) -> String {
    format!("{binding_name}::{}", variant.name)
}

fn core_pattern(core_path: &str, variant: &EnumVariant) -> String {
    if variant.fields.is_empty() {
        format!("{core_path}::{}", variant.name)
    } else if is_tuple_variant(&variant.fields) {
        format!("{core_path}::{}(..)", variant.name)
    } else {
        format!("{core_path}::{} {{ .. }}", variant.name)
    }
}

fn core_expression(variant: &EnumVariant) -> String {
    if variant.fields.is_empty() {
        format!("Self::{}", variant.name)
    } else if is_tuple_variant(&variant.fields) {
        let defaults = variant
            .fields
            .iter()
            .map(|_| "Default::default()")
            .collect::<Vec<_>>()
            .join(", ");
        format!("Self::{}({defaults})", variant.name)
    } else {
        let defaults = variant
            .fields
            .iter()
            .map(|field| format!("{}: Default::default()", field.name))
            .collect::<Vec<_>>()
            .join(", ");
        format!("Self::{} {{ {defaults} }}", variant.name)
    }
}

fn binding_expression(variant: &EnumVariant) -> String {
    format!("Self::{}", variant.name)
}
