//! P12-S8-B — `Op::SetI` (`R[A][B_imm] := R[C]`) sunk emit. When
//! escape sweep tags `SetISunkWrite { site_idx, key }`, the trace
//! IR `def_var`s `regs[C]` into the matching virt slot Variable
//! (instead of calling the `set_int` heap helper).
//!
//! Combined with S8-A's Move alias tracking, this opens Lua 5.5
//! frontend's `t[k] = v` pattern (lowered as `Move temp=R[t];
//! SetI temp[k]=v`) to sunk emit when the table is a local literal
//! constructor (`local t = {nil, nil}; t[1]=i; ...`).

use luna_jit::version::LuaVersion;

/// `for i=1,N do local t = {nil, nil}; t[1] = i; t[2] = i*2;
/// s = s + t[1] + t[2] end` — the seti_pattern. Pre-S8: Move
/// `temp = t` escapes the site; SetI helper-paths run. Post-S8:
/// Move aliases (S8-A), SetI sunk-emits into virt slots (S8-B),
/// GetI reads back from virt slots (S5-B). Whole loop body runs
/// without touching the heap.
#[test]
fn seti_pattern_sunk_emit_unlocks_full_body() {
    let mut vm = luna_jit::new_with_jit(LuaVersion::Lua54);
    vm.set_jit_enabled(false);
    vm.set_trace_jit_enabled(true);

    let r = vm
        .eval(
            "local s = 0
             for i = 1, 1000 do
                 local t = {nil, nil}
                 t[1] = i
                 t[2] = i * 2
                 s = s + t[1] + t[2]
             end
             return s",
        )
        .unwrap();
    // sum (i + 2i) for i=1..1000 = 3 * 500500 = 1501500.
    assert!(
        matches!(r[0], luna_jit::runtime::Value::Int(1501500)),
        "expected Int(1501500), got {:?}",
        r[0]
    );
    assert!(
        vm.trace_sunk_alloc_count() >= 1,
        "post-S8-A/B, seti_pattern body must take the sunk path; \
         got sunk_alloc_count={}, sinkable_seen={}, compiled={}",
        vm.trace_sunk_alloc_count(),
        vm.trace_sinkable_seen_count(),
        vm.trace_compiled_count(),
    );
}

/// SetI followed by GetI on the same key — the virt slot tracker
/// must propagate the kind via virt_kinds so the GetI emits the
/// right `current_kinds` for downstream use. Loads Int into slot 1,
/// reads it back, uses in Int arith. If kind plumbing is broken,
/// the Add would see RegKind::Unset and bail dispatch.
#[test]
fn seti_then_geti_propagates_kind_through_virt_slot() {
    let mut vm = luna_jit::new_with_jit(LuaVersion::Lua54);
    vm.set_jit_enabled(false);
    vm.set_trace_jit_enabled(true);

    let r = vm
        .eval(
            "local s = 0
             for i = 1, 1000 do
                 local t = {nil}
                 t[1] = i * i
                 s = s + t[1]
             end
             return s",
        )
        .unwrap();
    // sum i^2 for i=1..1000 = 1000 * 1001 * 2001 / 6 = 333833500.
    assert!(
        matches!(r[0], luna_jit::runtime::Value::Int(333833500)),
        "expected Int(333833500), got {:?}",
        r[0]
    );
    assert!(
        vm.trace_sunk_alloc_count() >= 1,
        "SetI sunk emit must keep the site Sinkable; got \
         sunk_alloc_count={}, sinkable_seen={}",
        vm.trace_sunk_alloc_count(),
        vm.trace_sinkable_seen_count(),
    );
}

/// SetI with OOB key (key > array_cap) → must escape the site,
/// not sunk-emit (virt_vars has only `cap` slots). Verifies the
/// escape sweep's `key in 1..=cap` gate.
#[test]
fn seti_oob_key_escapes_site_falls_back_to_helper() {
    let mut vm = luna_jit::new_with_jit(LuaVersion::Lua54);
    vm.set_jit_enabled(false);
    vm.set_trace_jit_enabled(true);

    // local t = {1}  -- cap=1
    // t[1] = i       -- sunk OK (key 1, in cap)
    // t[5] = i * 2   -- OOB (key 5 > cap 1), escapes
    let r = vm
        .eval(
            "local s = 0
             for i = 1, 500 do
                 local t = {1}
                 t[1] = i
                 t[5] = i * 2
                 s = s + t[1] + t[5]
             end
             return s",
        )
        .unwrap();
    // sum (i + 2i) for i=1..500 = 3 * 125250 = 375750.
    assert!(
        matches!(r[0], luna_jit::runtime::Value::Int(375750)),
        "expected Int(375750), got {:?}",
        r[0]
    );
    // The trace may compile (helper path used after escape) but
    // sunk_alloc_count for THIS site should NOT bump (it escapes
    // on the OOB SetI). Other sunk sites elsewhere in the program
    // may still bump.
    let _ = vm.trace_sunk_alloc_count();
}
