use super::config::MergeConfig;
use super::trace;
use super::{LoopHeaderPhiBuilder, MergeContracts};
use crate::mir::join_ir::lowering::error_tags;
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
use crate::mir::{MirModule, ValueId};
use std::collections::BTreeMap;

#[allow(unused_imports)]
use super::{
    block_allocator, block_remapper, boundary_carrier_layout, boundary_logging, carrier_init_builder,
    contract_checks, debug_assertions, entry_selector, exit_args_collector, exit_line,
    exit_phi_builder, expr_result_resolver, header_phi_prebuild, instruction_rewriter,
    loop_header_phi_builder, loop_header_phi_info, merge_result, phi_block_remapper, rewriter,
    tail_call_classifier, tail_call_lowering_policy, value_collector, value_remapper,
};

/// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function
///
/// # Phase 189: Multi-Function MIR Merge
///
/// This merges JoinIR-generated blocks by:
/// 1. Remapping all block IDs across ALL functions to avoid conflicts
/// 2. Remapping all value IDs across ALL functions to avoid conflicts
/// 3. Adding all blocks from all functions to current_function
/// 4. Jumping from current_block to the entry block
/// 5. Converting Return → Jump to exit block for all functions
///
/// **Multi-Function Support** (Phase 189):
/// - Pattern 1 (Simple While) generates 3 functions: entry + loop_step + k_exit
/// - All functions are flattened into current_function with global ID remapping
/// - Single exit block receives all Return instructions from all functions
///
/// # Phase 188-Impl-3: JoinInlineBoundary Support
///
/// When `boundary` is provided, injects Copy instructions at the entry block
/// to connect host ValueIds to JoinIR local ValueIds:
///
/// ```text
/// entry_block:
///   // Injected by boundary
///   ValueId(100) = Copy ValueId(4)  // join_input → host_input
///   // Original JoinIR instructions follow...
/// ```
///
/// This enables clean separation: JoinIR uses local IDs (0,1,2...),
/// host uses its own IDs, and Copy instructions bridge the gap.
///
/// # Returns
///
/// Returns `Ok(Some(exit_phi_id))` if the merged JoinIR functions have return values
/// that were collected into an exit block PHI. Exit reconnection is driven by
/// explicit exit_bindings on the boundary (no legacy host_outputs).
pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
    builder: &mut crate::mir::builder::MirBuilder,
    mir_module: &MirModule,
    boundary: Option<&JoinInlineBoundary>,
    debug: bool,
) -> Result<Option<ValueId>, String> {
    // Phase 131 Task 6: Use MergeConfig for consolidated configuration
    let config = MergeConfig::with_debug(debug);
    let verbose = config.dev_log;
    let trace = trace::trace();

    trace.stderr_if(
        &format!(
            "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions",
            mir_module.functions.len()
        ),
        debug,
    );

    if let Some(boundary) = boundary {
        if let Err(msg) = boundary.validate_jump_args_layout() {
            return Err(error_tags::freeze_with_hint(
                "phase256/jump_args_layout",
                &msg,
                "set JoinInlineBoundary.jump_args_layout via builder and avoid expr_result/carrier mismatch",
            ));
        }
    }

    // Phase 286 P3: Validate boundary contract BEFORE merge begins
    // This catches boundary construction bugs early with clear diagnostics
    if let Some(boundary) = boundary {
        // Enrich context with host_fn and join-side info for better error diagnostics
        let host_fn = builder
            .scope_ctx
            .current_function
            .as_ref()
            .map(|f| f.signature.name.as_str())
            .unwrap_or("<unknown>");

        // Join-side info: continuation count + boundary summary
        let cont_count = boundary.continuation_func_ids.len();
        let join_summary = format!(
            "conts={} exits={} conds={}",
            cont_count,
            boundary.exit_bindings.len(),
            boundary.condition_bindings.len()
        );

        let context = format!(
            "merge_joinir_mir_blocks host={} join={} phase=<unknown> [{}]",
            host_fn, cont_count, join_summary
        );

        if let Err(msg) = contract_checks::verify_boundary_contract_at_creation(boundary, &context) {
            return Err(msg);  // Fail-Fast: [joinir/contract:B*] error
        }
    }

    // Phase 287 P0.5: Delegated to boundary_logging module
    boundary_logging::log_boundary_info(boundary, &trace, verbose);

    // Phase 1: Allocate block IDs for all functions
    // Phase 177-3: block_allocator now returns exit_block_id to avoid conflicts
    let (mut remapper, exit_block_id) =
        block_allocator::allocate_blocks(builder, mir_module, debug)?;

    // Phase 2: Collect values from all functions
    let (mut used_values, value_to_func_name, function_params) =
        value_collector::collect_values(mir_module, &remapper, debug)?;

    // Phase 171-fix + Phase 256.7-fix: Add condition_bindings' join_values to used_values for remapping
    // UNLESS they are function params. Params should NOT be remapped (they're defined
    // by boundary Copies and used directly in JoinIR body).
    if let Some(boundary) = boundary {
        // Build all_params set for checking (moved before condition_bindings loop)
        let all_params: std::collections::HashSet<ValueId> = function_params
            .values()
            .flat_map(|params| params.iter().copied())
            .collect();

        // Phase 283 P0 DEBUG: Log condition_bindings count
        trace.stderr_if(
            &format!(
                "[cf_loop/joinir] Phase 283 P0 DEBUG: Processing {} condition_bindings",
                boundary.condition_bindings.len()
            ),
            debug,
        );

        for binding in &boundary.condition_bindings {
            trace.stderr_if(
                &format!(
                    "[cf_loop/joinir] Phase 283 P0 DEBUG: Checking binding '{}' join={:?}",
                    binding.name, binding.join_value
                ),
                debug,
            );

            if all_params.contains(&binding.join_value) {
                trace.stderr_if(
                    &format!(
                        "[cf_loop/joinir] Phase 256.7-fix: Skipping condition binding '{}' (JoinIR {:?} is a param)",
                        binding.name, binding.join_value
                    ),
                    debug,
                );
            } else {
                // Phase 283 P0 FIX: Ensure remapper has valid mapping (Fail-Fast)
                if let Some(host_id) = builder.variable_ctx.variable_map.get(&binding.name) {
                    // Variable exists in host context - map join_value to existing host_id
                    trace.stderr_if(
                        &format!(
                            "[cf_loop/joinir] Phase 283 P0: ✅ Condition binding '{}' JoinIR {:?} → host {:?}",
                            binding.name, binding.join_value, host_id
                        ),
                        debug,
                    );
                    remapper.set_value(binding.join_value, *host_id);
                    used_values.insert(binding.join_value);
                } else {
                    // Fail-Fast: No host ValueId found → surface root cause immediately
                    return Err(format!(
                        "[merge/phase2.1] Condition variable '{}' (join={:?}) has no host ValueId in variable_map. \
                        This indicates the value was not properly supplied by boundary builder or cond_env. \
                        Check: (1) boundary builder supplies all condition vars, (2) cond_env correctly tracks host ValueIds.",
                        binding.name, binding.join_value
                    ));
                }
            }
        }

        // Phase 172-3 + Phase 256 P1.10: Add exit_bindings' join_exit_values to used_values
        // UNLESS they are function params. Params should NOT be remapped (they're defined
        // by call site Copies and used directly in k_exit body).
        // Note: all_params was already built above for condition_bindings check.

        for binding in &boundary.exit_bindings {
            if all_params.contains(&binding.join_exit_value) {
                trace.stderr_if(
                    &format!(
                        "[cf_loop/joinir] Phase 256 P1.10: Skipping exit binding '{}' (JoinIR {:?} is a param)",
                        binding.carrier_name, binding.join_exit_value
                    ),
                    debug,
                );
            } else {
                trace.stderr_if(
                    &format!(
                        "[cf_loop/joinir] Phase 172-3: Adding exit binding '{}' JoinIR {:?} to used_values",
                        binding.carrier_name, binding.join_exit_value
                    ),
                    debug,
                );
                used_values.insert(binding.join_exit_value);
            }
        }
    }

    // Phase 201-A + Phase 287 P0.4: Pre-build loop header PHIs BEFORE Phase 3
    //
    // We need to allocate PHI dst ValueIds before remap_values() runs,
    // to prevent conflicts where a Const instruction gets a ValueId that
    // will later be used as a PHI dst, causing carrier value corruption.
    let (mut loop_header_phi_info, merge_entry_block, reserved_value_ids) =
        header_phi_prebuild::prebuild_header_phis(
            builder,
            mir_module,
            boundary,
            &remapper,
            &function_params,
            debug,
        )?;
    if let Some(boundary) = boundary {
        contract_checks::verify_header_phi_layout(boundary, &loop_header_phi_info)?;
    }

    // Phase 3: Remap ValueIds (with reserved PHI dsts protection)
    // Phase 287 P0.2: Delegated to value_remapper module
    value_remapper::remap_values(
        builder,
        &used_values,
        &mut remapper,
        &reserved_value_ids,
        debug,
    )?;

    // Phase 177-3 DEBUG: Verify remapper state after Phase 3
    trace.stderr_if("[DEBUG-177] === Remapper state after Phase 3 ===", verbose);
    trace.stderr_if(
        &format!("[DEBUG-177] used_values count: {}", used_values.len()),
        verbose,
    );
    for value_id in &used_values {
        if let Some(remapped) = remapper.get_value(*value_id) {
            trace.stderr_if(
                &format!("[DEBUG-177]   JoinIR {:?} → Host {:?}", value_id, remapped),
                verbose,
            );
        } else {
            trace.stderr_if(
                &format!("[DEBUG-177]   JoinIR {:?} → NOT FOUND ❌", value_id),
                verbose,
            );
        }
    }

    // Check condition_bindings specifically
    if let Some(boundary) = boundary {
        trace.stderr_if("[DEBUG-177] === Condition bindings check ===", verbose);
        for binding in &boundary.condition_bindings {
            let lookup_result = remapper.get_value(binding.join_value);
            trace.stderr_if(
                &format!(
                    "[DEBUG-177]   '{}': JoinIR {:?} → {:?}",
                    binding.name, binding.join_value, lookup_result
                ),
                verbose,
            );
        }
    }
    trace.stderr_if("[DEBUG-177] ==============================", verbose);

    // Phase 3.5: Override remapper for function parameters to use PHI dsts
    //
    // Phase 201-A: This phase now uses the loop_header_phi_info built before Phase 3.
    // The PHI dst allocation has been moved earlier to prevent ValueId conflicts.
    if let Some(boundary) = boundary {
        if let Some(loop_var_name) = &boundary.loop_var_name {
            // Phase 201-A: PHI info is already built (before Phase 3) - just use it

            // Phase 33-21: Override remapper for loop_step's parameters
            //
            // JoinIR generates separate parameter ValueIds for each function:
            // - main(): boundary.join_inputs slots for (i_init, carrier1_init, ...)
            // - loop_step(): loop_step params for (i_param, carrier1_param, ...)
            //
            // The loop body uses loop_step's parameters, so we need to remap THOSE
            // to the header PHI dsts, not main()'s parameters.
            //
            // We get loop_step's parameters from function_params collected earlier.
            // Phase 33-21: Override remapper for ALL functions' parameters
            //
            // JoinIR generates separate parameter ValueIds for each function:
            // - main (join_func_0): boundary.join_inputs slots for (i_init, carrier1_init, ...)
            // - loop_step (join_func_1): loop_step params for (i_param, carrier1_param, ...)
            //
            // ALL of these need to be mapped to header PHI dsts so that:
            // 1. condition evaluation uses PHI result
            // 2. loop body uses PHI result
            // 3. tail call args are correctly routed

            // Phase 177-3 fix: Protect condition-ONLY bindings from being overridden to PHI dsts
            //
            // Problem: condition_bindings may contain:
            // 1. True condition-only variables (e.g., 'limit' in loop(i < limit)) - NOT carriers
            // 2. Body-only carriers added by Phase 176-5 (e.g., 'result') - ARE carriers
            //
            // We must ONLY protect (1), not (2), because:
            // - Condition-only vars should keep their HOST mapping (e.g., limit = %8)
            // - Body-only carriers MUST be remapped to PHI dsts (e.g., result = %24)
            //
            // Solution: Protect condition_bindings that are NOT in exit_bindings (i.e., not carriers)
            let carrier_names: std::collections::HashSet<&str> = boundary
                .exit_bindings
                .iter()
                .map(|eb| eb.carrier_name.as_str())
                .collect();

            let condition_binding_ids: std::collections::HashSet<ValueId> = boundary
                .condition_bindings
                .iter()
                .filter(|cb| !carrier_names.contains(cb.name.as_str()))
                .map(|cb| cb.join_value)
                .collect();

            if !condition_binding_ids.is_empty() {
                trace.stderr_if(
                    &format!(
                        "[cf_loop/joinir] Phase 177-3: Protected ValueIds (condition-only, not carriers): {:?}",
                        condition_binding_ids
                    ),
                    verbose,
                );
                for cb in &boundary.condition_bindings {
                    let is_carrier = carrier_names.contains(cb.name.as_str());
                    trace.stderr_if(
                        &format!(
                            "[cf_loop/joinir] Phase 177-3:   '{}': JoinIR {:?} (carrier={})",
                            cb.name, cb.join_value, is_carrier
                        ),
                        verbose,
                    );
                }
            }

            let canonical_main = crate::mir::join_ir::lowering::canonical_names::MAIN;
            let canonical_loop_step = crate::mir::join_ir::lowering::canonical_names::LOOP_STEP;
            let main_func_name = if function_params.contains_key(canonical_main) {
                canonical_main
            } else {
                "join_func_0"
            };
            let loop_step_func_name = if function_params.contains_key(canonical_loop_step) {
                canonical_loop_step
            } else {
                "join_func_1"
            };

            if function_params.get(main_func_name).is_none() {
                trace.stderr_if(
                    &format!(
                        "[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}",
                        main_func_name,
                        function_params.keys().collect::<Vec<_>>()
                    ),
                    verbose,
                );
            }
            let layout = boundary_carrier_layout::BoundaryCarrierLayout::from_boundary(boundary);
            let layout_names = layout.ordered_names();
            if let Some(main_params) = function_params.get(main_func_name) {
                trace.stderr_if(
                    &format!(
                        "[DEBUG-177] Phase 33-21: main ({}) params: {:?}",
                        main_func_name, main_params
                    ),
                    verbose,
                );
                trace.stderr_if(
                    &format!(
                        "[DEBUG-177] Phase 33-21: carrier_phis count: {}, names: {:?}",
                        loop_header_phi_info.carrier_phis.len(),
                        loop_header_phi_info
                            .carrier_phis
                            .iter()
                            .map(|(n, _)| n.as_str())
                            .collect::<Vec<_>>()
                    ),
                    verbose,
                );
                // Map main's parameters to header PHI dsts.
                for (idx, &main_param) in main_params.iter().enumerate() {
                    let Some(carrier_name) = layout_names.get(idx) else {
                        continue;
                    };
                    let Some(entry) = loop_header_phi_info.carrier_phis.get(*carrier_name) else {
                        continue;
                    };

                    // Phase 177-3: Don't override condition_bindings
                    if condition_binding_ids.contains(&main_param) {
                        trace.stderr_if(
                            &format!(
                                "[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')",
                                main_param, carrier_name
                            ),
                            verbose,
                        );
                        continue;
                    }

                    trace.stderr_if(
                        &format!(
                            "[DEBUG-177] Phase 33-21: REMAP main param[{}] {:?} → {:?} (carrier '{}')",
                            idx, main_param, entry.phi_dst, carrier_name
                        ),
                        verbose,
                    );
                    remapper.set_value(main_param, entry.phi_dst);
                }
            }

            // Phase 177-3-B: Handle body-only carriers
            // These are carriers in carrier_phis that are NOT in main function params.
            // They appear in condition_bindings (added by Phase 176-5) but need PHI remapping.
            for (carrier_name, entry) in &loop_header_phi_info.carrier_phis {
                // Check if this carrier has a condition_binding
                if let Some(binding) = boundary
                    .condition_bindings
                    .iter()
                    .find(|cb| cb.name == *carrier_name)
                {
                    // Skip if it's a true condition-only variable (already protected above)
                    if condition_binding_ids.contains(&binding.join_value) {
                        continue;
                    }
                    // This is a body-only carrier - remap it to PHI dst
                    trace.stderr_if(
                        &format!(
                            "[cf_loop/joinir] Phase 177-3-B: Body-only carrier '{}': JoinIR {:?} → PHI {:?}",
                            carrier_name, binding.join_value, entry.phi_dst
                        ),
                        verbose,
                    );
                    remapper.set_value(binding.join_value, entry.phi_dst);
                }
            }

            // Map loop_step's parameters
            // DEBUG-177: Always log function_params keys to diagnose multi-carrier issue
            trace.stderr_if(
                &format!(
                    "[DEBUG-177] Phase 33-21: function_params keys: {:?}",
                    function_params.keys().collect::<Vec<_>>()
                ),
                verbose,
            );
            if function_params.get(loop_step_func_name).is_none() {
                trace.stderr_if(
                    &format!(
                        "[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}",
                        loop_step_func_name,
                        function_params.keys().collect::<Vec<_>>()
                    ),
                    verbose,
                );
            }
            if let Some(loop_step_params) = function_params.get(loop_step_func_name) {
                // DEBUG-177: Always log loop_step params
                trace.stderr_if(
                    &format!(
                        "[DEBUG-177] Phase 33-21: loop_step ({}) params: {:?}",
                        loop_step_func_name, loop_step_params
                    ),
                    verbose,
                );
                // Phase 177-FIX: Process loop_step params but skip if already mapped
                //
                // We use a name-based approach: for each carrier_phi, check if
                // its join_value was already set in Phase 177-3-B (body-only carriers).
                // Only process loop_step params for carriers NOT already handled.
                for loop_step_param in loop_step_params {
                    // Phase 177-3: Don't override condition_bindings
                    if condition_binding_ids.contains(loop_step_param) {
                        trace.stderr_if(
                            &format!(
                                "[DEBUG-177] Phase 177-FIX: Skipping condition_binding {:?}",
                                loop_step_param
                            ),
                            verbose,
                        );
                        continue;
                    }
                    // Find which carrier this param belongs to by matching join_value
                    // Check if this param was already handled by Phase 177-3-B
                    let already_mapped = boundary.condition_bindings.iter().any(|cb| {
                        cb.join_value == *loop_step_param
                            && loop_header_phi_info
                                .carrier_phis
                                .iter()
                                .any(|(name, _)| name == &cb.name)
                    });
                    if already_mapped {
                        trace.stderr_if(
                            &format!(
                                "[DEBUG-177] Phase 177-FIX: Skipping {:?} (already mapped by Phase 177-3-B)",
                                loop_step_param
                            ),
                            verbose,
                        );
                        continue;
                    }
                    // Phase 29ag P0: Use BoundaryCarrierLayout for index-based matching.
                    if let Some(param_idx) =
                        loop_step_params.iter().position(|p| p == loop_step_param)
                    {
                        // Map params[i] to carrier_order[i]
                        if let Some(carrier_name) = layout_names.get(param_idx) {
                            let entry = match loop_header_phi_info.carrier_phis.get(*carrier_name) {
                                Some(entry) => entry,
                                None => continue,
                            };
                            trace.stderr_if(
                                &format!(
                                    "[DEBUG-177] Phase 177-STRUCT-2: REMAP loop_step param[{}] {:?} → {:?} (carrier '{}')",
                                    param_idx, loop_step_param, entry.phi_dst, carrier_name
                                ),
                                verbose,
                            );
                            remapper.set_value(*loop_step_param, entry.phi_dst);
                        }
                    }
                }
            }

            if function_params.get(main_func_name).is_none()
                && function_params.get(loop_step_func_name).is_none()
            {
                // Fallback: Use boundary.join_inputs for remap (no ValueId(idx) assumption).
                if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(loop_var_name) {
                    let join_ids = boundary.join_inputs.as_slice();
                    let loop_var_idx = layout_names
                        .iter()
                        .position(|name| *name == loop_var_name);
                    if let Some(idx) = loop_var_idx {
                        if let Some(&join_value_id) = join_ids.get(idx) {
                            // Phase 177-3: Don't override condition_bindings
                            if !condition_binding_ids.contains(&join_value_id) {
                                remapper.set_value(join_value_id, phi_dst);
                                trace.stderr_if(
                                    &format!(
                                        "[cf_loop/joinir] Phase 33-16 fallback: Override remap {:?} → {:?} (PHI dst)",
                                        join_value_id, phi_dst
                                    ),
                                    debug,
                                );
                            } else {
                                trace.stderr_if(
                                    &format!(
                                        "[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding {:?}",
                                        join_value_id
                                    ),
                                    verbose,
                                );
                            }
                        }
                    }
                }
                // Phase 29ag P1: Use boundary.join_inputs for remap instead of ValueId(idx).
                let join_ids = boundary.join_inputs.as_slice();
                for (idx, carrier_name) in layout_names.iter().enumerate() {
                    if *carrier_name == loop_var_name {
                        continue;
                    }
                    let entry = match loop_header_phi_info.carrier_phis.get(*carrier_name) {
                        Some(e) => e,
                        None => continue,
                    };
                    let Some(&join_value_id) = join_ids.get(idx) else {
                        continue;
                    };
                    // Phase 177-3: Don't override condition_bindings
                    if !condition_binding_ids.contains(&join_value_id) {
                        remapper.set_value(join_value_id, entry.phi_dst);
                        trace.stderr_if(
                            &format!(
                                "[cf_loop/joinir] Phase 33-20 fallback: Override remap {:?} → {:?} (carrier '{}' PHI dst)",
                                join_value_id, entry.phi_dst, carrier_name
                            ),
                            debug,
                        );
                    } else {
                        trace.stderr_if(
                            &format!(
                                "[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding {:?} ('{}')",
                                join_value_id, carrier_name
                            ),
                            verbose,
                        );
                    }
                }
            }

            // Phase 177-3 DEBUG: Check remapper after Phase 33-21 overrides
            trace.stderr_if("[DEBUG-177] === Remapper state after Phase 33-21 ===", verbose);
            for binding in &boundary.condition_bindings {
                let lookup_result = remapper.get_value(binding.join_value);
                trace.stderr_if(
                    &format!(
                        "[DEBUG-177]   '{}': JoinIR {:?} → {:?} (after 33-21)",
                        binding.name, binding.join_value, lookup_result
                    ),
                    verbose,
                );
            }

            // Phase 201-A: loop_header_phi_info already built (no assignment needed)
        }
    }

    // Phase 4: Merge blocks and rewrite instructions
    // Phase 33-16: Pass mutable loop_header_phi_info for latch_incoming tracking
    // Phase 177-3: Pass exit_block_id from allocator to avoid conflicts
    // Phase 260 P0.1: Use rewriter module (re-exports instruction_rewriter)
    let merge_result = rewriter::merge_and_rewrite(
        builder,
        mir_module,
        &mut remapper,
        &value_to_func_name,
        &function_params,
        boundary,
        &mut loop_header_phi_info,
        exit_block_id,
        debug,
    )?;

    // Phase 4.5: Finalize loop header PHIs (insert into header block)
    //
    // By now, rewriter has set latch_incoming for all carriers.
    // We can finalize the PHIs and insert them into the header block.
    if !loop_header_phi_info.carrier_phis.is_empty() {
        trace.stderr_if(
            &format!(
                "[cf_loop/joinir] Phase 4.5: Finalizing {} header PHIs",
                loop_header_phi_info.carrier_phis.len()
            ),
            debug,
        );
        LoopHeaderPhiBuilder::finalize(builder, &loop_header_phi_info, debug)?;
    }

    // Contract check (Fail-Fast): ensure we didn't leave dangling Jump/Branch targets.
    // Phase 131 Task 6: Use MergeConfig.strict_mode instead of env checks
    if config.strict_mode || config.dev_log {
        if let Some(ref current_func) = builder.scope_ctx.current_function {
            // Note: exit_block_id may be allocated but not inserted yet (it becomes the
            // current block after merge, and subsequent AST lowering fills it).
            // We still want to catch truly dangling targets (e.g., jumps to skipped k_exit).
            let contracts = MergeContracts {
                allowed_missing_jump_targets: vec![merge_result.exit_block_id],
            };
            contract_checks::verify_all_terminator_targets_exist(current_func, &contracts)?;
        }
    }

    // Phase 5: Build exit PHI (expr result only, not carrier PHIs)
    // Phase 33-20: Carrier PHIs are now taken from header PHI info, not exit block
    // Phase 246-EX: REVERT Phase 33-20 - Use EXIT PHI dsts, not header PHI dsts!
    // Phase 131 P1.5: DirectValue mode completely skips PHI generation
    trace.stderr_if(
        &format!(
            "[cf_loop/joinir] Phase 131 P1.5 DEBUG: boundary={:?}, mode={:?}",
            boundary.is_some(),
            boundary.map(|b| b.exit_reconnect_mode)
        ),
        debug,
    );

    // Phase 131 P1.5: Check if DirectValue mode (skip PHI generation)
    let is_direct_value_mode = boundary
        .map(|b| b.exit_reconnect_mode == crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode::DirectValue)
        .unwrap_or(false);

    // Phase 131 P1.5: Mode detection (dev-only visibility)
    trace.stderr_if(
        &format!(
            "[cf_loop/joinir] Phase 131 P1.5: exit_reconnect_mode={:?}, is_direct_value_mode={}",
            boundary.map(|b| b.exit_reconnect_mode),
            is_direct_value_mode
        ),
        debug || config.dev_log,
    );

    let (exit_phi_result_id, exit_carrier_phis) = if is_direct_value_mode {
        // DirectValue mode: Skip PHI generation completely
        trace.stderr_if(
            "[cf_loop/joinir] Phase 131 P1.5: DirectValue mode - skipping exit PHI generation",
            debug,
        );
        (None, BTreeMap::new())
    } else {
        // Phi mode: Generate exit PHIs as usual
        trace.stderr_if(
            "[cf_loop/joinir] Phase 131 P1.5: Phi mode - generating exit PHIs",
            debug,
        );
        exit_phi_builder::build_exit_phi(
            builder,
            merge_result.exit_block_id,
            &merge_result.exit_phi_inputs,
            &merge_result.carrier_inputs,
            debug,
        )?
    };

    // Phase 118 P2: Contract check (Fail-Fast) - exit_bindings LoopState carriers must have exit PHIs.
    // Phase 131 P1.5: Skip this check in DirectValue mode
    if let Some(boundary) = boundary {
        if !is_direct_value_mode {
            contract_checks::verify_exit_bindings_have_exit_phis(boundary, &exit_carrier_phis)?;
        }
    }

    // Phase 118 P1: Dev-only carrier-phi SSOT logs (exit_bindings vs carrier_inputs vs exit_carrier_phis)
    // Phase 131 Task 6: Use config.dev_log instead of env check
    if config.dev_log {
        if let Some(boundary) = boundary {
            let exit_binding_names: Vec<&str> = boundary
                .exit_bindings
                .iter()
                .map(|b| b.carrier_name.as_str())
                .collect();
            let carrier_input_names: Vec<&str> =
                merge_result.carrier_inputs.keys().map(|s| s.as_str()).collect();
            let exit_phi_names: Vec<&str> =
                exit_carrier_phis.keys().map(|s| s.as_str()).collect();

            trace.stderr_if(
                &format!(
                    "[joinir/phase118/dev] exit_bindings carriers={:?}",
                    exit_binding_names
                ),
                true,
            );
            trace.stderr_if(
                &format!(
                    "[joinir/phase118/dev] carrier_inputs keys={:?}",
                    carrier_input_names
                ),
                true,
            );
            trace.stderr_if(
                &format!(
                    "[joinir/phase118/dev] exit_carrier_phis keys={:?}",
                    exit_phi_names
                ),
                true,
            );
        }
    }

    // Phase 246-EX: CRITICAL FIX - Use exit PHI dsts for variable_map reconnection
    //
    // **Why EXIT PHI, not HEADER PHI?**
    //
    // Header PHI represents the value at the BEGINNING of each iteration.
    // Exit PHI represents the FINAL value when leaving the loop (from any exit path).
    //
    // For Pattern 2 loops with multiple exit paths (natural exit + break):
    // - Header PHI: `%15 = phi [%3, bb7], [%42, bb14]` (loop variable at iteration start)
    // - Exit PHI:   `%5 = phi [%15, bb11], [%15, bb13]` (final value from exit paths)
    //
    // When we exit the loop, we want the FINAL value (%5), not the iteration-start value (%15).
    // Phase 33-20 incorrectly used header PHI, causing loops to return initial values (e.g., 0 instead of 42).
    //
    // Example (_atoi):
    // - Initial: result=0 (header PHI)
    // - After iteration 1: result=4 (updated in loop body)
    // - After iteration 2: result=42 (updated in loop body)
    // - Exit: Should return 42 (exit PHI), not 0 (header PHI initial value)
    //
    // The exit PHI correctly merges values from both exit paths, giving us the final result.
    let carrier_phis = &exit_carrier_phis;

    trace.stderr_if(
        &format!(
            "[cf_loop/joinir] Phase 246-EX: Using EXIT PHI dsts for variable_map (not header): {:?}",
            carrier_phis
                .iter()
                .map(|(n, v)| (n.as_str(), v))
                .collect::<Vec<_>>()
        ),
        debug && !carrier_phis.is_empty(),
    );

    // Phase 6: Reconnect boundary (if specified)
    // Phase 197-B: Pass remapper to enable per-carrier exit value lookup
    // Phase 33-10-Refactor-P3: Delegate to ExitLineOrchestrator
    // Phase 246-EX: Now uses EXIT PHI dsts (reverted Phase 33-20)
    // Phase 131 P2: DirectValue mode SSOT uses MergeResult.remapped_exit_values
    let remapped_exit_values = merge_result.remapped_exit_values.clone();

    if let Some(boundary) = boundary {
        exit_line::ExitLineOrchestrator::execute(
            builder,
            boundary,
            carrier_phis,
            &remapped_exit_values, // Phase 131 P1.5: Now populated with exit PHI dsts
            debug,
        )?;
    }

    let exit_block_id = merge_result.exit_block_id;

    // Phase 256.7-fix: Use merge_entry_block for the Jump
    // This is the block where boundary Copies are injected (main's entry when condition_bindings exist).
    // The host should Jump here first, then main's tail call jumps to the loop header.
    let entry_block = merge_entry_block;

    trace.stderr_if(
        &format!(
            "[cf_loop/joinir] Phase 256.7-fix: Entry block (merge_entry_block): {:?}, loop_header={:?}",
            entry_block, loop_header_phi_info.header_block
        ),
        debug,
    );
    trace.stderr_if(
        &format!(
            "[cf_loop/joinir]   Current block before emit_jump: {:?}",
            builder.current_block
        ),
        debug,
    );
    trace.stderr_if(
        &format!(
            "[cf_loop/joinir]   Jumping to entry block: {:?}",
            entry_block
        ),
        debug,
    );

    crate::mir::builder::emission::branch::emit_jump(builder, entry_block)?;

    trace.stderr_if(
        &format!(
            "[cf_loop/joinir]   After emit_jump, current_block: {:?}",
            builder.current_block
        ),
        debug,
    );

    // Switch to exit block for subsequent code
    builder.start_new_block(exit_block_id)?;

    // Phase 287 P0.5: Delegated to boundary_logging module
    boundary_logging::log_merge_complete(mir_module.functions.len(), exit_block_id, &trace, debug);

    // Phase 200-3: Verify JoinIR contracts (debug only)
    #[cfg(debug_assertions)]
    {
        if let Some(boundary) = boundary {
            if let Some(ref func) = builder.scope_ctx.current_function {
                debug_assertions::verify_joinir_contracts(
                    func,
                    loop_header_phi_info.header_block,
                    exit_block_id,
                    &loop_header_phi_info,
                    boundary,
                );
            }
            trace.stderr_if(
                "[cf_loop/joinir] Phase 200-3: Contract verification passed",
                debug,
            );
        }
    }

    // Phase 246-EX-FIX: Handle loop variable expr_result separately from carrier expr_result
    //
    // The loop variable (e.g., 'i') is returned via exit_phi_result_id, not carrier_phis.
    // Other carriers use carrier_phis. We need to check which case we're in.
    let expr_result_value = if let Some(b) = boundary {
        if let Some(expr_result_id) = b.expr_result {
            // Check if expr_result is the loop variable
            if let Some(loop_var_name) = &b.loop_var_name {
                // Find the exit binding for the loop variable
                let loop_var_binding = b
                    .exit_bindings
                    .iter()
                    .find(|binding| binding.carrier_name == *loop_var_name);

                if let Some(binding) = loop_var_binding {
                    if binding.join_exit_value == expr_result_id {
                        // expr_result is the loop variable! Use exit_phi_result_id
                        trace.stderr_if(
                            &format!(
                                "[cf_loop/joinir] Phase 246-EX-FIX: expr_result {:?} is loop variable '{}', using exit_phi_result_id {:?}",
                                expr_result_id, loop_var_name, exit_phi_result_id
                            ),
                            debug,
                        );
                        exit_phi_result_id
                    } else {
                        // expr_result is not the loop variable, resolve as carrier
                        expr_result_resolver::ExprResultResolver::resolve(
                            Some(expr_result_id),
                            b.exit_bindings.as_slice(),
                            &carrier_phis,
                            &remapper,
                            debug,
                        )?
                    }
                } else {
                    // No loop variable binding, resolve normally
                    expr_result_resolver::ExprResultResolver::resolve(
                        Some(expr_result_id),
                        b.exit_bindings.as_slice(),
                        &carrier_phis,
                        &remapper,
                        debug,
                    )?
                }
            } else {
                // No loop variable name, resolve normally
                expr_result_resolver::ExprResultResolver::resolve(
                    Some(expr_result_id),
                    b.exit_bindings.as_slice(),
                    &carrier_phis,
                    &remapper,
                    debug,
                )?
            }
        } else {
            None
        }
    } else {
        None
    };

    // Return expr_result if present, otherwise fall back to exit_phi_result_id
    if let Some(resolved) = expr_result_value {
        trace.stderr_if(
            &format!(
                "[cf_loop/joinir] Phase 246-EX-FIX: Returning expr_result_value {:?}",
                resolved
            ),
            debug,
        );
        Ok(Some(resolved))
    } else {
        // Fallback: return exit_phi_result_id (for legacy patterns or carrier-only loops)
        trace.stderr_if(
            &format!(
                "[cf_loop/joinir] Phase 221-R: Returning exit_phi_result_id (fallback): {:?}",
                exit_phi_result_id
            ),
            debug && exit_phi_result_id.is_some(),
        );
        Ok(exit_phi_result_id)
    }
}
