//! Per-handler emitter for one target language. Generated by the
//! per-handler dispatch path — `generate_per_handler_method_for_lang`
//! in the parent module selects between these per backend.

use super::super::super::ast::{CodegenNode, Param, Visibility};
use super::super::super::codegen_utils::{
    cpp_map_type, csharp_map_type, expression_to_string, go_map_type, java_map_type,
    kotlin_map_type, state_var_init_value, swift_map_type, to_snake_case, type_to_cpp_string,
    HandlerContext,
};
use super::super::super::frame_expansion::{
    emit_handler_body_via_statements, normalize_indentation,
};
use super::super::{emit_handler_return_init, handler_method_name};
use crate::frame_c::compiler::arcanum::HandlerEntry;
use crate::frame_c::compiler::frame_ast::StateVarAst;
use crate::frame_c::visitors::TargetLanguage;

/// Emit a single C handler method under the per-handler contract:
///   static void Sys__s_<State>_hdl_<kind>_<event>(Sys* self, Sys_FrameEvent* __e, Sys_Compartment* compartment)
/// C specifics: free functions (not class methods), `(T) FrameDict_get(...)`
/// typed cast for state-var access, `->` deref, C backend prefixes
/// `Sys_` and inserts `self` automatically.
#[allow(clippy::too_many_arguments)]
pub(crate) fn generate_c_handler_method(
    system_name: &str,
    state_name: &str,
    parent_state: Option<&str>,
    handler: &HandlerEntry,
    state_vars_for_init: &[StateVarAst],
    source: &[u8],
    _has_state_vars: bool,
    defined_systems: &std::collections::HashSet<String>,
    _sys_param_locals: &[String],
    _is_start_state: bool,
    state_param_names: &std::collections::HashMap<String, Vec<String>>,
    state_enter_param_names: &std::collections::HashMap<String, Vec<String>>,
    state_exit_param_names: &std::collections::HashMap<String, Vec<String>>,
    event_param_names: &std::collections::HashMap<String, Vec<String>>,
    handler_state_var_types: &std::collections::HashMap<String, String>,
    state_hsm_parents: &std::collections::HashMap<String, String>,
    state_param_types: &std::collections::HashMap<(String, String), String>,
) -> CodegenNode {
    let method_name = handler_method_name(state_name, handler);
    let lang = TargetLanguage::C;

    let ctx = HandlerContext {
        system_name: system_name.to_string(),
        state_name: state_name.to_string(),
        event_name: handler.event.clone(),
        parent_state: parent_state.map(|s| s.to_string()),
        defined_systems: defined_systems.clone(),
        use_sv_comp: false,
        per_handler: true,
        state_var_types: handler_state_var_types.clone(),
        state_param_names: state_param_names.clone(),
        state_enter_param_names: state_enter_param_names.clone(),
        state_exit_param_names: state_exit_param_names.clone(),
        event_param_names: event_param_names.clone(),
        state_hsm_parents: state_hsm_parents.clone(),
        current_return_type: handler.return_type.clone(),
        state_param_types: state_param_types.clone(),
    };

    let mut body = String::new();

    // State-param / handler-param binding from a void* slot. Maps Frame
    // types to native C types so `str` → `const char*`, `bool` → `int`,
    // `float`/`double` → `double` (via the runtime's `Sys_unpack_double`
    // bit-pun helper, since `(intptr_t)(0.5)` truncates), etc. Falls
    // back to `int` for unknown types — matches `DispatchSyntax::C`'s
    // `c_param_type_and_cast` helper, extended for floats.
    //
    // Returns (decl_type, full_extract_expr) where the extractor takes
    // the void* value placeholder `{val}`.
    let c_extract = |type_str: &str, val_expr: &str| -> (String, String) {
        let t = type_str.trim();
        match t {
            "str" | "string" | "String" | "char*" | "const char*" => (
                "const char*".to_string(),
                format!("(const char*){}", val_expr),
            ),
            "float" | "f32" | "f64" | "double" => (
                "double".to_string(),
                format!("{}_unpack_double({})", system_name, val_expr),
            ),
            // Frame's `: list` lands as <sys>_FrameVec* (see backends/c.rs
            // convert_type_to_c). State-args/event-args of list type
            // need the typed cast, not the int fallthrough.
            "list" | "List" | "Array" | "Array<any>" => {
                let typ = format!("{}_FrameVec*", system_name);
                let extract = format!("({}){}", typ, val_expr);
                (typ, extract)
            }
            // Same shape for `: dict` → <sys>_FrameDict*.
            "dict" | "Dict" | "Record<string, any>" => {
                let typ = format!("{}_FrameDict*", system_name);
                let extract = format!("({}){}", typ, val_expr);
                (typ, extract)
            }
            _ if t.ends_with('*') => (t.to_string(), format!("({}){}", t, val_expr)),
            _ => ("int".to_string(), format!("(int)(intptr_t){}", val_expr)),
        }
    };
    if let Some(sp_names) = state_param_names.get(state_name) {
        let handler_param_names: std::collections::HashSet<&str> =
            handler.params.iter().map(|p| p.name.as_str()).collect();
        for (i, name) in sp_names.iter().enumerate() {
            // Dynamic languages let the handler-param prefetch reassign
            // an outer `name`; C disallows redeclaration. When a handler
            // param shadows a state-arg, skip the state-arg prefetch and
            // let the param binding take over.
            if handler_param_names.contains(name.as_str()) {
                continue;
            }
            let type_str = state_param_types
                .get(&(state_name.to_string(), name.clone()))
                .map(|s| s.as_str())
                .unwrap_or("int");
            let val_expr = format!(
                "{}_FrameVec_get(compartment->state_args, {})",
                system_name, i
            );
            let (c_type, extract) = c_extract(type_str, &val_expr);
            body.push_str(&format!("{} {} = {};\n", c_type, name, extract));
            body.push_str(&format!("(void){};\n", name));
        }
    }

    // Handler-param binding. Reuse the c_extract helper from above so
    // float/double params route through `Sys_unpack_double` (intptr_t
    // cast truncates floats — same root issue as state-args).
    let param_source_pre = if handler.is_enter {
        "compartment->enter_args"
    } else if handler.is_exit {
        "compartment->exit_args"
    } else {
        "__e->_parameters"
    };
    for (i, param) in handler.params.iter().enumerate() {
        let type_str = param.symbol_type.as_deref().unwrap_or("int");
        let val_expr = format!("{}_FrameVec_get({}, {})", system_name, param_source_pre, i);
        let (c_decl, extract) = c_extract(type_str, &val_expr);
        body.push_str(&format!("{} {} = {};\n", c_decl, param.name, extract));
        body.push_str(&format!("(void){};\n", param.name));
    }

    // State-var init (lifecycle enter only). Uses FrameDict_has + FrameDict_set.
    if handler.is_enter {
        for var in state_vars_for_init {
            let init_val = if let Some(ref init) = var.init {
                expression_to_string(init, lang)
            } else {
                state_var_init_value(&var.var_type, lang)
            };
            body.push_str(&format!(
                "if (!{}_FrameDict_has(compartment->state_vars, \"{}\")) {{\n    {}_FrameDict_set(compartment->state_vars, \"{}\", (void*)(intptr_t)({}));\n}}\n",
                system_name, var.name, system_name, var.name, init_val
            ));
        }
    }

    let return_init_code = emit_handler_return_init(handler, lang, "", &ctx.system_name);
    if !return_init_code.is_empty() {
        body.push_str(&return_init_code);
    }

    let body_src = emit_handler_body_via_statements(&handler.body_span, source, lang, &ctx);
    body.push_str(&body_src);

    let event_type = format!("{}_FrameEvent*", system_name);
    let comp_type = format!("{}_Compartment*", system_name);

    CodegenNode::Method {
        name: method_name,
        params: vec![
            Param::new("__e").with_type(&event_type),
            Param::new("compartment").with_type(&comp_type),
        ],
        return_type: None,
        body: vec![CodegenNode::NativeBlock {
            code: body,
            span: Some(crate::frame_c::compiler::frame_ast::Span {
                start: handler.body_span.start,
                end: handler.body_span.end,
            }),
        }],
        is_async: false,
        is_static: false,
        visibility: Visibility::Private,
        decorators: vec![],
    }
}
