//! 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 Python handler method with the per-handler contract:
///   def _s_<State>_hdl_<kind>_<event>(self, __e, compartment):
/// Body layout:
///   1. Param binding from __e._parameters (user methods) or
///      compartment.enter_args / compartment.exit_args (lifecycle).
///   2. State-var init preamble (lifecycle enter only — guarded `if not in`).
///   3. Return-init assignment (if handler declares one).
///   4. User-written handler body, expanded with per_handler: true so
///      state-var access targets `compartment.state_vars[…]`.
pub(crate) fn generate_python_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>,
) -> CodegenNode {
    let method_name = handler_method_name(state_name, handler);

    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. State params (declared via `$State(a, b)`) flow
    // through `compartment.state_args` — bind them to named locals at the
    // top of every handler so handler bodies can reference them by name.
    if let Some(sp_names) = state_param_names.get(state_name) {
        for (i, name) in sp_names.iter().enumerate() {
            body.push_str(&format!("{} = compartment.state_args[{}]\n", name, i));
        }
    }

    // Param binding. Lifecycle handlers read from compartment.enter_args /
    // exit_args (set by the transition codegen on the target/source
    // compartment). User-method handlers read from __e._parameters.
    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() {
        body.push_str(&format!("{} = {}[{}]\n", param.name, param_source, i));
    }

    // State-var initialization — only the lifecycle `$>` handler does this.
    // The `if not in` guard preserves pop$ restore semantics.
    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, TargetLanguage::Python3)
            } else {
                state_var_init_value(&var.var_type, TargetLanguage::Python3)
            };
            body.push_str(&format!(
                "if \"{}\" not in compartment.state_vars:\n    compartment.state_vars[\"{}\"] = {}\n",
                var.name, var.name, init_val
            ));
        }
    }

    // Return-init (declared via handler `: Type = default`).
    let return_init_code =
        emit_handler_return_init(handler, TargetLanguage::Python3, "", &ctx.system_name);
    if !return_init_code.is_empty() {
        body.push_str(&return_init_code);
    }

    // User-written handler body. Frame expansion uses ctx.per_handler=true,
    // so state-var access emits compartment.state_vars[…] and HSM forwards
    // emit self._state_Parent(__e, compartment.parent_compartment).
    let body_src =
        emit_handler_body_via_statements(&handler.body_span, source, TargetLanguage::Python3, &ctx);
    body.push_str(&body_src);

    // Empty body placeholder — Python requires a statement.
    if body.trim().is_empty() {
        body.push_str("pass\n");
    }

    CodegenNode::Method {
        name: method_name,
        params: vec![Param::new("__e"), Param::new("compartment")],
        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![],
    }
}
