//! Phase 195-196: Pattern 4 (Loop with Continue) Lowerer
//!
//! Phase 195: Initial minimal implementation for single-carrier (sum only)
//! Phase 196: Extended to support multiple carrier variables (sum, count, etc.)
//! Phase 202-C: Unified dual counters into JoinValueSpace
//!
//! Target: apps/tests/loop_continue_pattern4.hako (single carrier)
//!         apps/tests/loop_continue_multi_carrier.hako (multi carrier)
//!
//! Code (multi-carrier):
//! ```nyash
//! static box Main {
//!   main() {
//!     local i = 0
//!     local sum = 0
//!     local count = 0
//!     loop(i < 10) {
//!       i = i + 1
//!       if (i % 2 == 0) {
//!         continue
//!       }
//!       sum = sum + i
//!       count = count + 1
//!     }
//!     print(sum)    // 25
//!     print(count)  // 5
//!     return 0
//!   }
//! }
//! ```
//!
//! Expected output: sum = 25 (1+3+5+7+9), count = 5 (five odd numbers)
//!
//! ## Design Notes (Phase 196)
//!
//! This lowerer now generates JoinIR for N carrier variables dynamically:
//! - Loop function takes (i, carrier1, carrier2, ...) as parameters
//! - Each carrier gets a Select instruction for continue vs normal path
//! - k_exit returns all carriers, ExitMeta maps each to its exit ValueId
//!
//! Key changes from Phase 195:
//! - CarrierInfo passed in to enumerate all carriers
//! - ExitMeta::multiple() instead of ExitMeta::single()
//! - Dynamic parameter/slot allocation based on carrier count
//!
//! ## Phase 202-C: JoinValueSpace Unification
//!
//! Previously used dual counters:
//! - `value_counter`: JoinIR internal ValueIds (0, 1, 2, ...)
//! - `join_value_counter`: Condition-only variables (carrier_count + 1, ...)
//!
//! Now unified via JoinValueSpace:
//! - **Param region (100+)**: ConditionEnv, CarrierInfo parameters
//! - **Local region (1000+)**: Const, BinOp, intermediate values
//!
//! This prevents ValueId collisions after remapping (Phase 201 design).
//!
//! Following the "80/20 rule" from CLAUDE.md - now generalizing after working.

mod carrier_updates;
mod condition_lowering;
mod validation;

use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox;
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
use crate::mir::join_ir::lowering::return_collector::ReturnInfo; // Phase 284 P1
use crate::mir::join_ir::lowering::return_jump_emitter::emit_return_conditional_jump; // Phase 284 P1
use crate::mir::join_ir::{
    BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
    UnaryOp,
};
use crate::mir::ValueId;
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism

use carrier_updates::emit_carrier_updates;
use condition_lowering::lower_header_condition;
use validation::validate_condition_scope;

/// Lower Pattern 4 (Loop with Continue) to JoinIR
///
/// # Phase 195-196: Pure JoinIR Fragment Generation
/// # Phase 202-C: JoinValueSpace Unification
///
/// This version generates JoinIR using **JoinValueSpace-allocated ValueIds**.
/// It has NO knowledge of the host function's ValueId space. The boundary mapping
/// is handled separately via JoinInlineBoundary.
///
/// ## Design Philosophy
///
/// - **Box A**: JoinIR Frontend (doesn't know about host ValueIds)
/// - **Box B**: This function - converts to JoinIR with JoinValueSpace IDs
/// - **Box C**: JoinInlineBoundary - stores boundary info
/// - **Box D**: merge_joinir_mir_blocks - injects Copy instructions
///
/// This clean separation ensures JoinIR lowerers are:
/// - Pure transformers (no side effects)
/// - Reusable (same lowerer works in any context)
/// - Testable (can test JoinIR independently)
///
/// # Arguments
///
/// * `_scope` - LoopScopeShape (reserved for future generic implementation)
/// * `condition` - Loop condition AST node (e.g., `i < end`) (Phase 169)
/// * `_builder` - MirBuilder for variable resolution (Phase 169)
/// * `carrier_info` - Phase 196: Carrier metadata for dynamic multi-carrier support
/// * `carrier_updates` - Phase 197: Update expressions for each carrier variable
/// * `join_value_space` - Phase 202-C: Unified JoinIR ValueId allocator (Local region: 1000+)
/// * `return_info` - Phase 284 P1: Optional return statement info for early exit
///
/// # Returns
///
/// * `Ok((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata
/// * `Err(String)` - Pattern not matched or lowering error
///
/// # Boundary Contract (Phase 196 updated, Phase 202-C: JoinValueSpace)
///
/// This function returns a JoinModule with:
/// - **Input slots**: Local region (1000+) = i_init, carrier values
/// - **Output slots**: k_exit returns all carrier values
/// - **Exit metadata**: ExitMeta containing all carrier bindings
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
///
/// # Phase 284 P1: Return Handling
///
/// If `return_info` is provided, generates:
/// - A `k_return` function that returns the literal value
/// - Conditional jump to k_return when return condition is met
pub(crate) fn lower_loop_with_continue_minimal(
    _scope: LoopScopeShape,
    condition: &ASTNode,
    _builder: &mut MirBuilder,
    carrier_info: &CarrierInfo,
    carrier_updates: &BTreeMap<String, UpdateExpr>, // Phase 222.5-D: HashMap → BTreeMap for determinism
    join_value_space: &mut JoinValueSpace,
    return_info: Option<&ReturnInfo>, // Phase 284 P1
    #[cfg(feature = "normalized_dev")] binding_map: Option<
        &std::collections::BTreeMap<String, crate::mir::BindingId>,
    >,
) -> Result<(JoinModule, ExitMeta), String> {
    // Phase 170-D-impl-3: Validate that loop condition only uses supported variable scopes
    // LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables
    let loop_var_name = carrier_info.loop_var_name.clone();
    let loop_cond_scope = validate_condition_scope(&_scope, condition, &loop_var_name)?;

    let debug = DebugOutputBox::new_dev("joinir/pattern4");
    debug.log(
        "phase170d",
        &format!(
            "Condition variables verified: {:?}",
            loop_cond_scope.var_names()
        ),
    );

    let mut join_module = JoinModule::new();
    let carrier_count = carrier_info.carriers.len();

    debug.log(
        "phase202c",
        &format!(
            "Generating JoinIR for {} carriers: {:?}",
            carrier_count,
            carrier_info
                .carriers
                .iter()
                .map(|c| &c.name)
                .collect::<Vec<_>>()
        ),
    );

    // ==================================================================
    // Function IDs allocation
    // ==================================================================
    let main_id = JoinFuncId::new(0);
    let loop_step_id = JoinFuncId::new(1);
    let k_exit_id = JoinFuncId::new(2);
    // Phase 284 P1: k_return for early return handling
    let k_return_id = JoinFuncId::new(3);

    // ==================================================================
    // ValueId allocation (Phase 202-C: Dynamic based on carrier count)
    // ==================================================================
    // Phase 286 P1: Function parameters MUST use alloc_param() (Param region: 100-999)
    // Local variables use alloc_local() (Local region: 1000+)

    // main() parameters: [i_init, carrier1_init, carrier2_init, ...]
    let i_init = join_value_space.alloc_param();  // PARAM region
    let mut carrier_init_ids: Vec<ValueId> = Vec::new();
    for _ in 0..carrier_count {
        carrier_init_ids.push(join_value_space.alloc_param());  // PARAM region
    }
    let loop_result = join_value_space.alloc_local(); // result from loop_step (LOCAL region)

    // loop_step() parameters: [i_param, carrier1_param, carrier2_param, ...]
    let i_param = join_value_space.alloc_param();  // PARAM region
    let mut carrier_param_ids: Vec<ValueId> = Vec::new();
    for _ in 0..carrier_count {
        carrier_param_ids.push(join_value_space.alloc_param());  // PARAM region
    }

    // k_exit() parameters: [carrier1_exit, carrier2_exit, ...]
    // Phase 286 P1: Function parameters MUST use alloc_param() (PARAM region: 100-999)
    let mut carrier_exit_ids: Vec<ValueId> = Vec::new();
    for _ in 0..carrier_count {
        carrier_exit_ids.push(join_value_space.alloc_param());  // PARAM region
    }

    // Phase 202-C: Build ConditionEnv for condition lowering using JoinValueSpace
    // Loop variable and condition-only variables use Param region (100+)
    use crate::mir::join_ir::lowering::condition_to_joinir::extract_condition_variables;
    let mut env = ConditionEnv::new();

    // Add loop parameter to env (using i_param which is in Local region)
    let loop_var_name = carrier_info.loop_var_name.clone();
    env.insert(loop_var_name.clone(), i_param);

    // Extract and add condition-only variables (Param region)
    let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]);
    let mut condition_var_join_ids = std::collections::BTreeMap::new();
    for var_name in &condition_var_names {
        let join_id = join_value_space.alloc_param();
        env.insert(var_name.clone(), join_id);
        condition_var_join_ids.insert(var_name.clone(), join_id);
    }

    // Phase 80-C (P2): Register BindingIds for condition variables (dev-only)
    #[cfg(feature = "normalized_dev")]
    if let Some(binding_map) = binding_map {
        let debug = DebugOutputBox::new_dev("phase80/p4");

        // Register loop variable BindingId
        if let Some(bid) = binding_map.get(&loop_var_name) {
            env.register_loop_var_binding(*bid, i_param);
            #[cfg(debug_assertions)]
            debug.log(
                "register",
                &format!(
                    "Registered loop var '{}' BindingId({}) -> ValueId({})",
                    loop_var_name, bid.0, i_param.0
                ),
            );
        }

        // Register condition binding BindingIds
        for (var_name, join_id) in &condition_var_join_ids {
            if let Some(bid) = binding_map.get(var_name) {
                env.register_condition_binding(*bid, *join_id);
                #[cfg(debug_assertions)]
                debug.log(
                    "register",
                    &format!(
                        "Registered condition binding '{}' BindingId({}) -> ValueId({})",
                        var_name, bid.0, join_id.0
                    ),
                );
            }
        }
    }

    // Phase 202-C: Create allocator closure AFTER all direct allocations
    // This avoids borrow checker issues
    let mut alloc_value = || join_value_space.alloc_local();

    // Phase 169 / Phase 171-fix / Phase 244: Lower condition using ConditionLoweringBox trait
    let (cond_value, mut cond_instructions) = lower_header_condition(
        condition,
        &env,
        carrier_info,
        &loop_var_name,
        i_param,
        &mut alloc_value,
        None,
        &debug,
    )?;

    // Loop control temporaries
    let exit_cond = alloc_value();
    let const_1 = alloc_value();
    let i_next = alloc_value();
    let const_2 = alloc_value();
    let remainder = alloc_value();
    let const_0 = alloc_value();
    let continue_cond = alloc_value();

    // Per-carrier: next value and merged value (after Select)
    let mut carrier_next_ids: Vec<ValueId> = Vec::new();
    let mut carrier_merged_ids: Vec<ValueId> = Vec::new();
    for _ in 0..carrier_count {
        carrier_next_ids.push(alloc_value()); // carrier_next = carrier + ...
        carrier_merged_ids.push(alloc_value()); // carrier_merged = Select(...)
    }

    // ==================================================================
    // main() function
    // ==================================================================
    // Phase 196: main() takes i and all carriers as parameters
    let mut main_params = vec![i_init];
    main_params.extend(carrier_init_ids.iter().copied());

    let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params.clone());

    // result = loop_step(i_init, carrier1_init, carrier2_init, ...)
    main_func.body.push(JoinInst::Call {
        func: loop_step_id,
        args: main_params.clone(),
        k_next: None,
        dst: Some(loop_result),
    });

    // return result
    main_func.body.push(JoinInst::Ret {
        value: Some(loop_result),
    });

    join_module.add_function(main_func);

    // ==================================================================
    // loop_step(i, carrier1, carrier2, ...) function
    // ==================================================================
    let mut loop_step_params = vec![i_param];
    loop_step_params.extend(carrier_param_ids.iter().copied());

    let mut loop_step_func =
        JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_step_params);

    // ------------------------------------------------------------------
    // Natural Exit Condition Check (Phase 169: from AST)
    // ------------------------------------------------------------------
    // Insert all condition evaluation instructions
    loop_step_func.body.append(&mut cond_instructions);

    // Negate the condition for exit check: exit_cond = !cond_value
    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::UnaryOp {
            dst: exit_cond,
            op: UnaryOp::Not,
            operand: cond_value,
        }));

    // Jump(k_exit, [carrier1, carrier2, ...], cond=exit_cond)
    // Phase 196: Pass ALL carrier params as exit values
    loop_step_func.body.push(JoinInst::Jump {
        cont: k_exit_id.as_cont(),
        args: carrier_param_ids.clone(),
        cond: Some(exit_cond),
    });

    // ------------------------------------------------------------------
    // Loop Body: i_next = i + 1
    // ------------------------------------------------------------------
    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: const_1,
            value: ConstValue::Integer(1),
        }));

    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::BinOp {
            dst: i_next,
            op: BinOpKind::Add,
            lhs: i_param,
            rhs: const_1,
        }));

    // ------------------------------------------------------------------
    // Phase 284 P1: Return Condition Check (if return_info present)
    // ------------------------------------------------------------------
    // For fixture: if (i == 3) { return 7 }
    // Condition is checked against i_next (after increment)
    //
    // Phase 284 P1 Refactor: Use emit_return_conditional_jump SSOT
    emit_return_conditional_jump(
        &mut loop_step_func,
        return_info,
        k_return_id,
        i_next,
        &loop_var_name,
        &mut alloc_value,
        &debug,
    )?;

    // ------------------------------------------------------------------
    // Continue Condition Check: (i_next % 2 == 0)
    // ------------------------------------------------------------------
    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: const_2,
            value: ConstValue::Integer(2),
        }));

    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::BinOp {
            dst: remainder,
            op: BinOpKind::Mod,
            lhs: i_next,
            rhs: const_2,
        }));

    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: const_0,
            value: ConstValue::Integer(0),
        }));

    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Compare {
            dst: continue_cond,
            op: CompareOp::Eq,
            lhs: remainder,
            rhs: const_0,
        }));

    emit_carrier_updates(
        carrier_info,
        carrier_updates,
        &carrier_param_ids,
        &carrier_next_ids,
        &carrier_merged_ids,
        continue_cond,
        const_1,
        i_next,
        &debug,
        &mut loop_step_func.body,
    )?;

    // ------------------------------------------------------------------
    // Tail call: loop_step(i_next, carrier1_merged, carrier2_merged, ...)
    // ------------------------------------------------------------------
    let mut tail_call_args = vec![i_next];
    tail_call_args.extend(carrier_merged_ids.iter().copied());

    loop_step_func.body.push(JoinInst::Call {
        func: loop_step_id,
        args: tail_call_args,
        k_next: None, // CRITICAL: None for tail call
        dst: None,
    });

    join_module.add_function(loop_step_func);

    // ==================================================================
    // k_exit(carrier1_exit, carrier2_exit, ...) function
    // ==================================================================
    let mut k_exit_func =
        JoinFunction::new(k_exit_id, "k_exit".to_string(), carrier_exit_ids.clone());

    // For now, return the first carrier's exit value (or void if no carriers)
    // TODO: Consider returning a tuple or using a different mechanism
    let ret_value = if carrier_exit_ids.is_empty() {
        None
    } else {
        Some(carrier_exit_ids[0])
    };

    k_exit_func.body.push(JoinInst::Ret { value: ret_value });

    join_module.add_function(k_exit_func);

    // ==================================================================
    // Phase 284 P1: k_return() function for early return
    // ==================================================================
    if let Some(ret_info) = return_info {
        let return_value_id = alloc_value();

        let mut k_return_func = JoinFunction::new(k_return_id, "k_return".to_string(), vec![]);

        // Generate: return_value = Const(ret_info.value)
        k_return_func
            .body
            .push(JoinInst::Compute(MirLikeInst::Const {
                dst: return_value_id,
                value: ConstValue::Integer(ret_info.value),
            }));

        // Generate: Ret(return_value)
        k_return_func.body.push(JoinInst::Ret {
            value: Some(return_value_id),
        });

        join_module.add_function(k_return_func);

        debug.log(
            "phase284",
            &format!(
                "Generated k_return function: returns {}",
                ret_info.value
            ),
        );
    }

    // Set entry point
    join_module.entry = Some(main_id);

    debug.log_simple("Phase 202-C: Generated JoinIR for Loop with Continue Pattern");
    debug.log_simple("Functions: main, loop_step, k_exit");
    debug.log_simple("Continue: Select-based skip");
    debug.log_simple("ValueId allocation: JoinValueSpace (Local region 1000+)");
    debug.log(
        "summary",
        &format!(
            "Carriers: {} ({:?})",
            carrier_count,
            carrier_info
                .carriers
                .iter()
                .map(|c| c.name.as_str())
                .collect::<Vec<_>>()
        ),
    );

    // ==================================================================
    // Phase 197-B: Build ExitMeta with carrier_param_ids (Jump arguments)
    // ==================================================================
    // Previously used carrier_exit_ids (k_exit parameters) which are not defined
    // when MIR functions are merged. Now use carrier_param_ids which are the
    // actual values passed to k_exit via Jump instruction.
    //
    // k_exit receives carrier values via: Jump(k_exit, [carrier_param1, carrier_param2, ...])
    // These carrier_param_ids are defined in loop_step and properly remapped.
    let mut exit_values: Vec<(String, ValueId)> = Vec::new();
    for (idx, carrier) in carrier_info.carriers.iter().enumerate() {
        // Phase 197-B: Use carrier_param_ids instead of carrier_exit_ids
        let exit_id = carrier_param_ids[idx];
        exit_values.push((carrier.name.clone(), exit_id));
        debug.log(
            "exit_meta",
            &format!(
                "ExitMeta: {} → ValueId({}) (carrier_param)",
                carrier.name, exit_id.0
            ),
        );
    }

    let exit_meta = ExitMeta::multiple(exit_values);

    debug.log(
        "phase169",
        &format!(
            "ExitMeta total: {} bindings (condition from AST)",
            exit_meta.exit_values.len()
        ),
    );

    Ok((join_module, exit_meta))
}
