//! 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:
///   void _s_<State>_hdl_<kind>_<event>(SysFrameEvent& __e, std::shared_ptr<SysCompartment> compartment)
/// C++ specifics: `std::any_cast<T>(compartment->state_args[i])` typed cast,
/// `.count("x")` init guard, shared_ptr dereference with `->`.
#[allow(clippy::too_many_arguments)]
pub(crate) fn generate_cpp_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::Cpp;

    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: std::collections::HashMap::new(),
    };

    let mut body = String::new();

    // State-param binding. C++ uses `auto name = std::any_cast<T>(compartment->state_args[i]);`.
    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() {
            // Skip when a handler param shadows a state-arg — C++
            // disallows variable redeclaration in the same scope.
            if handler_param_names.contains(name.as_str()) {
                continue;
            }
            let frame_type = state_param_types
                .get(&(state_name.to_string(), name.clone()))
                .map(|s| s.as_str())
                .unwrap_or("int");
            let cpp_type = cpp_map_type(frame_type);
            // Untyped params land as `std::any`; copy without cast.
            if cpp_type == "std::any" {
                body.push_str(&format!(
                    "auto {} = compartment->state_args[{}];\n",
                    name, i
                ));
            } else {
                body.push_str(&format!(
                    "{} {} = std::any_cast<{}>(compartment->state_args[{}]);\n",
                    cpp_type, name, cpp_type, i
                ));
            }
        }
    }

    // Handler-param binding. When the declared type maps to `std::any`
    // (i.e., untyped params), copy directly without a cast — the same
    // carve-out fmt_unpack applies for the legacy monolithic path.
    let param_source = 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 cpp_type = cpp_map_type(type_str);
        if cpp_type == "std::any" {
            body.push_str(&format!("auto {} = {}[{}];\n", param.name, param_source, i));
        } else {
            body.push_str(&format!(
                "auto {} = std::any_cast<{}>({}[{}]);\n",
                param.name, cpp_type, param_source, i
            ));
        }
    }

    // State-var init (lifecycle enter only). String literals get wrapped
    // in `std::string(...)` so the any holds std::string, not const char* —
    // matches fmt_init_sv's existing carve-out in the legacy path.
    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)
            };
            let wrapped = if init_val.trim().starts_with('"') && init_val.trim().ends_with('"') {
                format!("std::string({})", init_val)
            } else {
                init_val
            };
            body.push_str(&format!(
                "if (compartment->state_vars.count(\"{}\") == 0) {{\n    compartment->state_vars[\"{}\"] = {};\n}}\n",
                var.name, var.name, wrapped
            ));
        }
    }

    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!("std::shared_ptr<{}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![],
    }
}
