//! JoinIR Instruction Rewriter
//!
//! Rewrites JoinIR instructions with remapped IDs and merges blocks
//! into the host MIR builder.
//!
//! Phase 4 Extraction: Separated from merge_joinir_mir_blocks (lines 260-546)
//! Phase 33-17: Further modularization - extracted TailCallClassifier and MergeResult
//! Phase 179-A Step 3: Named constants for magic values
//! Phase 131 Task 2: Extracted TailCallLoweringPolicyBox for k_exit normalization

use super::exit_args_collector::ExitArgsCollectorBox;
use super::loop_header_phi_info::LoopHeaderPhiInfo;
use super::merge_result::MergeResult;
use super::tail_call_classifier::{classify_tail_call, TailCallKind};
use super::tail_call_lowering_policy::{LoweringDecision, TailCallLoweringPolicyBox};
use super::super::trace;
use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper;
use crate::mir::join_ir::lowering::error_tags;
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
use crate::mir::join_ir_vm_bridge::join_func_name;
use crate::mir::types::ConstValue;
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, MirType, ValueId};
use std::collections::BTreeMap; // Phase 222.5-E: HashMap → BTreeMap for determinism
use std::collections::BTreeSet;

/// Phase 132-R0 Task 3: Structural check for skippable continuation functions
///
/// A continuation function is skippable if it is a pure exit stub:
/// - 1 block only
/// - No instructions
/// - Return terminator only
///
/// This is a structural check (no by-name/by-id inference).
pub(super) fn is_skippable_continuation(func: &MirFunction) -> bool {
    if func.blocks.len() != 1 {
        return false;
    }
    let Some(block) = func.blocks.get(&func.entry_block) else {
        return false;
    };
    if !block.instructions.is_empty() {
        return false;
    }
    matches!(block.terminator, Some(MirInstruction::Return { .. }))
}

fn propagate_value_type_for_inst(
    builder: &mut crate::mir::builder::MirBuilder,
    source_func: &MirFunction,
    old_inst: &MirInstruction,
    new_inst: &MirInstruction,
) {
    let Some(_) = builder.scope_ctx.current_function else {
        return;
    };

    let (old_dst, new_dst) = match (old_inst, new_inst) {
        (MirInstruction::Const { dst: o, .. }, MirInstruction::Const { dst: n, .. }) => {
            (o, n)
        }
        (MirInstruction::BinOp { dst: o, .. }, MirInstruction::BinOp { dst: n, .. }) => (o, n),
        (MirInstruction::UnaryOp { dst: o, .. }, MirInstruction::UnaryOp { dst: n, .. }) => (o, n),
        (MirInstruction::Compare { dst: o, .. }, MirInstruction::Compare { dst: n, .. }) => (o, n),
        (MirInstruction::Load { dst: o, .. }, MirInstruction::Load { dst: n, .. }) => (o, n),
        (MirInstruction::Cast { dst: o, .. }, MirInstruction::Cast { dst: n, .. }) => (o, n),
        (MirInstruction::TypeOp { dst: o, .. }, MirInstruction::TypeOp { dst: n, .. }) => (o, n),
        (MirInstruction::ArrayGet { dst: o, .. }, MirInstruction::ArrayGet { dst: n, .. }) => (o, n),
        (MirInstruction::Copy { dst: o, .. }, MirInstruction::Copy { dst: n, .. }) => (o, n),
        (MirInstruction::NewBox { dst: o, .. }, MirInstruction::NewBox { dst: n, .. }) => (o, n),
        (MirInstruction::TypeCheck { dst: o, .. }, MirInstruction::TypeCheck { dst: n, .. }) => (o, n),
        (MirInstruction::Phi { dst: o, .. }, MirInstruction::Phi { dst: n, .. }) => (o, n),
        _ => return,
    };

    // Prefer SSOT from the source MIR (JoinIR→MIR bridge may have hints).
    if let Some(ty) = source_func.metadata.value_types.get(old_dst).cloned() {
        builder.type_ctx.value_types.insert(*new_dst, ty);
        return;
    }

    // Fallback inference from the rewritten instruction itself.
    //
    // This is important for the LLVM harness: it relies on `dst_type` hints to keep `+`
    // as numeric add instead of `concat_hh_mixed` when types are otherwise unknown.
    match new_inst {
        MirInstruction::Const { dst, value } if dst == new_dst => {
            let ty = match value {
                ConstValue::Integer(_) => Some(MirType::Integer),
                ConstValue::Float(_) => Some(MirType::Float),
                ConstValue::Bool(_) => Some(MirType::Bool),
                ConstValue::String(_) => Some(MirType::String),
                ConstValue::Void => Some(MirType::Void),
                _ => None,
            };
            if let Some(ty) = ty {
                builder.type_ctx.value_types.insert(*dst, ty);
            }
        }
        MirInstruction::Copy { dst, src } if dst == new_dst => {
            if let Some(src_ty) = builder.type_ctx.value_types.get(src).cloned() {
                builder.type_ctx.value_types.insert(*dst, src_ty);
            }
        }
        MirInstruction::BinOp { dst, lhs, rhs, .. } if dst == new_dst => {
            let lhs_ty = builder.type_ctx.value_types.get(lhs);
            let rhs_ty = builder.type_ctx.value_types.get(rhs);
            if matches!(lhs_ty, Some(MirType::Integer)) && matches!(rhs_ty, Some(MirType::Integer))
            {
                builder.type_ctx.value_types.insert(*dst, MirType::Integer);
            }
        }
        MirInstruction::Compare { dst, .. } if dst == new_dst => {
            builder.type_ctx.value_types.insert(*dst, MirType::Bool);
        }
        _ => {}
    }
}

/// Phase 4: Merge ALL functions and rewrite instructions
///
/// Returns:
/// - MergeResult containing exit_block_id, exit_phi_inputs, and carrier_inputs
///
/// # Phase 33-16
///
/// The `loop_header_phi_info` parameter is used to:
/// 1. Set latch_incoming when processing tail calls
/// 2. Provide PHI dsts for exit value collection (instead of undefined parameters)
///
/// # Phase 33-17
///
/// Uses extracted modules:
/// - tail_call_classifier: TailCallKind classification
/// - merge_result: MergeResult data structure
///
/// # Phase 177-3
///
/// `exit_block_id` is now passed in from block_allocator to ensure it doesn't
/// conflict with JoinIR function blocks.
pub(super) fn merge_and_rewrite(
    builder: &mut crate::mir::builder::MirBuilder,
    mir_module: &MirModule,
    remapper: &mut JoinIrIdRemapper,
    value_to_func_name: &BTreeMap<ValueId, String>, // Phase 222.5-E: HashMap → BTreeMap for determinism
    function_params: &BTreeMap<String, Vec<ValueId>>, // Phase 222.5-E: HashMap → BTreeMap for determinism
    boundary: Option<&JoinInlineBoundary>,
    loop_header_phi_info: &mut LoopHeaderPhiInfo,
    exit_block_id: BasicBlockId,
    debug: bool,
) -> Result<MergeResult, String> {
    let trace = trace::trace();
    let verbose = debug || crate::config::env::joinir_dev_enabled();
    macro_rules! log {
        ($enabled:expr, $($arg:tt)*) => {
            trace.stderr_if(&format!($($arg)*), $enabled);
        };
    }

    let continuation_candidates: BTreeSet<String> = boundary
        .map(|b| {
            b.continuation_func_ids
                .iter()
                .copied()
                .map(join_func_name)
                .collect()
        })
        .unwrap_or_default();

    let skippable_continuation_func_names: BTreeSet<String> = mir_module
        .functions
        .iter()
        .filter_map(|(func_name, func)| {
            if continuation_candidates.contains(func_name) && is_skippable_continuation(func) {
                Some(func_name.clone())
            } else {
                None
            }
        })
        .collect();

    // Phase 132 P1: Tail call lowering is driven by the continuation contract (SSOT)
    // Only structurally-skippable continuation functions are lowered to exit jumps.
    let tail_call_policy = TailCallLoweringPolicyBox::new(skippable_continuation_func_names.clone());

    // Phase 177-3: exit_block_id is now passed in from block_allocator
    log!(
        verbose,
        "[cf_loop/joinir/instruction_rewriter] Phase 177-3: Using exit_block_id = {:?}",
        exit_block_id
    );

    // Phase 189 FIX: Build set of boundary join_inputs to skip their Const initializers
    let boundary_input_set: std::collections::HashSet<ValueId> = boundary
        .map(|b| b.join_inputs.iter().copied().collect())
        .unwrap_or_default();
    let strict_exit =
        crate::config::env::joinir_strict_enabled() || crate::config::env::joinir_dev_enabled();

    // Phase 189-Fix: Collect return values from JoinIR functions for exit PHI
    let mut exit_phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();

    // Phase 33-13: Collect carrier exit values for carrier PHIs
    // Map from carrier_name to Vec of (from_block, exit_value)
    let mut carrier_inputs: std::collections::BTreeMap<String, Vec<(BasicBlockId, ValueId)>> =
        std::collections::BTreeMap::new();

    // Build function_entry_map for Call→Jump conversion
    // Phase 222.5-E: HashMap → BTreeMap for determinism
    let mut function_entry_map: BTreeMap<String, BasicBlockId> = BTreeMap::new();
    for (func_name, func) in &mir_module.functions {
        let entry_block_new = remapper
            .get_block(func_name, func.entry_block)
            .ok_or_else(|| format!("Entry block not found for {}", func_name))?;
        function_entry_map.insert(func_name.clone(), entry_block_new);
    }

    // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order
    if debug {
        log!(
            true,
            "[cf_loop/joinir] Phase 189: Merging {} functions",
            mir_module.functions.len()
        );
    }

    let mut functions_merge: Vec<_> = mir_module.functions.iter().collect();
    functions_merge.sort_by_key(|(name, _)| name.as_str());

    let entry_func_name = functions_merge.first().map(|(name, _)| name.as_str());

    for (func_name, func) in functions_merge {
        // Phase 33-15: Identify continuation functions (k_exit, etc.)
        // Continuation functions receive values from Jump args, not as independent sources
        // We should NOT collect their Return values for exit_phi_inputs
        let is_continuation_candidate = continuation_candidates.contains(func_name);
        let is_skippable_continuation = skippable_continuation_func_names.contains(func_name);

        if debug {
            log!(
                true,
                "[cf_loop/joinir]   Merging function '{}' with {} blocks, entry={:?} (continuation_candidate={}, skippable_continuation={})",
                func_name,
                func.blocks.len(),
                func.entry_block,
                is_continuation_candidate,
                is_skippable_continuation
            );
        }

        // Phase 132 P1: Skip only *structurally skippable* continuation functions.
        //
        // Continuation functions can be real code (e.g. k_exit tailcalls post_k).
        // Merge must follow the explicit continuation contract and then decide
        // skippability by structure only.
        if is_skippable_continuation {
            if debug {
                log!(
                    true,
                    "[cf_loop/joinir]   Phase 132 P1: Skipping skippable continuation function '{}' blocks",
                    func_name
                );
            }
            continue;
        }

        // Build a local block map for this function (for remap_instruction compatibility)
        // Phase 222.5-E: HashMap → BTreeMap for determinism
        let mut local_block_map: BTreeMap<BasicBlockId, BasicBlockId> = BTreeMap::new();
        for old_block_id in func.blocks.keys() {
            let new_block_id = remapper
                .get_block(func_name, *old_block_id)
                .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?;
            local_block_map.insert(*old_block_id, new_block_id);
        }

        // Clone and remap all blocks from this function
        // DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order
        let mut blocks_merge: Vec<_> = func.blocks.iter().collect();
        blocks_merge.sort_by_key(|(id, _)| id.0);

        for (old_block_id, old_block) in blocks_merge {
            // Use remapper to get correct mapping for this function's block
            let new_block_id = remapper
                .get_block(func_name, *old_block_id)
                .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?;

            // Phase 195 FIX: Reuse existing block if present (preserves PHI from JoinIR Select lowering)
            // ultrathink "finalizer集約案": Don't overwrite blocks with BasicBlock::new()
            let mut new_block =
                if let Some(ref mut current_func) = builder.scope_ctx.current_function {
                    current_func
                        .blocks
                        .remove(&new_block_id)
                        .unwrap_or_else(|| BasicBlock::new(new_block_id))
                } else {
                    BasicBlock::new(new_block_id)
                };

            // Phase 33-16: Identify loop entry point
            //
            // The entry function's entry block is special: it IS the loop header.
            // Redirecting tail calls from this block to itself would create a self-loop.
            // This flag is used to prevent such incorrect redirection.
            let is_loop_entry_point =
                entry_func_name == Some(func_name.as_str()) && *old_block_id == func.entry_block;

            // DEBUG: Print block being processed
            if debug {
                log!(
                    true,
                    "[cf_loop/joinir] === Processing block {:?} (from func '{}') ===",
                    old_block_id, func_name
                );
                log!(
                    true,
                    "[cf_loop/joinir]   Original block has {} instructions:",
                    old_block.instructions.len()
                );
                for (idx, inst) in old_block.instructions.iter().enumerate() {
                    log!(true, "[cf_loop/joinir]     [{}] {:?}", idx, inst);
                }
                log!(
                    true,
                    "[cf_loop/joinir]   Original block terminator: {:?}",
                    old_block.terminator
                );
            }

            // Remap instructions (Phase 189: Convert inter-function Calls to control flow)
            let mut found_tail_call = false;
            let mut tail_call_target: Option<(BasicBlockId, Vec<ValueId>)> = None;
            // Phase 131 Task 2: Use TailCallLoweringPolicyBox to detect k_exit tail calls
            // k_exit (continuation) must not jump to its entry block.
            // We skip merging continuation functions, so any tail-call to k_exit must be
            // lowered as an exit jump to `exit_block_id` (and contribute exit values).
            let mut k_exit_lowering_decision: Option<LoweringDecision> = None;

            // Phase 177-3: Check if this block is the loop header with PHI nodes
            let is_loop_header_with_phi =
                is_loop_entry_point && !loop_header_phi_info.carrier_phis.is_empty();

            if is_loop_entry_point {
                log!(
                    verbose,
                    "[cf_loop/joinir] Phase 177-3 DEBUG: is_loop_entry_point={}, carrier_phis.len()={}, is_loop_header_with_phi={}",
                    is_loop_entry_point,
                    loop_header_phi_info.carrier_phis.len(),
                    is_loop_header_with_phi
                );
            }

            // Phase 177-3: Collect PHI dst IDs that will be created for this block
            // (if this is the loop header)
            let phi_dst_ids_for_block: std::collections::HashSet<ValueId> =
                if is_loop_header_with_phi {
                    loop_header_phi_info
                        .carrier_phis
                        .values()
                        .map(|entry| entry.phi_dst)
                        .collect()
                } else {
                    std::collections::HashSet::new()
                };

            if is_loop_header_with_phi && !phi_dst_ids_for_block.is_empty() {
                log!(
                    verbose,
                    "[cf_loop/joinir] Phase 177-3: Loop header with {} PHI dsts to protect: {:?}",
                    phi_dst_ids_for_block.len(),
                    phi_dst_ids_for_block
                );
            }

            // First pass: Process all instructions, identify tail calls
            for inst in &old_block.instructions {
                // Phase 177-3: Skip Copy instructions that would overwrite PHI dsts
                // The loop header PHIs will provide these values, so copies are redundant/harmful
                if is_loop_header_with_phi {
                    if let MirInstruction::Copy { dst, src } = inst {
                        // Check if this copy's dst is a PHI dst (after remapping)
                        let dst_remapped = remapper.get_value(*dst).unwrap_or(*dst);
                        log!(
                            verbose,
                            "[cf_loop/joinir]   Phase 177-3 DEBUG: Copy {:?} = {:?}, dst_remapped = {:?}, in phi_dsts = {}",
                            dst, src, dst_remapped, phi_dst_ids_for_block.contains(&dst_remapped)
                        );
                        if phi_dst_ids_for_block.contains(&dst_remapped) {
                            log!(
                                verbose,
                                "[cf_loop/joinir]   Phase 177-3: ✅ Skipping loop header Copy to PHI dst {:?} (original {:?})",
                                dst_remapped, dst
                            );
                            continue; // Skip - would overwrite PHI
                        }
                    }
                }

                // Phase 189: Skip Const String instructions that define function names
                if let MirInstruction::Const { dst, value } = inst {
                    if let crate::mir::types::ConstValue::String(_) = value {
                        if value_to_func_name.contains_key(dst) {
                            if debug {
                                log!(true, "[cf_loop/joinir]   Skipping function name const: {:?}", inst);
                            }
                            continue; // Skip this instruction
                        }
                    }
                    // Phase 189 FIX: Skip Const instructions in entry function's entry block
                    // that initialize boundary inputs. BoundaryInjector provides these values via Copy.
                    if is_loop_entry_point && boundary_input_set.contains(dst) {
                        if debug {
                            log!(true, "[cf_loop/joinir]   Skipping boundary input const (replaced by BoundaryInjector Copy): {:?}", inst);
                        }
                        continue; // Skip - BoundaryInjector will provide the value
                    }
                }

                // Phase 189: Detect tail calls and save parameters
                if let MirInstruction::Call { func, args, .. } = inst {
                    if let Some(callee_name) = value_to_func_name.get(func) {
                        // Phase 131 Task 2: Use TailCallLoweringPolicyBox to classify tail calls
                        // k_exit calls are "exit edges" (not normal tail calls).
                        // Otherwise we'd Jump to the k_exit entry block, but continuation
                        // blocks are intentionally not merged (skip), causing invalid BB.
                        let remapped_args: Vec<ValueId> = args
                            .iter()
                            .map(|&v| remapper.get_value(v).unwrap_or(v))
                            .collect();

                        if let Some(decision) = tail_call_policy.classify_tail_call(callee_name, &remapped_args) {
                            // This is a k_exit tail call - policy says normalize to exit jump
                            k_exit_lowering_decision = Some(decision);
                            found_tail_call = true;
                            if debug {
                                log!(
                                    true,
                                    "[cf_loop/joinir] Phase 131 Task 2: Detected k_exit tail call '{}' (args={:?}), will normalize to Jump(exit_block_id={:?})",
                                    callee_name,
                                    args,
                                    exit_block_id
                                );
                            }
                            continue; // Skip the Call instruction itself
                        }
                        // Not a k_exit call - check if it's a normal tail call (intra-module jump)
                        if let Some(&target_block) = function_entry_map.get(callee_name) {
                            // This is a tail call - save info and skip the Call instruction itself
                            tail_call_target = Some((target_block, remapped_args));
                            found_tail_call = true;

                            if debug {
                                log!(
                                    true,
                                    "[cf_loop/joinir]   Detected tail call to '{}' (args={:?}), will convert to Jump",
                                    func_name, args
                                );
                            }

                            continue; // Skip the Call instruction itself
                        }
                    }
                }

                // Phase 33-20: Skip Copy instructions that would overwrite header PHI dsts
                // In the header block, carriers are defined by PHIs, not Copies.
                // JoinIR function parameters get copied to local variables, but after
                // inlining with header PHIs, those Copies would overwrite the PHI results.
                if let MirInstruction::Copy { dst, src: _ } = inst {
                    // Check if this Copy's dst (after remapping) matches any header PHI dst
                    let remapped_dst = remapper.get_value(*dst).unwrap_or(*dst);
                    let is_header_phi_dst = loop_header_phi_info
                        .carrier_phis
                        .values()
                        .any(|entry| entry.phi_dst == remapped_dst);

                    if is_header_phi_dst {
                        if debug {
                            log!(
                                true,
                                "[cf_loop/joinir] Phase 33-20: Skipping Copy that would overwrite header PHI dst {:?}",
                                remapped_dst
                            );
                        }
                        continue; // Skip - PHI already defines this value
                    }
                }

                // Process regular instructions - Phase 189: Use remapper.remap_instruction() + manual block remapping
                let remapped = remapper.remap_instruction(inst);

                // Phase 189 FIX: Manual block remapping for Branch/Phi (JoinIrIdRemapper doesn't know func_name)
                let remapped_with_blocks = match remapped {
                    MirInstruction::Branch {
                        condition,
                        then_bb,
                        else_bb,
                    } => MirInstruction::Branch {
                        condition,
                        then_bb: local_block_map.get(&then_bb).copied().unwrap_or(then_bb),
                        else_bb: local_block_map.get(&else_bb).copied().unwrap_or(else_bb),
                    },
                    MirInstruction::Phi { dst, inputs, type_hint } => {
                        use super::phi_block_remapper::remap_phi_instruction;
                        remap_phi_instruction(dst, &inputs, type_hint, &local_block_map)
                    }
                    other => other,
                };

                if debug {
                    match inst {
                        MirInstruction::BoxCall { .. } => {
                            log!(
                                true,
                                "[cf_loop/joinir]   Adding BoxCall to block {:?}: {:?}",
                                new_block_id, inst
                            );
                        }
                        _ => {}
                    }
                }

                propagate_value_type_for_inst(builder, func, inst, &remapped_with_blocks);
                new_block.instructions.push(remapped_with_blocks);
            }

            // DEBUG: Print what was added to the block after first pass
            if debug {
                log!(
                    true,
                    "[cf_loop/joinir]   After first pass, new_block has {} instructions",
                    new_block.instructions.len()
                );
                for (idx, inst) in new_block.instructions.iter().enumerate() {
                    log!(true, "[cf_loop/joinir]     [{}] {:?}", idx, inst);
                }
            }

            // Second pass: Insert parameter bindings for tail calls
            // Phase 188-Impl-3: Use actual parameter ValueIds from target function
            if let Some((target_block, args)) = tail_call_target {
                if debug {
                    log!(
                        true,
                        "[cf_loop/joinir]   Inserting param bindings for tail call to {:?}",
                        target_block
                    );
                }

                // Find the target function name from the target_block
                // We need to reverse-lookup the function name from the entry block
                let mut target_func_name: Option<String> = None;
                for (fname, &entry_block) in &function_entry_map {
                    if entry_block == target_block {
                        target_func_name = Some(fname.clone());
                        break;
                    }
                }

                if let Some(target_func_name) = target_func_name {
                    if let Some(target_params) = function_params.get(&target_func_name) {
                        // Phase 33-21: Skip parameter binding in header block
                        //
                        // The header block (loop entry point) has PHIs that define carriers.
                        // Parameter bindings are only needed for back edges (latch → header).
                        // In the header block, the PHI itself provides the initial values,
                        // so we don't need Copy instructions from tail call args.
                        //
                        // Without this check, the generated MIR would have:
                        //   bb_header:
                        //     %phi_dst = phi [entry_val, bb_entry], [latch_val, bb_latch]
                        //     %phi_dst = copy %undefined  ← ❌ This overwrites the PHI!
                        if is_loop_entry_point {
                            if debug {
                                log!(
                                    true,
                                    "[cf_loop/joinir] Phase 33-21: Skip param bindings in header block (PHIs define carriers)"
                                );
                            }
                        } else {
                            // Insert Copy instructions for parameter binding
                            // Phase 131-6 FIX: Skip Copy when dst is a header PHI destination
                            // The PHI node will receive the value as an incoming edge instead
                            for (i, arg_val_remapped) in args.iter().enumerate() {
                                if i < target_params.len() {
                                    let param_val_original = target_params[i];
                                    if let Some(param_val_remapped) =
                                        remapper.get_value(param_val_original)
                                    {
                                        // Phase 131-6: Check if this would overwrite a header PHI dst
                                        let is_header_phi_dst = loop_header_phi_info
                                            .carrier_phis
                                            .values()
                                            .any(|entry| entry.phi_dst == param_val_remapped);

                                        if is_header_phi_dst {
                                            if debug {
                                                log!(
                                                    true,
                                                    "[cf_loop/joinir] Phase 131-6: Skip param binding to PHI dst {:?} (PHI receives value via incoming edge)",
                                                    param_val_remapped
                                                );
                                            }
                                            // Skip - PHI will receive this value as incoming edge
                                            continue;
                                        }

                                        new_block.instructions.push(MirInstruction::Copy {
                                            dst: param_val_remapped,
                                            src: *arg_val_remapped,
                                        });

                                        if debug {
                                            log!(
                                                true,
                                                "[cf_loop/joinir]     Param binding: arg {:?} → param {:?}",
                                                arg_val_remapped, param_val_remapped
                                            );
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                // Phase 33-16: Record latch incoming for loop header PHI
                //
                // At this point, args[0] contains the updated loop variable value (i_next).
                // We record this as the latch incoming so that the header PHI can reference
                // the correct SSA value at loop continuation time.
                if let Some(b) = boundary {
                    if let Some(loop_var_name) = &b.loop_var_name {
                        if !args.is_empty() {
                            // The first arg is the loop variable's updated value
                            let latch_value = args[0];
                            // The current block (new_block_id) is the latch block
                            loop_header_phi_info.set_latch_incoming(
                                loop_var_name,
                                new_block_id, // latch block
                                latch_value,  // updated loop var value (i_next)
                            );

                            if debug {
                                log!(
                                    true,
                                    "[cf_loop/joinir] Phase 33-16: Set latch incoming for '{}': block={:?}, value={:?}",
                                    loop_var_name, new_block_id, latch_value
                                );
                            }
                        }
                    }

                    // Phase 33-20: Also set latch incoming for other carriers from exit_bindings
                    // Phase 176-4 FIX: exit_bindings may include the loop variable itself
                    // We need to skip it since it's already handled above via boundary.loop_var_name
                    // The remaining non-loop-variable carriers are ordered to match args[1..] (after the loop variable)
                    let mut carrier_arg_idx = 1; // Start from args[1] (args[0] is loop variable)
                    for binding in b.exit_bindings.iter() {
                        // Skip if this binding is for the loop variable (already handled)
                        if let Some(ref loop_var) = b.loop_var_name {
                            if &binding.carrier_name == loop_var {
                                if debug {
                                    log!(
                                        true,
                                        "[cf_loop/joinir] Phase 176-4: Skipping loop variable '{}' in exit_bindings (handled separately)",
                                        binding.carrier_name
                                    );
                                }
                                continue; // Skip loop variable
                            }
                        }

                        // Process non-loop-variable carrier
                        if carrier_arg_idx < args.len() {
                            let latch_value = args[carrier_arg_idx];
                            loop_header_phi_info.set_latch_incoming(
                                &binding.carrier_name,
                                new_block_id,
                                latch_value,
                            );

                            if debug {
                                log!(
                                    true,
                                    "[cf_loop/joinir] Phase 176-4: Set latch incoming for carrier '{}': block={:?}, value={:?} (arg[{}])",
                                    binding.carrier_name, new_block_id, latch_value, carrier_arg_idx
                                );
                            }
                            carrier_arg_idx += 1;
                        } else if debug {
                            log!(
                                true,
                                "[cf_loop/joinir] Phase 33-20 WARNING: No arg for carrier '{}' at index {}",
                                binding.carrier_name, carrier_arg_idx
                            );
                        }
                    }
                }

                // Phase 33-16: Classify tail call to determine redirection behavior
                //
                // Tail calls have different semantics depending on where they occur:
                // - LoopEntry: First entry (entry function's entry block) → no redirect
                // - BackEdge: Loop continuation (other blocks) → redirect to header PHI
                // - ExitJump: Exit to continuation → handled by Return conversion
                let tail_call_kind = classify_tail_call(
                    is_loop_entry_point,
                    !loop_header_phi_info.carrier_phis.is_empty(),
                    boundary.is_some(),
                );

                let actual_target = match tail_call_kind {
                    TailCallKind::BackEdge => {
                        // Back edge: redirect to header block where PHIs are
                        if debug {
                            log!(
                                true,
                                "[cf_loop/joinir] Phase 33-16: BackEdge detected, redirecting from {:?} to header {:?}",
                                target_block, loop_header_phi_info.header_block
                            );
                        }
                        loop_header_phi_info.header_block
                    }
                    TailCallKind::LoopEntry => {
                        // Loop entry: no redirect (entry block IS the header)
                        if debug {
                            log!(
                                true,
                                "[cf_loop/joinir] Phase 33-16: LoopEntry detected, using direct target {:?}",
                                target_block
                            );
                        }
                        target_block
                    }
                    TailCallKind::ExitJump => {
                        // Exit: use target as-is (will be handled by Return conversion)
                        target_block
                    }
                };

                new_block.terminator = Some(MirInstruction::Jump {
                    target: actual_target,
                });

                // DEBUG: Print final state after adding parameter bindings
                if debug {
                    log!(
                        true,
                        "[cf_loop/joinir]   After adding param bindings, new_block has {} instructions",
                        new_block.instructions.len()
                    );
                    for (idx, inst) in new_block.instructions.iter().enumerate() {
                        log!(true, "[cf_loop/joinir]     [{}] {:?}", idx, inst);
                    }
                }
            }
            new_block.instruction_spans = old_block.instruction_spans.clone();

            if debug {
                log!(true, "[cf_loop/joinir]   Span sync: new_block.instructions.len()={}, old_block.instruction_spans.len()={}, new_block.instruction_spans.len()={}",
                    new_block.instructions.len(),
                    old_block.instruction_spans.len(),
                    new_block.instruction_spans.len()
                );
            }

            // Remap terminator (convert Return → Jump to exit) if not already set by tail call
            if !found_tail_call {
                if let Some(ref term) = old_block.terminator {
                    let remapped_term = match term {
                        MirInstruction::Return { value } => {
                            // Convert Return to Jump to exit block
                            // All functions return to same exit block (Phase 189)
                            //
                            // Phase 246-EX: Use Jump args from block metadata for exit values
                            //
                            // The JoinIR Jump instruction passes ALL carrier values in its args,
                            // but the JoinIR→MIR conversion in joinir_block_converter only preserved
                            // the first arg in the Return value. We now use the jump_args metadata
                            // to recover all the original Jump args.
                            //
                            if let Some(_ret_val) = value {
                                // Phase 246-EX: Check if this block has jump_args metadata
                                if let Some(ref jump_args) = old_block.jump_args {
                                    log!(
                                        verbose,
                                        "[DEBUG-177] Phase 246-EX: Block {:?} has jump_args metadata: {:?}",
                                        old_block.id, jump_args
                                    );

                                    // The jump_args are in JoinIR value space, remap them to HOST
                                    let remapped_args: Vec<ValueId> = jump_args
                                        .iter()
                                        .map(|&arg| remapper.remap_value(arg))
                                        .collect();

                                    log!(
                                        verbose,
                                        "[DEBUG-177] Phase 246-EX: Remapped jump_args: {:?}",
                                        remapped_args
                                    );

                                    // Phase 118 P2: Use ExitArgsCollectorBox to collect exit values
                                    if let Some(b) = boundary {
                                        let collector = ExitArgsCollectorBox::new();
                                        let collection_result = collector.collect(
                                            &b.exit_bindings,
                                            &remapped_args,
                                            new_block_id,
                                            strict_exit,
                                        )?;

                                        // Add expr_result to exit_phi_inputs (if present)
                                        if let Some(expr_result_val) = collection_result.expr_result_value {
                                            exit_phi_inputs.push((new_block_id, expr_result_val));
                                            log!(
                                                verbose,
                                                "[DEBUG-177] Phase 118: exit_phi_inputs from ExitArgsCollectorBox: ({:?}, {:?})",
                                                new_block_id, expr_result_val
                                            );
                                        }

                                        // Add carrier values to carrier_inputs
                                        for (carrier_name, (block_id, value_id)) in collection_result.carrier_values {
                                            carrier_inputs
                                                .entry(carrier_name.clone())
                                                .or_insert_with(Vec::new)
                                                .push((block_id, value_id));
                                            log!(
                                                verbose,
                                                "[DEBUG-177] Phase 118: Collecting carrier '{}': from {:?} value {:?}",
                                                carrier_name, block_id, value_id
                                            );
                                        }
                                    }
                                } else {
                                    // Fallback: Use header PHI dst (old behavior for blocks without jump_args)
                                    log!(
                                        verbose,
                                        "[DEBUG-177] Phase 246-EX: Block {:?} has NO jump_args, using header PHI fallback",
                                        old_block.id
                                    );

                                    if let Some(b) = boundary {
                                        if let Some(loop_var_name) = &b.loop_var_name {
                                            if let Some(phi_dst) =
                                                loop_header_phi_info.get_carrier_phi(loop_var_name)
                                            {
                                                exit_phi_inputs.push((new_block_id, phi_dst));
                                                if debug {
                                                    log!(
                                                        true,
                                                        "[cf_loop/joinir] Phase 246-EX fallback: Using header PHI dst {:?} for exit (loop_var='{}')",
                                                        phi_dst, loop_var_name
                                                    );
                                                }
                                            }
                                        }

                                        // Phase 227: Filter out ConditionOnly carriers from exit PHI
                                        // Phase 131 P1.5: For DirectValue mode, if no header PHI, use host_slot (initial value)
                                        for binding in &b.exit_bindings {
                                            if binding.role == crate::mir::join_ir::lowering::carrier_info::CarrierRole::ConditionOnly {
                                                continue;
                                            }

                                            if let Some(phi_dst) = loop_header_phi_info
                                                .get_carrier_phi(&binding.carrier_name)
                                            {
                                                carrier_inputs
                                                    .entry(binding.carrier_name.clone())
                                                    .or_insert_with(Vec::new)
                                                    .push((new_block_id, phi_dst));
                                            } else if b.exit_reconnect_mode == crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode::DirectValue {
                                                // Phase 131 P1.5: DirectValue mode fallback - use host_slot (initial value)
                                                // This handles k_exit blocks that don't have jump_args and no header PHI
                                                carrier_inputs
                                                    .entry(binding.carrier_name.clone())
                                                    .or_insert_with(Vec::new)
                                                    .push((new_block_id, binding.host_slot));
                                                log!(
                                                    verbose,
                                                    "[cf_loop/joinir] Phase 131 P1.5: DirectValue fallback for '{}': using host_slot {:?}",
                                                    binding.carrier_name, binding.host_slot
                                                );
                                            }
                                        }
                                    }
                                }
                            }

                            MirInstruction::Jump {
                                target: exit_block_id,
                            }
                        }
                        MirInstruction::Jump { target } => {
                            // Phase 189 FIX: Remap block ID for Jump
                            MirInstruction::Jump {
                                target: local_block_map.get(target).copied().unwrap_or(*target),
                            }
                        }
                        MirInstruction::Branch {
                            condition,
                            then_bb,
                            else_bb,
                        } => {
                            // Phase 189 FIX: Remap block IDs AND condition ValueId for Branch
                            MirInstruction::Branch {
                                condition: remapper.remap_value(*condition),
                                then_bb: local_block_map.get(then_bb).copied().unwrap_or(*then_bb),
                                else_bb: local_block_map.get(else_bb).copied().unwrap_or(*else_bb),
                            }
                        }
                        _ => remapper.remap_instruction(term),
                    };
                    new_block.terminator = Some(remapped_term);
                }
            }

            // Phase 131 Task 2: If this block tail-calls k_exit, normalize to exit jump
            // Use TailCallLoweringPolicyBox to generate the correct terminator
            if let Some(decision) = k_exit_lowering_decision {
                match decision {
                    LoweringDecision::NormalizeToExitJump { args } => {
                        // Collect exit values from k_exit arguments
                        if let Some(b) = boundary {
                            let collector = ExitArgsCollectorBox::new();
                            let collection_result =
                                collector.collect(&b.exit_bindings, &args, new_block_id, strict_exit)?;
                            if let Some(expr_result_value) = collection_result.expr_result_value {
                                exit_phi_inputs.push((collection_result.block_id, expr_result_value));
                            }
                            for (carrier_name, pair) in collection_result.carrier_values {
                                carrier_inputs
                                    .entry(carrier_name)
                                    .or_insert_with(Vec::new)
                                    .push(pair);
                            }
                        } else if strict_exit {
                            return Err(error_tags::freeze_with_hint(
                                "phase131/k_exit/no_boundary",
                                "k_exit tail call detected without JoinInlineBoundary",
                                "k_exit must be handled as fragment exit; ensure boundary is passed when merging JoinIR fragments",
                            ));
                        }

                        // Generate exit jump using policy box
                        let exit_jump = tail_call_policy.rewrite_to_exit_jump(exit_block_id);
                        new_block.terminator = Some(exit_jump.clone());

                        // Strict mode: verify the generated terminator matches contract
                        if strict_exit {
                            tail_call_policy.verify_exit_jump(&exit_jump, exit_block_id)?;
                        }
                    }
                    LoweringDecision::NormalTailCall => {
                        // Should never happen (policy only returns NormalizeToExitJump for k_exit)
                        unreachable!("TailCallLoweringPolicyBox returned NormalTailCall for k_exit");
                    }
                }
            }

            // Phase 189 FIX: Ensure instruction_spans matches instructions length
            // The original spans may not cover all instructions after remapping/adding
            // (PHI instructions, tail call parameter bindings, etc.)
            let inst_count = new_block.instructions.len();
            let span_count = new_block.instruction_spans.len();
            if inst_count > span_count {
                // Use a default span for the extra instructions
                let default_span = new_block
                    .instruction_spans
                    .last()
                    .copied()
                    .unwrap_or_else(crate::ast::Span::unknown);
                new_block.instruction_spans.resize(inst_count, default_span);
            } else if inst_count < span_count {
                // Truncate spans to match instructions
                new_block.instruction_spans.truncate(inst_count);
            }

            // Add block to current function
            if let Some(ref mut current_func) = builder.scope_ctx.current_function {
                current_func.add_block(new_block);
            }
        }
    }

    // Phase 188-Impl-3: Inject Copy instructions for boundary inputs using BoundaryInjector
    if let Some(boundary) = boundary {
        use crate::mir::builder::joinir_inline_boundary_injector::BoundaryInjector;

        // Get entry function's entry block (first function by convention)
        let (entry_func_name, entry_func) = mir_module
            .functions
            .iter()
            .next()
            .ok_or("JoinIR module has no functions")?;
        let entry_block_remapped = remapper
            .get_block(entry_func_name, entry_func.entry_block)
            .ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?;

        // Create BTreeMap from remapper for BoundaryInjector (temporary adapter)
        // Phase 222.5-E: HashMap → BTreeMap for determinism
        let mut value_map_for_injector = BTreeMap::new();

        // Phase 171-fix: Add join_inputs to value_map
        for join_in in &boundary.join_inputs {
            if let Some(remapped) = remapper.get_value(*join_in) {
                value_map_for_injector.insert(*join_in, remapped);
            }
        }

        // Phase 171-fix: Add condition_bindings to value_map
        // Each binding specifies JoinIR ValueId → HOST ValueId mapping
        // We remap the JoinIR ValueId to a new MIR ValueId
        for binding in &boundary.condition_bindings {
            if let Some(remapped) = remapper.get_value(binding.join_value) {
                value_map_for_injector.insert(binding.join_value, remapped);
                if debug {
                    log!(
                        true,
                        "[cf_loop/joinir] Phase 171-fix: Condition binding '{}': JoinIR {:?} → remapped {:?} (HOST {:?})",
                        binding.name, binding.join_value, remapped, binding.host_value
                    );
                }
            }
        }

        // Phase 177-3: Collect PHI dst IDs from loop_header_phi_info
        let phi_dst_ids: std::collections::HashSet<ValueId> = loop_header_phi_info
            .carrier_phis
            .values()
            .map(|entry| entry.phi_dst)
            .collect();

        // Use BoundaryInjector to inject Copy instructions
        // Phase 177-3 Option B: Returns reallocations map for condition_bindings with PHI collisions
        if let Some(ref mut current_func) = builder.scope_ctx.current_function {
            let _reallocations = BoundaryInjector::inject_boundary_copies(
                current_func,
                entry_block_remapped,
                boundary,
                &value_map_for_injector,
                &phi_dst_ids,
                debug,
            )?;

            // Note: reallocations map is returned but not currently used.
            // If condition variables need to be referenced after this point,
            // the reallocations map would need to be applied to update references.
            // For now, condition_bindings are read-only variables used only in
            // the loop condition, so no further updates are needed.
        }
    }

    // Phase 131 P2: DirectValue mode remapped_exit_values SSOT
    //
    // Contract (DirectValue):
    // - boundary.exit_bindings[*].join_exit_value is a JoinIR-side ValueId that must be defined
    //   in the merged MIR (e.g., final env value produced by the loop body).
    // - remapper owns JoinIR→Host mapping, so merge_and_rewrite is responsible for producing
    //   carrier_name → host ValueId.
    let remapped_exit_values: BTreeMap<String, ValueId> = match boundary {
        Some(b)
            if b.exit_reconnect_mode
                == crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode::DirectValue =>
        {
            let mut direct_values = BTreeMap::new();
            for binding in &b.exit_bindings {
                if binding.role
                    == crate::mir::join_ir::lowering::carrier_info::CarrierRole::ConditionOnly
                {
                    continue;
                }

                let host_vid = remapper.get_value(binding.join_exit_value).ok_or_else(|| {
                    error_tags::freeze_with_hint(
                        "phase131/directvalue/remap_missing",
                        &format!(
                            "DirectValue: join_exit_value {:?} for carrier '{}' was not remapped",
                            binding.join_exit_value, binding.carrier_name
                        ),
                        "ensure exit_bindings.join_exit_value is included in merge used_values and references a value defined by the fragment",
                    )
                })?;
                direct_values.insert(binding.carrier_name.clone(), host_vid);
            }
            direct_values
        }
        _ => BTreeMap::new(),
    };

    Ok(MergeResult {
        exit_block_id,
        exit_phi_inputs,
        carrier_inputs,
        remapped_exit_values, // Phase 131 P1.5
    })
}
