//! 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 Dart handler method under the per-handler contract.
/// Dart: `final x = …;` param bindings with explicit type casts,
/// `containsKey("x")` init guard, null-safe compartment handling.
pub(crate) fn generate_dart_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::Dart;

    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.
    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 — Dart's
            // `final` can't be redeclared in the same scope.
            if handler_param_names.contains(name.as_str()) {
                continue;
            }
            body.push_str(&format!(
                "final {} = compartment.state_args[{}];\n",
                name, i
            ));
        }
    }

    // Handler-param binding with type cast to keep Dart's strict typing happy.
    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("");
        let dart_type = match type_str {
            "int" | "number" | "num" => "int",
            "double" | "float" => "double",
            "bool" | "boolean" => "bool",
            "String" | "str" | "string" => "String",
            _ => "",
        };
        if dart_type.is_empty() {
            body.push_str(&format!(
                "final {} = {}[{}];\n",
                param.name, param_source, i
            ));
        } else {
            body.push_str(&format!(
                "final {} = {}[{}] as {};\n",
                param.name, param_source, i, dart_type
            ));
        }
    }

    // State-var init (lifecycle enter only).
    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 (!compartment.state_vars.containsKey(\"{}\")) {{\n    compartment.state_vars[\"{}\"] = {};\n}}\n",
                var.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![],
    }
}
