//! Phase 188.3 P1: Pattern 6 (Nested Loop Minimal) JoinIR Lowerer
//!
//! Target: apps/tests/phase1883_nested_minimal.hako
//!
//! Code:
//! ```nyash
//! static box Main {
//!   main() {
//!     local sum = 0
//!     local i = 0
//!     loop(i < 3) {
//!       local j = 0
//!       loop(j < 3) {
//!         sum = sum + 1
//!         j = j + 1
//!       }
//!       i = i + 1
//!     }
//!     return sum
//!   }
//! }
//! ```
//!
//! Expected JoinIR (4 functions):
//! ```text
//! fn main(i0, sum0):
//!   Call(loop_step, [i0, sum0])
//!   Ret 0
//!
//! fn loop_step(i, sum):  // outer loop
//!   exit_cond = !(i < 3)
//!   Jump(k_exit, [sum], cond=exit_cond)
//!   j0 = 0
//!   Call(inner_step, [j0, i, sum])
//!
//! fn inner_step(j, i_outer, sum):  // inner loop (tail recursion)
//!   exit_cond = !(j < 3)
//!   Jump(k_inner_exit, [i_outer, sum], cond=exit_cond)
//!   sum_next = sum + 1
//!   j_next = j + 1
//!   Call(inner_step, [j_next, i_outer, sum_next])
//!
//! fn k_inner_exit(i, sum):  // outer continuation (after inner loop)
//!   i_next = i + 1
//!   Call(loop_step, [i_next, sum])
//!
//! fn k_exit(sum):
//!   Ret sum
//! ```
//!
//! ## Design Notes (Phase 188.3)
//!
//! - **4-function model**: main, loop_step (outer), inner_step, k_inner_exit, k_exit
//! - **Carrier design**: sum is passed as argument (not global)
//! - **Merge control**: continuation_func_ids includes k_exit, k_inner_exit, inner_step
//! - **Pattern1-based**: Both outer and inner are Pattern1 (no break/continue)

use crate::mir::join_ir::lowering::canonical_names as cn;
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::{
    BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
    UnaryOp,
};

/// Canonical names for Pattern 6 continuations
pub const INNER_STEP: &str = "inner_step";
pub const K_INNER_EXIT: &str = "k_inner_exit";

/// Lower Pattern 6 (Nested Loop Minimal) to JoinIR
///
/// # Phase 188.3 P1: Minimal nested loop implementation
///
/// This version generates JoinIR using **JoinValueSpace** for unified ValueId allocation.
/// It uses the Local region (1000+) to avoid collision with Param region (100-999).
///
/// ## 4-Function Model
///
/// - **main(i0, sum0)**: Entry point, calls loop_step
/// - **loop_step(i, sum)**: Outer loop header, initializes & calls inner loop
/// - **inner_step(j, i_outer, sum)**: Inner loop body (tail recursion)
/// - **k_inner_exit(i, sum)**: Outer continuation after inner loop exits
/// - **k_exit(sum)**: Final exit continuation
///
/// # Arguments
///
/// * `_scope` - LoopScopeShape (reserved for future generic implementation)
/// * `join_value_space` - Unified ValueId allocator (Phase 202-A)
///
/// # Returns
///
/// * `Some(JoinModule)` - Successfully lowered to JoinIR
/// * `None` - Pattern not matched (fallback to other lowerers)
///
/// # Boundary Contract
///
/// This function returns a JoinModule with:
/// - **Input slots**: main() params (i0, sum0) for outer loop variables
/// - **Caller responsibility**: Create JoinInlineBoundary to map param ValueIds to host variables
pub(crate) fn lower_nested_loop_minimal(
    _scope: LoopScopeShape,
    join_value_space: &mut JoinValueSpace,
) -> Option<JoinModule> {
    let mut join_module = JoinModule::new();

    // ==================================================================
    // Function IDs allocation
    // ==================================================================
    let main_id = JoinFuncId::new(0);
    let loop_step_id = JoinFuncId::new(1);
    let inner_step_id = JoinFuncId::new(2);
    let k_inner_exit_id = JoinFuncId::new(3);
    let k_exit_id = JoinFuncId::new(4);

    // ==================================================================
    // ValueId allocation - main() function
    // ==================================================================
    let i_main_param = join_value_space.alloc_param(); // i0 (outer loop var)
    let sum_main_param = join_value_space.alloc_param(); // sum0 (carrier)
    let loop_result = join_value_space.alloc_local(); // result from loop_step
    let const_0_main = join_value_space.alloc_local(); // return value

    // ==================================================================
    // ValueId allocation - loop_step (outer loop)
    // ==================================================================
    let i_step_param = join_value_space.alloc_param(); // outer loop var
    let sum_step_param = join_value_space.alloc_param(); // sum carrier
    let const_3 = join_value_space.alloc_local(); // outer limit (3)
    let cmp_i_lt = join_value_space.alloc_local(); // i < 3
    let exit_cond_outer = join_value_space.alloc_local(); // !(i < 3)
    let j0 = join_value_space.alloc_local(); // inner loop init (0)

    // ==================================================================
    // ValueId allocation - inner_step (inner loop)
    // ==================================================================
    let j_inner_param = join_value_space.alloc_param(); // inner loop var
    let i_inner_param = join_value_space.alloc_param(); // outer var (read-only)
    let sum_inner_param = join_value_space.alloc_param(); // sum carrier
    let const_3_inner = join_value_space.alloc_local(); // inner limit (3)
    let cmp_j_lt = join_value_space.alloc_local(); // j < 3
    let exit_cond_inner = join_value_space.alloc_local(); // !(j < 3)
    let const_1_sum = join_value_space.alloc_local(); // increment (1)
    let sum_next = join_value_space.alloc_local(); // sum + 1
    let const_1_j = join_value_space.alloc_local(); // increment (1)
    let j_next = join_value_space.alloc_local(); // j + 1

    // ==================================================================
    // ValueId allocation - k_inner_exit (outer continuation)
    // ==================================================================
    let i_kexit_param = join_value_space.alloc_param(); // outer var
    let sum_kexit_param = join_value_space.alloc_param(); // sum carrier
    let const_1_i = join_value_space.alloc_local(); // increment (1)
    let i_next = join_value_space.alloc_local(); // i + 1

    // ==================================================================
    // ValueId allocation - k_exit (final exit)
    // ==================================================================
    let sum_exit_param = join_value_space.alloc_param(); // sum for return

    // ==================================================================
    // main() function
    // ==================================================================
    let mut main_func =
        JoinFunction::new(main_id, cn::MAIN.to_string(), vec![i_main_param, sum_main_param]);

    // result = loop_step(i_main_param, sum_main_param)
    main_func.body.push(JoinInst::Call {
        func: loop_step_id,
        args: vec![i_main_param, sum_main_param],
        k_next: None,
        dst: Some(loop_result),
    });

    // return 0 (statement position)
    main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
        dst: const_0_main,
        value: ConstValue::Integer(0),
    }));

    main_func.body.push(JoinInst::Ret {
        value: Some(const_0_main),
    });

    join_module.add_function(main_func);

    // ==================================================================
    // loop_step(i, sum) - outer loop header
    // ==================================================================
    let mut loop_step_func = JoinFunction::new(
        loop_step_id,
        cn::LOOP_STEP.to_string(),
        vec![i_step_param, sum_step_param],
    );

    // exit_cond = !(i < 3)
    // Step 1: const 3
    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: const_3,
            value: ConstValue::Integer(3),
        }));

    // Step 2: cmp_i_lt = (i < 3)
    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Compare {
            dst: cmp_i_lt,
            op: CompareOp::Lt,
            lhs: i_step_param,
            rhs: const_3,
        }));

    // Step 3: exit_cond_outer = !cmp_i_lt
    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::UnaryOp {
            dst: exit_cond_outer,
            op: UnaryOp::Not,
            operand: cmp_i_lt,
        }));

    // Jump(k_exit, [sum], cond=exit_cond_outer)
    loop_step_func.body.push(JoinInst::Jump {
        cont: k_exit_id.as_cont(),
        args: vec![sum_step_param],
        cond: Some(exit_cond_outer),
    });

    // j0 = 0 (inner loop initialization)
    loop_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: j0,
            value: ConstValue::Integer(0),
        }));

    // Call(inner_step, [j0, i, sum]) - no continuation (tail call)
    loop_step_func.body.push(JoinInst::Call {
        func: inner_step_id,
        args: vec![j0, i_step_param, sum_step_param],
        k_next: None,
        dst: None,
    });

    join_module.add_function(loop_step_func);

    // ==================================================================
    // inner_step(j, i_outer, sum) - inner loop (tail recursion)
    // ==================================================================
    let mut inner_step_func = JoinFunction::new(
        inner_step_id,
        INNER_STEP.to_string(),
        vec![j_inner_param, i_inner_param, sum_inner_param],
    );

    // exit_cond = !(j < 3)
    // Step 1: const 3
    inner_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: const_3_inner,
            value: ConstValue::Integer(3),
        }));

    // Step 2: cmp_j_lt = (j < 3)
    inner_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Compare {
            dst: cmp_j_lt,
            op: CompareOp::Lt,
            lhs: j_inner_param,
            rhs: const_3_inner,
        }));

    // Step 3: exit_cond_inner = !cmp_j_lt
    inner_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::UnaryOp {
            dst: exit_cond_inner,
            op: UnaryOp::Not,
            operand: cmp_j_lt,
        }));

    // Jump(k_inner_exit, [i_outer, sum], cond=exit_cond_inner)
    inner_step_func.body.push(JoinInst::Jump {
        cont: k_inner_exit_id.as_cont(),
        args: vec![i_inner_param, sum_inner_param],
        cond: Some(exit_cond_inner),
    });

    // sum_next = sum + 1
    // Step 1: const 1
    inner_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: const_1_sum,
            value: ConstValue::Integer(1),
        }));

    // Step 2: sum_next = sum + 1
    inner_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::BinOp {
            dst: sum_next,
            op: BinOpKind::Add,
            lhs: sum_inner_param,
            rhs: const_1_sum,
        }));

    // j_next = j + 1
    // Step 1: const 1
    inner_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: const_1_j,
            value: ConstValue::Integer(1),
        }));

    // Step 2: j_next = j + 1
    inner_step_func
        .body
        .push(JoinInst::Compute(MirLikeInst::BinOp {
            dst: j_next,
            op: BinOpKind::Add,
            lhs: j_inner_param,
            rhs: const_1_j,
        }));

    // Call(inner_step, [j_next, i_outer, sum_next]) - tail recursion
    inner_step_func.body.push(JoinInst::Call {
        func: inner_step_id,
        args: vec![j_next, i_inner_param, sum_next],
        k_next: None,
        dst: None,
    });

    join_module.add_function(inner_step_func);

    // ==================================================================
    // k_inner_exit(i, sum) - outer continuation (after inner loop)
    // ==================================================================
    let mut k_inner_exit_func = JoinFunction::new(
        k_inner_exit_id,
        K_INNER_EXIT.to_string(),
        vec![i_kexit_param, sum_kexit_param],
    );

    // i_next = i + 1
    // Step 1: const 1
    k_inner_exit_func
        .body
        .push(JoinInst::Compute(MirLikeInst::Const {
            dst: const_1_i,
            value: ConstValue::Integer(1),
        }));

    // Step 2: i_next = i + 1
    k_inner_exit_func
        .body
        .push(JoinInst::Compute(MirLikeInst::BinOp {
            dst: i_next,
            op: BinOpKind::Add,
            lhs: i_kexit_param,
            rhs: const_1_i,
        }));

    // Call(loop_step, [i_next, sum]) - tail call to outer loop
    k_inner_exit_func.body.push(JoinInst::Call {
        func: loop_step_id,
        args: vec![i_next, sum_kexit_param],
        k_next: None,
        dst: None,
    });

    join_module.add_function(k_inner_exit_func);

    // ==================================================================
    // k_exit(sum) - final exit continuation
    // ==================================================================
    let mut k_exit_func = JoinFunction::new(k_exit_id, cn::K_EXIT.to_string(), vec![sum_exit_param]);

    // return sum
    k_exit_func.body.push(JoinInst::Ret {
        value: Some(sum_exit_param),
    });

    join_module.add_function(k_exit_func);

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

    eprintln!("[joinir/pattern6] Generated JoinIR for Nested Loop Minimal Pattern");
    eprintln!("[joinir/pattern6] Functions: main, loop_step, inner_step, k_inner_exit, k_exit");

    Some(join_module)
}
