use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::{MirCompiler, MirInstruction};

fn lit_i(i: i64) -> ASTNode {
    ASTNode::Literal {
        value: LiteralValue::Integer(i),
        span: Span::unknown(),
    }
}

fn bool_lt(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
    ASTNode::BinaryOp {
        operator: BinaryOperator::Less,
        left: Box::new(lhs),
        right: Box::new(rhs),
        span: Span::unknown(),
    }
}

#[test]
fn mir13_no_phi_if_merge_inserts_edge_copies_for_return() {
    // Force PHI-off mode
    std::env::set_var("NYASH_MIR_NO_PHI", "1");

    // if (1 < 2) { return 40 } else { return 50 }
    let ast = ASTNode::If {
        condition: Box::new(bool_lt(lit_i(1), lit_i(2))),
        then_body: vec![ASTNode::Return {
            value: Some(Box::new(lit_i(40))),
            span: Span::unknown(),
        }],
        else_body: Some(vec![ASTNode::Return {
            value: Some(Box::new(lit_i(50))),
            span: Span::unknown(),
        }]),
        span: Span::unknown(),
    };

    let mut mc = MirCompiler::with_options(false);
    let cr = mc.compile(ast).expect("compile");
    let f = cr.module.functions.get("main").expect("function main");

    // Find the block that returns a value and capture that return value id
    let mut ret_block_id = None;
    let mut ret_val = None;
    for (bid, bb) in &f.blocks {
        if let Some(MirInstruction::Return { value: Some(v) }) = bb.terminator.clone() {
            ret_block_id = Some(*bid);
            ret_val = Some(v);
            break;
        }
    }
    let ret_block = ret_block_id.expect("ret block");
    let out_v = ret_val.expect("ret value");

    // In PHI-off mode we expect copies into each predecessor of the merge/ret block
    let preds: Vec<_> = f
        .blocks
        .get(&ret_block)
        .expect("ret block present")
        .predecessors
        .iter()
        .copied()
        .collect();
    assert!(
        preds.len() >= 2,
        "expected at least two predecessors at merge"
    );

    for p in preds {
        let bb = f.blocks.get(&p).expect("pred block present");
        let has_copy = bb
            .instructions
            .iter()
            .any(|inst| matches!(inst, MirInstruction::Copy { dst, .. } if *dst == out_v));
        assert!(has_copy, "expected Copy to out_v in predecessor {:?}", p);
    }

    // And we expect that the merge/ret block itself does not contain
    // an extra Copy to the merged value (edge-copy only policy)
    let merge_bb = f.blocks.get(&ret_block).expect("ret block present");
    let merge_has_copy = merge_bb
        .instructions
        .iter()
        .any(|inst| matches!(inst, MirInstruction::Copy { dst, .. } if *dst == out_v));
    assert!(
        !merge_has_copy,
        "ret/merge block should not contain Copy to merged value (edge-copy only)"
    );
}
