#![cfg(feature = "normalized_dev")]

use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
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::loop_with_break_minimal::lower_loop_with_break_minimal;
use crate::mir::join_ir::lowering::loop_with_if_phi_if_sum::lower_if_sum_pattern;
use crate::mir::join_ir::{
    BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
    UnaryOp,
};
use crate::mir::{BasicBlockId, ValueId};
use crate::{config::env::joinir_dev_enabled, config::env::joinir_test_debug_enabled};
use std::collections::{BTreeMap, BTreeSet};

fn const_i64(func: &mut JoinFunction, dst: ValueId, value: i64) {
    func.body.push(JoinInst::Compute(MirLikeInst::Const {
        dst,
        value: ConstValue::Integer(value),
    }));
}

fn compare(func: &mut JoinFunction, dst: ValueId, op: CompareOp, lhs: ValueId, rhs: ValueId) {
    func.body.push(JoinInst::Compute(MirLikeInst::Compare {
        dst,
        op,
        lhs,
        rhs,
    }));
}

fn unary_not(func: &mut JoinFunction, dst: ValueId, operand: ValueId) {
    func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
        dst,
        op: UnaryOp::Not,
        operand,
    }));
}

fn bin_add(func: &mut JoinFunction, dst: ValueId, lhs: ValueId, rhs: ValueId) {
    func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
        dst,
        op: BinOpKind::Add,
        lhs,
        rhs,
    }));
}

fn select(
    func: &mut JoinFunction,
    dst: ValueId,
    cond: ValueId,
    then_val: ValueId,
    else_val: ValueId,
) {
    func.body.push(JoinInst::Compute(MirLikeInst::Select {
        dst,
        cond,
        then_val,
        else_val,
    }));
}

/// Structured Pattern2 (joinir_min_loop 相当) をテスト用に生成するヘルパー。
pub fn build_pattern2_minimal_structured() -> JoinModule {
    let loop_cond = ASTNode::BinaryOp {
        operator: BinaryOperator::Less,
        left: Box::new(ASTNode::Variable {
            name: "i".to_string(),
            span: Span::unknown(),
        }),
        right: Box::new(ASTNode::Literal {
            value: LiteralValue::Integer(3),
            span: Span::unknown(),
        }),
        span: Span::unknown(),
    };

    let break_cond = ASTNode::BinaryOp {
        operator: BinaryOperator::GreaterEqual,
        left: Box::new(ASTNode::Variable {
            name: "i".to_string(),
            span: Span::unknown(),
        }),
        right: Box::new(ASTNode::Literal {
            value: LiteralValue::Integer(2),
            span: Span::unknown(),
        }),
        span: Span::unknown(),
    };

    let mut scope = LoopScopeShape {
        header: BasicBlockId(0),
        body: BasicBlockId(1),
        latch: BasicBlockId(2),
        exit: BasicBlockId(3),
        pinned: BTreeSet::new(),
        carriers: BTreeSet::new(),
        body_locals: BTreeSet::new(),
        exit_live: BTreeSet::new(),
        progress_carrier: None,
        variable_definitions: BTreeMap::new(),
    };
    scope.pinned.insert("i".to_string());

    let mut condition_env = ConditionEnv::new();
    condition_env.insert("i".to_string(), ValueId(100));

    let carrier_info = crate::mir::join_ir::lowering::carrier_info::CarrierInfo {
        loop_var_name: "i".to_string(),
        loop_var_id: ValueId(1),
        carriers: vec![],
        trim_helper: None,
        promoted_loopbodylocals: vec![],
        #[cfg(feature = "normalized_dev")]
        promoted_bindings: std::collections::BTreeMap::new(),
    };

    let carrier_updates: BTreeMap<String, UpdateExpr> = BTreeMap::new();
    let mut join_value_space = JoinValueSpace::new();

    let (module, _) = lower_loop_with_break_minimal(
        crate::mir::join_ir::lowering::loop_with_break_minimal::LoopWithBreakLoweringInputs {
            scope,
            condition: &loop_cond,
            break_condition: &break_cond,
            env: &condition_env,
            carrier_info: &carrier_info,
            carrier_updates: &carrier_updates,
            body_ast: &[],
            body_local_env: None,
            allowed_body_locals_for_conditions: None,
            join_value_space: &mut join_value_space,
            skeleton: None, // Phase 92 P0-3: skeleton=None for backward compatibility
            condition_only_recipe: None, // Phase 93 P0: None for normal loops
            body_local_derived_recipe: None, // Phase 94: None for fixture
            body_local_derived_slot_recipe: None, // Phase 29ab P4: None for fixture
            balanced_depth_scan_recipe: None, // Phase 107: None for fixture
        },
    )
    .expect("pattern2 minimal lowering should succeed");

    module
}

/// Pattern2 ブレークループ（fixture ベース）を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json
pub fn build_pattern2_break_fixture_structured() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("fixture JSON should be valid");

    let mut lowerer = AstToJoinIrLowerer::new();
    lowerer.lower_program_json(&program_json)
}

/// JsonParser 由来のミニ P2 ループ（空白スキップ相当）を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_mini.program.json
pub fn build_jsonparser_skip_ws_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_mini.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("jsonparser skip_ws fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    lowerer.lower_program_json(&program_json)
}

/// JsonParser _skip_whitespace 本体相当の P2 ループを Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_real.program.json
pub fn build_jsonparser_skip_ws_real_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_real.program.json"
    );

    let program_json: serde_json::Value = serde_json::from_str(FIXTURE)
        .expect("jsonparser skip_ws real fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] jsonparser_skip_ws_real structured module: {:#?}",
            module
        );
    }

    module
}

/// JsonParser _parse_number 本体相当の P2 ループを Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_parse_number_real.program.json
pub fn build_jsonparser_parse_number_real_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_parse_number_real.program.json"
    );

    let program_json: serde_json::Value = serde_json::from_str(FIXTURE)
        .expect("jsonparser parse_number real fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] jsonparser_parse_number_real structured module: {:#?}",
            module
        );
    }

    module
}

/// selfhost token-scan 系の P2 ループを Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_token_scan_p2.program.json
pub fn build_selfhost_token_scan_p2_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_token_scan_p2.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("selfhost token_scan P2 fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] selfhost_token_scan_p2 structured module: {:#?}",
            module
        );
    }

    module
}

/// selfhost token-scan 系の P2 ループ（accum 拡張）を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_token_scan_p2_accum.program.json
pub fn build_selfhost_token_scan_p2_accum_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_token_scan_p2_accum.program.json"
    );

    let program_json: serde_json::Value = serde_json::from_str(FIXTURE)
        .expect("selfhost token_scan P2 accum fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] selfhost_token_scan_p2_accum structured module: {:#?}",
            module
        );
    }

    module
}

/// Phase 47-B: Pattern3 if-sum (multi carrier) を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/pattern3_if_sum_multi_min.program.json
pub fn build_pattern3_if_sum_multi_min_structured_for_normalized_dev() -> JoinModule {
    // 手書き JoinIR（i/sum/count の 3 キャリア）
    let mut module = JoinModule::new();
    let mut next_id = 0u32;
    let mut alloc = || {
        let id = ValueId(next_id);
        next_id += 1;
        id
    };

    let main_id = JoinFuncId::new(0);
    let loop_id = JoinFuncId::new(1);
    let exit_id = JoinFuncId::new(2);

    // main: init i/sum/count = 0 → tail call loop_step
    let i0 = alloc();
    let sum0 = alloc();
    let count0 = alloc();
    let mut main = JoinFunction::new(main_id, "main".to_string(), vec![]);
    const_i64(&mut main, i0, 0);
    const_i64(&mut main, sum0, 0);
    const_i64(&mut main, count0, 0);
    main.body.push(JoinInst::Call {
        func: loop_id,
        args: vec![i0, sum0, count0],
        k_next: None,
        dst: None,
    });
    module.add_function(main);

    // loop_step params: i, sum, count
    let i_param = alloc();
    let sum_param = alloc();
    let count_param = alloc();
    let mut loop_step = JoinFunction::new(
        loop_id,
        "loop_step".to_string(),
        vec![i_param, sum_param, count_param],
    );

    // loop condition: i < 3
    let limit_const = alloc();
    let cmp_loop = alloc();
    let exit_cond = alloc();
    const_i64(&mut loop_step, limit_const, 3);
    compare(
        &mut loop_step,
        cmp_loop,
        CompareOp::Lt,
        i_param,
        limit_const,
    );
    unary_not(&mut loop_step, exit_cond, cmp_loop);
    loop_step.body.push(JoinInst::Jump {
        cont: exit_id.as_cont(),
        args: vec![sum_param, count_param],
        cond: Some(exit_cond),
    });

    // if condition: i > 0
    let cond_cmp = alloc();
    let zero_const = alloc();
    const_i64(&mut loop_step, zero_const, 0);
    compare(&mut loop_step, cond_cmp, CompareOp::Gt, i_param, zero_const);

    // then: sum = sum + 1, count = count + 1
    let one_const = alloc();
    let sum_then = alloc();
    let count_then = alloc();
    const_i64(&mut loop_step, one_const, 1);
    bin_add(&mut loop_step, sum_then, sum_param, one_const);
    bin_add(&mut loop_step, count_then, count_param, one_const);

    // else: identity
    let sum_else = alloc();
    let count_else = alloc();
    bin_add(&mut loop_step, sum_else, sum_param, zero_const);
    bin_add(&mut loop_step, count_else, count_param, zero_const);

    // select
    let sum_new = alloc();
    let count_new = alloc();
    select(&mut loop_step, sum_new, cond_cmp, sum_then, sum_else);
    select(&mut loop_step, count_new, cond_cmp, count_then, count_else);

    // counter update: i = i + 1
    let one_const2 = alloc();
    let i_next = alloc();
    const_i64(&mut loop_step, one_const2, 1);
    bin_add(&mut loop_step, i_next, i_param, one_const2);

    loop_step.body.push(JoinInst::Call {
        func: loop_id,
        args: vec![i_next, sum_new, count_new],
        k_next: None,
        dst: None,
    });
    module.add_function(loop_step);

    // k_exit(sum, count)
    let sum_final = alloc();
    let count_final = alloc();
    let mut k_exit = JoinFunction::new(exit_id, "k_exit".to_string(), vec![sum_final, count_final]);
    k_exit.body.push(JoinInst::Ret {
        value: Some(sum_final),
    });
    module.add_function(k_exit);

    module.entry = Some(main_id);
    module.phase = crate::mir::join_ir::JoinIrPhase::Structured;
    module
}

/// Phase 47-B: JsonParser if-sum mini を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_if_sum_min.program.json
pub fn build_pattern3_json_if_sum_min_structured_for_normalized_dev() -> JoinModule {
    // 手書き JoinIR（i/sum の 2 キャリア、JsonParser 由来の簡約 if-sum）
    let mut module = JoinModule::new();
    let mut next_id = 0u32;
    let mut alloc = || {
        let id = ValueId(next_id);
        next_id += 1;
        id
    };

    let main_id = JoinFuncId::new(0);
    let loop_id = JoinFuncId::new(1);
    let exit_id = JoinFuncId::new(2);

    // main: init i/sum = 0 → tail call loop_step
    let i0 = alloc();
    let sum0 = alloc();
    let mut main = JoinFunction::new(main_id, "main".to_string(), vec![]);
    main.body.push(JoinInst::Compute(MirLikeInst::Const {
        dst: i0,
        value: ConstValue::Integer(0),
    }));
    main.body.push(JoinInst::Compute(MirLikeInst::Const {
        dst: sum0,
        value: ConstValue::Integer(0),
    }));
    main.body.push(JoinInst::Call {
        func: loop_id,
        args: vec![i0, sum0],
        k_next: None,
        dst: None,
    });
    module.add_function(main);

    // loop_step params: i, sum
    let i_param = alloc();
    let sum_param = alloc();
    let mut loop_step =
        JoinFunction::new(loop_id, "loop_step".to_string(), vec![i_param, sum_param]);

    // loop condition: i < 5
    let limit_const = alloc();
    let cmp_loop = alloc();
    let exit_cond = alloc();
    loop_step.body.push(JoinInst::Compute(MirLikeInst::Const {
        dst: limit_const,
        value: ConstValue::Integer(5),
    }));
    compare(
        &mut loop_step,
        cmp_loop,
        CompareOp::Lt,
        i_param,
        limit_const,
    );
    unary_not(&mut loop_step, exit_cond, cmp_loop);
    loop_step.body.push(JoinInst::Jump {
        cont: exit_id.as_cont(),
        args: vec![sum_param],
        cond: Some(exit_cond),
    });

    // if condition: i > 0
    let cond_cmp = alloc();
    let zero_const = alloc();
    const_i64(&mut loop_step, zero_const, 0);
    compare(&mut loop_step, cond_cmp, CompareOp::Gt, i_param, zero_const);

    // then: sum = sum + i
    let sum_then = alloc();
    bin_add(&mut loop_step, sum_then, sum_param, i_param);

    // else: identity sum
    let sum_else = alloc();
    bin_add(&mut loop_step, sum_else, sum_param, zero_const);

    // select
    let sum_new = alloc();
    select(&mut loop_step, sum_new, cond_cmp, sum_then, sum_else);

    // counter update: i = i + 1
    let one_const = alloc();
    let i_next = alloc();
    const_i64(&mut loop_step, one_const, 1);
    bin_add(&mut loop_step, i_next, i_param, one_const);

    loop_step.body.push(JoinInst::Call {
        func: loop_id,
        args: vec![i_next, sum_new],
        k_next: None,
        dst: None,
    });
    module.add_function(loop_step);

    // k_exit(sum)
    let sum_final = alloc();
    let mut k_exit = JoinFunction::new(exit_id, "k_exit".to_string(), vec![sum_final]);
    k_exit.body.push(JoinInst::Ret {
        value: Some(sum_final),
    });
    module.add_function(k_exit);

    module.entry = Some(main_id);
    module.phase = crate::mir::join_ir::JoinIrPhase::Structured;
    module
}

/// selfhost if-sum P3 を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_if_sum_p3.program.json
pub fn build_selfhost_if_sum_p3_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_if_sum_p3.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("selfhost if_sum P3 fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] selfhost_if_sum_p3 structured module: {:#?}",
            module
        );
    }

    module
}

/// selfhost if-sum P3（ext 拡張）を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_if_sum_p3_ext.program.json
pub fn build_selfhost_if_sum_p3_ext_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_if_sum_p3_ext.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("selfhost if_sum P3 ext fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] selfhost_if_sum_p3_ext structured module: {:#?}",
            module
        );
    }

    module
}

/// selfhost args-parse P2（Phase 53）を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_args_parse_p2.program.json
pub fn build_selfhost_args_parse_p2_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_args_parse_p2.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("selfhost args_parse P2 fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] selfhost_args_parse_p2 structured module: {:#?}",
            module
        );
    }

    module
}

/// selfhost stmt-count P3（Phase 53）を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_stmt_count_p3.program.json
pub fn build_selfhost_stmt_count_p3_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_stmt_count_p3.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("selfhost stmt_count P3 fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] selfhost_stmt_count_p3 structured module: {:#?}",
            module
        );
    }

    module
}

/// JsonParser _atoi 相当のミニ P2 ループを Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_mini.program.json
pub fn build_jsonparser_atoi_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_mini.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("jsonparser atoi fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] jsonparser_atoi_mini structured module: {:#?}",
            module
        );
    }

    module
}

/// JsonParser _atoi 本体相当の P2 ループを Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_real.program.json
pub fn build_jsonparser_atoi_real_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_real.program.json"
    );

    let program_json: serde_json::Value =
        serde_json::from_str(FIXTURE).expect("jsonparser atoi real fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] jsonparser_atoi_real structured module: {:#?}",
            module
        );
    }

    module
}

/// Pattern3 if-sum minimal ループ（phase212_if_sum_min.hako 相当）を Structured で組み立てるヘルパー。
///
/// Phase 47-A: P3 Normalized の最小ケース検証用（dev-only）。
pub fn build_pattern3_if_sum_min_structured_for_normalized_dev() -> JoinModule {
    fn var(name: &str) -> ASTNode {
        ASTNode::Variable {
            name: name.to_string(),
            span: Span::unknown(),
        }
    }

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

    fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode {
        ASTNode::BinaryOp {
            operator: op,
            left: Box::new(left),
            right: Box::new(right),
            span: Span::unknown(),
        }
    }

    fn assignment(target: ASTNode, value: ASTNode) -> ASTNode {
        ASTNode::Assignment {
            target: Box::new(target),
            value: Box::new(value),
            span: Span::unknown(),
        }
    }

    // Minimal if-sum pattern:
    // loop(i < 3) {
    //   if (i > 0) { sum = sum + 1 } else { sum = sum + 0 }
    //   i = i + 1
    // }
    let loop_condition = bin(BinaryOperator::Less, var("i"), int_lit(3));
    let if_condition = bin(BinaryOperator::Greater, var("i"), int_lit(0));

    let then_update = assignment(var("sum"), bin(BinaryOperator::Add, var("sum"), int_lit(1)));
    let else_update = assignment(var("sum"), bin(BinaryOperator::Add, var("sum"), int_lit(0)));
    let counter_update = assignment(var("i"), bin(BinaryOperator::Add, var("i"), int_lit(1)));

    let if_stmt = ASTNode::If {
        condition: Box::new(if_condition),
        then_body: vec![then_update],
        else_body: Some(vec![else_update]),
        span: Span::unknown(),
    };
    let body = vec![if_stmt.clone(), counter_update];

    let mut join_value_space = JoinValueSpace::new();
    let mut cond_env = ConditionEnv::new();

    // Phase 220-D: ConditionEnv には少なくともループ変数 i を登録しておく。
    let i_id = join_value_space.alloc_param();
    cond_env.insert("i".to_string(), i_id);

    let (module, _meta) = lower_if_sum_pattern(
        &loop_condition,
        &if_stmt,
        &body,
        &cond_env,
        &mut join_value_space,
    )
    .expect("if-sum lowering should succeed for minimal pattern3");

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] pattern3_if_sum_min structured module: {:#?}",
            module
        );
    }

    module
}

/// Pattern4 continue minimal ループ（pattern4_continue_min 相当）を Structured で組み立てるヘルパー。
///
/// Phase 48-A: P4 Normalized の最小ケース検証用（dev-only）。
/// 単純な continue パターン（i == 2 でスキップ）。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/pattern4_continue_min.program.json
pub fn build_pattern4_continue_min_structured_for_normalized_dev() -> JoinModule {
    use super::dev_fixtures::NormalizedDevFixture;
    NormalizedDevFixture::Pattern4ContinueMinimal.load_and_lower()
}

/// JsonParser _parse_array の whitespace continue ループを Structured で組み立てるヘルパー（dev-only）。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_parse_array_continue_skip_ws.program.json
pub fn build_jsonparser_parse_array_continue_skip_ws_structured_for_normalized_dev() -> JoinModule {
    use super::dev_fixtures::NormalizedDevFixture;
    NormalizedDevFixture::Pattern4JsonParserParseArrayContinueSkipWs.load_and_lower()
}

/// JsonParser _parse_object の whitespace continue ループを Structured で組み立てるヘルパー（dev-only）。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_parse_object_continue_skip_ws.program.json
pub fn build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev() -> JoinModule
{
    use super::dev_fixtures::NormalizedDevFixture;
    NormalizedDevFixture::Pattern4JsonParserParseObjectContinueSkipWs.load_and_lower()
}

/// JsonParser _unescape_string の「i+=2 + continue」コアを Structured で組み立てるヘルパー（dev-only）。
///
/// 実ループ（`tools/hako_shared/json_parser.hako::_unescape_string`）から、
/// 文字列処理を除いて制御構造（continue + 可変ステップ更新）だけを抽出した最小フィクスチャ。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_unescape_string_step2_min.program.json
pub fn build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev() -> JoinModule {
    const FIXTURE: &str = include_str!(
        "../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_unescape_string_step2_min.program.json"
    );

    let program_json: serde_json::Value = serde_json::from_str(FIXTURE)
        .expect("jsonparser_unescape_string_step2_min fixture should be valid JSON");

    let mut lowerer = AstToJoinIrLowerer::new();
    let module = lowerer.lower_program_json(&program_json);

    if joinir_dev_enabled() && joinir_test_debug_enabled() {
        eprintln!(
            "[joinir/normalized-dev] jsonparser_unescape_string_step2_min structured module: {:#?}",
            module
        );
    }

    module
}

/// Pattern Continue + Return minimal を Structured で組み立てるヘルパー
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/pattern_continue_return_min.program.json
pub fn build_pattern_continue_return_min_structured_for_normalized_dev() -> JoinModule {
    use super::dev_fixtures::NormalizedDevFixture;
    NormalizedDevFixture::PatternContinueReturnMin.load_and_lower()
}

/// Parse String Composite minimal を Structured で組み立てるヘルパー
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/parse_string_composite_min.program.json
pub fn build_parse_string_composite_min_structured_for_normalized_dev() -> JoinModule {
    use super::dev_fixtures::NormalizedDevFixture;
    NormalizedDevFixture::ParseStringCompositeMin.load_and_lower()
}

/// Parse Array minimal を Structured で組み立てるヘルパー
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/parse_array_min.program.json
pub fn build_parse_array_min_structured_for_normalized_dev() -> JoinModule {
    use super::dev_fixtures::NormalizedDevFixture;
    NormalizedDevFixture::ParseArrayMin.load_and_lower()
}

/// Parse Object minimal を Structured で組み立てるヘルパー
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/parse_object_min.program.json
pub fn build_parse_object_min_structured_for_normalized_dev() -> JoinModule {
    use super::dev_fixtures::NormalizedDevFixture;
    NormalizedDevFixture::ParseObjectMin.load_and_lower()
}

/// まとめて import したいとき用のプレリュード。
pub mod prelude {
    pub use super::{
        build_jsonparser_atoi_real_structured_for_normalized_dev,
        build_jsonparser_atoi_structured_for_normalized_dev,
        build_jsonparser_parse_array_continue_skip_ws_structured_for_normalized_dev,
        build_jsonparser_parse_number_real_structured_for_normalized_dev,
        build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev,
        build_jsonparser_skip_ws_real_structured_for_normalized_dev,
        build_jsonparser_skip_ws_structured_for_normalized_dev,
        build_jsonparser_unescape_string_step2_min_structured_for_normalized_dev,
        build_parse_array_min_structured_for_normalized_dev,
        build_parse_object_min_structured_for_normalized_dev,
        build_parse_string_composite_min_structured_for_normalized_dev,
        build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
        build_pattern3_if_sum_min_structured_for_normalized_dev,
        build_pattern3_if_sum_multi_min_structured_for_normalized_dev,
        build_pattern3_json_if_sum_min_structured_for_normalized_dev,
        build_pattern4_continue_min_structured_for_normalized_dev,
        build_pattern_continue_return_min_structured_for_normalized_dev,
        build_selfhost_if_sum_p3_ext_structured_for_normalized_dev,
        build_selfhost_if_sum_p3_structured_for_normalized_dev,
        build_selfhost_token_scan_p2_accum_structured_for_normalized_dev,
        build_selfhost_token_scan_p2_structured_for_normalized_dev,
    };
}
