//! Phase 29z P0: RC insertion minimal selfcheck (opt-in)
//!
//! This binary constructs a tiny synthetic MIR module containing Store overwrites
//! and asserts that the RC insertion pass inserts ReleaseStrong in the expected place.
//!
//! Build/run (opt-in):
//! - `cargo run --bin rc_insertion_selfcheck --features rc-insertion-minimal`

use nyash_rust::ast::Span;
use nyash_rust::mir::passes::rc_insertion::insert_rc_instructions;
use nyash_rust::mir::{
    BasicBlock, BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule,
    MirType, ValueId,
};
use nyash_rust::mir::types::ConstValue;
use std::collections::HashMap;

fn build_module_with_block(block: BasicBlock, func_name: &str, mod_name: &str) -> MirModule {
    let signature = FunctionSignature {
        name: func_name.to_string(),
        params: vec![],
        return_type: MirType::Void,
        effects: EffectMask::PURE,
    };
    let entry = block.id;
    let mut func = MirFunction::new(signature, entry);
    func.blocks = HashMap::from([(entry, block)]);

    let mut module = MirModule::new(mod_name.to_string());
    module.add_function(func);
    module
}

fn build_module_with_blocks(
    blocks: Vec<BasicBlock>,
    entry: BasicBlockId,
    func_name: &str,
    mod_name: &str,
) -> MirModule {
    let signature = FunctionSignature {
        name: func_name.to_string(),
        params: vec![],
        return_type: MirType::Void,
        effects: EffectMask::PURE,
    };
    let mut func = MirFunction::new(signature, entry);
    func.blocks = blocks.into_iter().map(|b| (b.id, b)).collect();

    let mut module = MirModule::new(mod_name.to_string());
    module.add_function(func);
    module
}

fn assert_release_inserted(
    mut module: MirModule,
    func_name: &str,
    entry: BasicBlockId,
    expected_release: usize,
    label: &str,
) {
    let stats = insert_rc_instructions(&mut module);
    if stats.release_inserted != expected_release {
        eprintln!(
            "[FAIL] {}: expected release_inserted={}, got {}",
            label, expected_release, stats.release_inserted
        );
        std::process::exit(1);
    }

    let func = module
        .get_function(func_name)
        .expect("selfcheck function exists");
    let bb = func.blocks.get(&entry).expect("entry block exists");
    let release_count = bb
        .instructions
        .iter()
        .filter(|inst| matches!(inst, MirInstruction::ReleaseStrong { .. }))
        .count();
    if release_count != expected_release {
        eprintln!(
            "[FAIL] {}: expected ReleaseStrong count={}, got {}",
            label, expected_release, release_count
        );
        std::process::exit(1);
    }
    if bb.instructions.len() != bb.instruction_spans.len() {
        eprintln!(
            "[FAIL] {}: span count mismatch: insts={}, spans={}",
            label,
            bb.instructions.len(),
            bb.instruction_spans.len()
        );
        std::process::exit(1);
    }
}

fn assert_release_counts_in_blocks(
    mut module: MirModule,
    func_name: &str,
    expected_release: usize,
    block_expectations: &[(BasicBlockId, usize)],
    label: &str,
) {
    let stats = insert_rc_instructions(&mut module);
    if stats.release_inserted != expected_release {
        eprintln!(
            "[FAIL] {}: expected release_inserted={}, got {}",
            label, expected_release, stats.release_inserted
        );
        std::process::exit(1);
    }

    let func = module
        .get_function(func_name)
        .expect("selfcheck function exists");

    for (bid, expected_count) in block_expectations {
        let bb = func.blocks.get(bid).expect("block exists");
        let release_count = bb
            .instructions
            .iter()
            .filter(|inst| matches!(inst, MirInstruction::ReleaseStrong { .. }))
            .count();
        if release_count != *expected_count {
            eprintln!(
                "[FAIL] {}: block {:?} expected ReleaseStrong count={}, got {}",
                label, bid, expected_count, release_count
            );
            std::process::exit(1);
        }
        if bb.instructions.len() != bb.instruction_spans.len() {
            eprintln!(
                "[FAIL] {}: block {:?} span count mismatch: insts={}, spans={}",
                label,
                bid,
                bb.instructions.len(),
                bb.instruction_spans.len()
            );
            std::process::exit(1);
        }
    }
}

fn main() {
    // Case 1: Overwrite release (Store -> Store)
    let ptr = ValueId::new(100);
    let v1 = ValueId::new(1);
    let v2 = ValueId::new(2);

    let mut block = BasicBlock::new(BasicBlockId::new(0));
    block.instructions = vec![
        MirInstruction::Store { value: v1, ptr },
        MirInstruction::Store { value: v2, ptr },
    ];
    block.instruction_spans = vec![Span::unknown()];
    let entry = block.id;
    let module = build_module_with_block(block, "selfcheck_overwrite", "selfcheck_mod1");
    assert_release_inserted(module, "selfcheck_overwrite", entry, 1, "overwrite");

    // Case 2: Explicit drop (Store -> Const null -> Store null)
    let ptr = ValueId::new(200);
    let v1 = ValueId::new(10);
    let null_v = ValueId::new(11);

    let mut block = BasicBlock::new(BasicBlockId::new(0));
    block.instructions = vec![
        MirInstruction::Store { value: v1, ptr },
        MirInstruction::Const {
            dst: null_v,
            value: ConstValue::Null,
        },
        MirInstruction::Store {
            value: null_v,
            ptr,
        },
    ];
    block.instruction_spans = vec![Span::unknown()];
    let entry = block.id;
    let module = build_module_with_block(block, "selfcheck_drop", "selfcheck_mod2");
    assert_release_inserted(module, "selfcheck_drop", entry, 1, "explicit_drop");

    // Case 3: Block-end cleanup on Return (remaining tracked values)
    let ptr = ValueId::new(300);
    let v1 = ValueId::new(20);

    let mut block = BasicBlock::new(BasicBlockId::new(0));
    block.instructions = vec![MirInstruction::Store { value: v1, ptr }];
    block.instruction_spans = vec![Span::unknown()];
    block.terminator = Some(MirInstruction::Return { value: None });
    block.terminator_span = Some(Span::unknown());
    let entry = block.id;
    let module = build_module_with_block(block, "selfcheck_return_cleanup", "selfcheck_mod3");
    assert_release_inserted(
        module,
        "selfcheck_return_cleanup",
        entry,
        1,
        "return_cleanup",
    );

    // Case 3.5: Jump -> Return single-predecessor propagation
    let ptr = ValueId::new(350);
    let v1 = ValueId::new(21);

    let mut block_a = BasicBlock::new(BasicBlockId::new(0));
    block_a.instructions = vec![MirInstruction::Store { value: v1, ptr }];
    block_a.instruction_spans = vec![Span::unknown()];
    block_a.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(1),
        edge_args: None,
    });
    block_a.terminator_span = Some(Span::unknown());

    let mut block_b = BasicBlock::new(BasicBlockId::new(1));
    block_b.instructions = vec![];
    block_b.instruction_spans = vec![];
    block_b.terminator = Some(MirInstruction::Return { value: None });
    block_b.terminator_span = Some(Span::unknown());

    let module = build_module_with_blocks(
        vec![block_a, block_b],
        BasicBlockId::new(0),
        "selfcheck_jump_return_single_pred",
        "selfcheck_mod3p5",
    );
    assert_release_counts_in_blocks(
        module,
        "selfcheck_jump_return_single_pred",
        1,
        &[
            (BasicBlockId::new(0), 0),
            (BasicBlockId::new(1), 1),
        ],
        "jump_return_single_pred",
    );

    // Case 3.6: Jump chain -> Return (single-predecessor only)
    let ptr = ValueId::new(360);
    let v1 = ValueId::new(22);

    let mut block_a = BasicBlock::new(BasicBlockId::new(0));
    block_a.instructions = vec![MirInstruction::Store { value: v1, ptr }];
    block_a.instruction_spans = vec![Span::unknown()];
    block_a.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(1),
        edge_args: None,
    });
    block_a.terminator_span = Some(Span::unknown());

    let mut block_b = BasicBlock::new(BasicBlockId::new(1));
    block_b.instructions = vec![];
    block_b.instruction_spans = vec![];
    block_b.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_b.terminator_span = Some(Span::unknown());

    let mut block_c = BasicBlock::new(BasicBlockId::new(2));
    block_c.instructions = vec![];
    block_c.instruction_spans = vec![];
    block_c.terminator = Some(MirInstruction::Return { value: None });
    block_c.terminator_span = Some(Span::unknown());

    let module = build_module_with_blocks(
        vec![block_a, block_b, block_c],
        BasicBlockId::new(0),
        "selfcheck_jump_chain_single_pred",
        "selfcheck_mod3p6",
    );
    assert_release_counts_in_blocks(
        module,
        "selfcheck_jump_chain_single_pred",
        1,
        &[
            (BasicBlockId::new(0), 0),
            (BasicBlockId::new(1), 0),
            (BasicBlockId::new(2), 1),
        ],
        "jump_chain_single_pred",
    );

    // Case 3.7: Multi-predecessor Return with MATCHING states
    // P5: すべての incoming end_state が完全一致する場合のみ ReturnCleanup を成立させる
    let ptr = ValueId::new(370);
    let v_shared = ValueId::new(23); // 同じValueIdを両predecessorで使用

    let mut block_a = BasicBlock::new(BasicBlockId::new(0));
    block_a.instructions = vec![MirInstruction::Store { value: v_shared, ptr }];
    block_a.instruction_spans = vec![Span::unknown()];
    block_a.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_a.terminator_span = Some(Span::unknown());

    let mut block_b = BasicBlock::new(BasicBlockId::new(1));
    block_b.instructions = vec![MirInstruction::Store { value: v_shared, ptr }];
    block_b.instruction_spans = vec![Span::unknown()];
    block_b.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_b.terminator_span = Some(Span::unknown());

    let mut block_ret = BasicBlock::new(BasicBlockId::new(2));
    block_ret.instructions = vec![];
    block_ret.instruction_spans = vec![];
    block_ret.terminator = Some(MirInstruction::Return { value: None });
    block_ret.terminator_span = Some(Span::unknown());

    let module = build_module_with_blocks(
        vec![block_a, block_b, block_ret],
        BasicBlockId::new(0),
        "selfcheck_multi_pred_return_match",
        "selfcheck_mod3p7",
    );
    assert_release_counts_in_blocks(
        module,
        "selfcheck_multi_pred_return_match",
        1, // 全体で1（Return blockのみ）
        &[
            (BasicBlockId::new(0), 0), // Jump block: cleanup禁止
            (BasicBlockId::new(1), 0), // Jump block: cleanup禁止
            (BasicBlockId::new(2), 1), // Return block: multi-pred join成功
        ],
        "multi_pred_return_match",
    );

    // Case 3.8: Multi-predecessor Return with MISMATCHING states (negative test)
    // P5: state不一致 → join state を作らない → ReturnCleanup なし
    let ptr = ValueId::new(380);
    let v1 = ValueId::new(24);
    let v2 = ValueId::new(25); // 異なるValueId → state不一致

    let mut block_a = BasicBlock::new(BasicBlockId::new(0));
    block_a.instructions = vec![MirInstruction::Store { value: v1, ptr }];
    block_a.instruction_spans = vec![Span::unknown()];
    block_a.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_a.terminator_span = Some(Span::unknown());

    let mut block_b = BasicBlock::new(BasicBlockId::new(1));
    block_b.instructions = vec![MirInstruction::Store { value: v2, ptr }]; // v2 != v1 → 不一致
    block_b.instruction_spans = vec![Span::unknown()];
    block_b.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_b.terminator_span = Some(Span::unknown());

    let mut block_ret = BasicBlock::new(BasicBlockId::new(2));
    block_ret.instructions = vec![];
    block_ret.instruction_spans = vec![];
    block_ret.terminator = Some(MirInstruction::Return { value: None });
    block_ret.terminator_span = Some(Span::unknown());

    let module = build_module_with_blocks(
        vec![block_a, block_b, block_ret],
        BasicBlockId::new(0),
        "selfcheck_multi_pred_return_mismatch",
        "selfcheck_mod3p8",
    );
    assert_release_counts_in_blocks(
        module,
        "selfcheck_multi_pred_return_mismatch",
        0, // 全体で0（join stateを作らないのでcleanupなし）
        &[
            (BasicBlockId::new(0), 0), // Jump block: cleanup禁止
            (BasicBlockId::new(1), 0), // Jump block: cleanup禁止
            (BasicBlockId::new(2), 0), // Return block: state不一致 → join失敗
        ],
        "multi_pred_return_mismatch",
    );

    // Case 3.9: Multi-predecessor Return with PARTIAL MATCH (intersection)
    // P6: ptr1=v1 は一致、ptr2 は値が違う → intersection = {ptr1=v1} のみ cleanup
    let ptr1 = ValueId::new(391);
    let ptr2 = ValueId::new(392);
    let v1 = ValueId::new(26);
    let v2 = ValueId::new(27);
    let v3 = ValueId::new(28);

    let mut block_a = BasicBlock::new(BasicBlockId::new(0));
    block_a.instructions = vec![
        MirInstruction::Store { value: v1, ptr: ptr1 },
        MirInstruction::Store { value: v2, ptr: ptr2 },
    ];
    block_a.instruction_spans = vec![Span::unknown(), Span::unknown()];
    block_a.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_a.terminator_span = Some(Span::unknown());

    let mut block_b = BasicBlock::new(BasicBlockId::new(1));
    block_b.instructions = vec![
        MirInstruction::Store { value: v1, ptr: ptr1 }, // ptr1=v1 (一致)
        MirInstruction::Store { value: v3, ptr: ptr2 }, // ptr2=v3 (不一致)
    ];
    block_b.instruction_spans = vec![Span::unknown(), Span::unknown()];
    block_b.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_b.terminator_span = Some(Span::unknown());

    let mut block_ret = BasicBlock::new(BasicBlockId::new(2));
    block_ret.instructions = vec![];
    block_ret.instruction_spans = vec![];
    block_ret.terminator = Some(MirInstruction::Return { value: None });
    block_ret.terminator_span = Some(Span::unknown());

    let module = build_module_with_blocks(
        vec![block_a, block_b, block_ret],
        BasicBlockId::new(0),
        "selfcheck_intersection_partial",
        "selfcheck_mod3p9",
    );
    assert_release_counts_in_blocks(
        module,
        "selfcheck_intersection_partial",
        1, // 全体で1（intersection = v1 のみ）
        &[
            (BasicBlockId::new(0), 0), // Jump block: cleanup禁止
            (BasicBlockId::new(1), 0), // Jump block: cleanup禁止
            (BasicBlockId::new(2), 1), // Return block: intersection で cleanup
        ],
        "intersection_partial",
    );

    // Case 3.10: Multi-predecessor Return with EMPTY intersection
    // P6: 同じ ptr だが value が違う → intersection が空 → cleanup なし
    let ptr = ValueId::new(3100);
    let v1 = ValueId::new(29);
    let v2 = ValueId::new(30);

    let mut block_a = BasicBlock::new(BasicBlockId::new(0));
    block_a.instructions = vec![MirInstruction::Store { value: v1, ptr }];
    block_a.instruction_spans = vec![Span::unknown()];
    block_a.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_a.terminator_span = Some(Span::unknown());

    let mut block_b = BasicBlock::new(BasicBlockId::new(1));
    block_b.instructions = vec![MirInstruction::Store { value: v2, ptr }]; // 同じ ptr、違う value
    block_b.instruction_spans = vec![Span::unknown()];
    block_b.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(2),
        edge_args: None,
    });
    block_b.terminator_span = Some(Span::unknown());

    let mut block_ret = BasicBlock::new(BasicBlockId::new(2));
    block_ret.instructions = vec![];
    block_ret.instruction_spans = vec![];
    block_ret.terminator = Some(MirInstruction::Return { value: None });
    block_ret.terminator_span = Some(Span::unknown());

    let module = build_module_with_blocks(
        vec![block_a, block_b, block_ret],
        BasicBlockId::new(0),
        "selfcheck_intersection_empty",
        "selfcheck_mod3p10",
    );
    assert_release_counts_in_blocks(
        module,
        "selfcheck_intersection_empty",
        0, // 全体で0（intersection が空）
        &[
            (BasicBlockId::new(0), 0), // Jump block: cleanup禁止
            (BasicBlockId::new(1), 0), // Jump block: cleanup禁止
            (BasicBlockId::new(2), 0), // Return block: intersection 空 → cleanup なし
        ],
        "intersection_empty",
    );

    // Case 4: Jump terminator should NOT inject block-end cleanup (unsafe cross-block)
    let ptr = ValueId::new(400);
    let v1 = ValueId::new(30);

    let mut block = BasicBlock::new(BasicBlockId::new(0));
    block.instructions = vec![MirInstruction::Store { value: v1, ptr }];
    block.instruction_spans = vec![Span::unknown()];
    block.terminator = Some(MirInstruction::Jump {
        target: BasicBlockId::new(1),
        edge_args: None,
    });
    block.terminator_span = Some(Span::unknown());
    let entry = block.id;
    let module = build_module_with_block(block, "selfcheck_jump_skip", "selfcheck_mod4");
    assert_release_inserted(
        module,
        "selfcheck_jump_skip",
        entry,
        0,
        "jump_no_cleanup",
    );

    // Case 5: Branch terminator should NOT inject block-end cleanup (unsafe cross-block)
    let ptr = ValueId::new(500);
    let v1 = ValueId::new(40);
    let cond = ValueId::new(41);

    let mut block = BasicBlock::new(BasicBlockId::new(0));
    block.instructions = vec![MirInstruction::Store { value: v1, ptr }];
    block.instruction_spans = vec![Span::unknown()];
    block.terminator = Some(MirInstruction::Branch {
        condition: cond,
        then_bb: BasicBlockId::new(1),
        else_bb: BasicBlockId::new(2),
        then_edge_args: None,
        else_edge_args: None,
    });
    block.terminator_span = Some(Span::unknown());
    let entry = block.id;
    let module = build_module_with_block(block, "selfcheck_branch_skip", "selfcheck_mod5");
    assert_release_inserted(
        module,
        "selfcheck_branch_skip",
        entry,
        0,
        "branch_no_cleanup",
    );

    // Case 3.11: ReleaseStrong values are sorted (deterministic ordering)
    // P7: 複数 ptr に異なる順序で store し、ReturnCleanup の values が昇順になることを検証
    let ptr1 = ValueId::new(3111);
    let ptr2 = ValueId::new(3112);
    let v_high = ValueId::new(100); // 大きい index
    let v_low = ValueId::new(50);   // 小さい index

    let mut block = BasicBlock::new(BasicBlockId::new(0));
    block.instructions = vec![
        MirInstruction::Store { value: v_high, ptr: ptr1 }, // 先に v100 を store
        MirInstruction::Store { value: v_low, ptr: ptr2 },  // 後に v50 を store
    ];
    block.instruction_spans = vec![Span::unknown(), Span::unknown()];
    block.terminator = Some(MirInstruction::Return { value: None });
    block.terminator_span = Some(Span::unknown());
    let entry = block.id;
    let module = build_module_with_block(block, "selfcheck_sorted_values", "selfcheck_mod3p11");

    // 関数全体の ReleaseStrong が昇順であることを検証
    assert_release_inserted(module.clone(), "selfcheck_sorted_values", entry, 1, "sorted_values_count");
    assert_all_release_values_sorted(module, "selfcheck_sorted_values", "sorted_values_order");

    // Case 3.12: Null propagation across Jump-chain
    // P8: null_val が Block A から Block B に伝播され、Block B で Store null_val が explicit drop として認識される
    // Block A: Store real_val → ptr1, Const null_val = Null, Store null_val → ptr1 (explicit drop)
    // Block B (Jump from A): Store real_val → ptr2, Store null_val → ptr2
    // → null_val は A から B に伝播
    // → Block B で ptr2 に null_val を Store すると explicit drop 扱い
    //
    // 期待:
    //   - Block A: ReleaseStrong 1 個（Store null_val → ptr1 の explicit drop で old(real_val) を release）
    //   - Block B: ReleaseStrong 1 個（Store null_val → ptr2 の explicit drop で old(real_val) を release）
    //   - ReturnCleanup: 0 個（ptr2 は null store で消えている）
    //   - 合計: 2 個
    //   - ❌ 失敗時: B=2（ReturnCleanup が残る → null 伝播が効いていない）
    let ptr1 = ValueId::new(3120);
    let ptr2 = ValueId::new(3121);
    let null_val = ValueId::new(3122);
    let real_val = ValueId::new(3123);

    let block_a_id = BasicBlockId::new(0);
    let block_b_id = BasicBlockId::new(1);

    // Block A
    let mut block_a = BasicBlock::new(block_a_id);
    block_a.instructions = vec![
        MirInstruction::Store { value: real_val, ptr: ptr1 },
        MirInstruction::Const { dst: null_val, value: ConstValue::Null },
        MirInstruction::Store { value: null_val, ptr: ptr1 }, // explicit drop → ReleaseStrong 1
    ];
    block_a.instruction_spans = vec![Span::unknown(); 3];
    block_a.terminator = Some(MirInstruction::Jump {
        target: block_b_id,
        edge_args: None,
    });
    block_a.terminator_span = Some(Span::unknown());

    // Block B
    let mut block_b = BasicBlock::new(block_b_id);
    block_b.instructions = vec![
        MirInstruction::Store { value: real_val, ptr: ptr2 },
        MirInstruction::Store { value: null_val, ptr: ptr2 }, // null_val from A (propagated) → ReleaseStrong 1
    ];
    block_b.instruction_spans = vec![Span::unknown(); 2];
    block_b.terminator = Some(MirInstruction::Return { value: None });
    block_b.terminator_span = Some(Span::unknown());

    let module = build_module_with_blocks(
        vec![block_a, block_b],
        block_a_id,
        "selfcheck_null_propagation",
        "selfcheck_mod3p12",
    );

    // 検証: Block A = 1, Block B = 1（ReturnCleanup なし）
    // 失敗時は Block B = 2（ReturnCleanup が残る = null 伝播が効いていない）
    assert_release_counts_in_blocks(
        module,
        "selfcheck_null_propagation",
        2, // 合計 2（Block A=1 + Block B=1）
        &[
            (block_a_id, 1), // explicit drop
            (block_b_id, 1), // explicit drop (null propagated)
        ],
        "null_propagation",
    );

    println!("[PASS] rc_insertion_selfcheck");
}

fn assert_all_release_values_sorted(
    mut module: MirModule,
    func_name: &str,
    label: &str,
) {
    let _stats = insert_rc_instructions(&mut module);
    let func = module.get_function(func_name).expect("function exists");

    for (bid, bb) in &func.blocks {
        for inst in &bb.instructions {
            if let MirInstruction::ReleaseStrong { values } = inst {
                for window in values.windows(2) {
                    if window[0] > window[1] {
                        eprintln!(
                            "[FAIL] {}: block {:?} values not sorted: {:?} > {:?}",
                            label, bid, window[0], window[1]
                        );
                        std::process::exit(1);
                    }
                }
            }
        }
    }
}
