//! Loop Header PHI Builder
//!
//! Phase 33-16: Generates PHI nodes at loop header blocks to track carrier values.
//! Phase 33-17-B: Refactored to separate data structures from builder logic.
//!
//! ## Problem
//!
//! JoinIR uses function parameters (i_param, i_exit) to pass values between
//! iterations. When inlined into MIR, these parameters have no SSA definition.
//! This causes SSA-undef errors when exit PHIs reference remapped parameters.
//!
//! ## Solution
//!
//! Generate a PHI node at the loop header block for each carrier variable:
//!
//! ```text
//! loop_header:
//!   i_phi = PHI [(entry_block, i_init), (latch_block, i_next)]
//!   // rest of loop...
//! ```
//!
//! The PHI's dst becomes the "current value" of the carrier during iteration.
//! Exit paths should reference this PHI dst, not the undefined parameters.
//!
//! ## Usage
//!
//! Called from merge pipeline between Phase 3 (remap_values) and Phase 4
//! (instruction_rewriter).

use super::loop_header_phi_info::{CarrierPhiEntry, LoopHeaderPhiInfo};
use super::super::trace;
use crate::mir::{BasicBlockId, MirInstruction, ValueId};

/// Builder for loop header PHIs
///
/// Generates PHI nodes at the loop header block to track carrier values.
pub struct LoopHeaderPhiBuilder;

impl LoopHeaderPhiBuilder {
    /// Generate header PHIs for loop carriers
    ///
    /// # Arguments
    ///
    /// * `builder` - MirBuilder for allocating ValueIds
    /// * `header_block` - The loop header block ID
    /// * `entry_block` - The block that jumps to header on first iteration
    /// * `carrier_info` - Carrier variable metadata from pattern lowerer
    /// * `remapper` - ID remapper for looking up remapped init values
    /// * `debug` - Enable debug output
    ///
    /// # Returns
    ///
    /// LoopHeaderPhiInfo with allocated PHI dsts.
    /// Note: latch_incoming is not yet set - that happens in instruction_rewriter.
    ///
    /// # Phase 228 Update
    ///
    /// Added CarrierInit and CarrierRole to carrier tuples:
    /// * `CarrierInit::FromHost` - Use host_id directly as PHI init value
    /// * `CarrierInit::BoolConst(val)` - Generate explicit bool constant for ConditionOnly carriers
    /// * `CarrierInit::LoopLocalZero` - Generate const 0 for loop-local derived carriers (no host slot)
    pub fn build(
        builder: &mut crate::mir::builder::MirBuilder,
        header_block: BasicBlockId,
        entry_block: BasicBlockId,
        loop_var_name: &str,
        loop_var_init: ValueId,
        carriers: &[(
            String,
            ValueId,
            crate::mir::join_ir::lowering::carrier_info::CarrierInit,
            crate::mir::join_ir::lowering::carrier_info::CarrierRole,
        )], // Phase 228: Added CarrierInit and CarrierRole
        expr_result_is_loop_var: bool,
        debug: bool,
    ) -> Result<LoopHeaderPhiInfo, String> {
        let trace = trace::trace();
        if debug {
            trace.stderr_if(
                &format!(
                    "[cf_loop/joinir] Phase 33-16: Building header PHIs at {:?}",
                    header_block
                ),
                true,
            );
            trace.stderr_if(
                &format!(
                    "[cf_loop/joinir]   Loop var '{}' init={:?}, entry_block={:?}",
                    loop_var_name, loop_var_init, entry_block
                ),
                true,
            );
        }

        let mut info = LoopHeaderPhiInfo::empty(header_block);

        // Allocate PHI for loop variable
        let loop_var_phi_dst = builder.next_value_id();

        // Phase 72: Observe PHI dst allocation
        #[cfg(debug_assertions)]
        crate::mir::join_ir::verify_phi_reserved::observe_phi_dst(loop_var_phi_dst);

        // Phase 131-11-H: Set PHI type from entry incoming (init value) only
        // Ignore backedge to avoid circular dependency in type inference
        if let Some(init_type) = builder.type_ctx.value_types.get(&loop_var_init).cloned() {
            builder
                .type_ctx
                .value_types
                .insert(loop_var_phi_dst, init_type.clone());

            trace.stderr_if(
                &format!(
                    "[carrier/phi] Loop var '{}': dst=%{} entry_type={:?} (backedge ignored)",
                    loop_var_name,
                    loop_var_phi_dst.as_u32(),
                    init_type
                ),
                debug || std::env::var("NYASH_CARRIER_PHI_DEBUG").ok().as_deref() == Some("1"),
            );
        }

        info.carrier_phis.insert(
            loop_var_name.to_string(),
            CarrierPhiEntry {
                phi_dst: loop_var_phi_dst,
                entry_incoming: (entry_block, loop_var_init),
                latch_incoming: None,
                role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, // Phase 227: Loop var is always LoopState
            },
        );
        // Phase 177-STRUCT-2: Record insertion order
        info.carrier_order.push(loop_var_name.to_string());

        if debug {
            trace.stderr_if(
                &format!(
                    "[cf_loop/joinir]   Loop var PHI: {:?} = phi [(from {:?}, {:?}), (latch TBD)]",
                    loop_var_phi_dst, entry_block, loop_var_init
                ),
                true,
            );
        }

        // Allocate PHIs for other carriers
        for (name, host_id, init, role) in carriers {
            // Phase 86: Use centralized CarrierInit builder
            let init_value =
                super::carrier_init_builder::init_value(builder, &init, *host_id, &name, debug);

            let phi_dst = builder.next_value_id();

            // Phase 72: Observe PHI dst allocation
            #[cfg(debug_assertions)]
            crate::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst);

            // Phase 131-11-H: Set PHI type from entry incoming (init value) only
            // Ignore backedge to avoid circular dependency in type inference
            if let Some(init_type) = builder.type_ctx.value_types.get(&init_value).cloned() {
                builder
                    .type_ctx
                    .value_types
                    .insert(phi_dst, init_type.clone());

                trace.stderr_if(
                    &format!(
                        "[carrier/phi] Carrier '{}': dst=%{} entry_type={:?} (backedge ignored)",
                        name,
                        phi_dst.as_u32(),
                        init_type
                    ),
                    debug || std::env::var("NYASH_CARRIER_PHI_DEBUG").ok().as_deref() == Some("1"),
                );
            }

            info.carrier_phis.insert(
                name.clone(),
                CarrierPhiEntry {
                    phi_dst,
                    entry_incoming: (entry_block, init_value),
                    latch_incoming: None,
                    role: *role, // Phase 228: Use role from carrier_info
                },
            );
            // Phase 177-STRUCT-2: Record insertion order
            info.carrier_order.push(name.clone());

            if debug {
                trace.stderr_if(
                    &format!(
                        "[cf_loop/joinir]   Carrier '{}' PHI: {:?} = phi [(from {:?}, {:?}), (latch TBD)], role={:?}",
                        name, phi_dst, entry_block, init_value, role
                    ),
                    true,
                );
            }
        }

        // Set expr_result if this pattern returns the loop var
        if expr_result_is_loop_var {
            info.expr_result_phi = Some(loop_var_phi_dst);
            if debug {
                trace.stderr_if(
                    &format!(
                        "[cf_loop/joinir]   expr_result = {:?} (loop var PHI)",
                        loop_var_phi_dst
                    ),
                    true,
                );
            }
        }

        Ok(info)
    }

    /// Finalize header PHIs by inserting them into the MIR block
    ///
    /// Called after instruction_rewriter has set all latch_incoming values.
    ///
    /// # Arguments
    ///
    /// * `builder` - MirBuilder containing the current function
    /// * `info` - LoopHeaderPhiInfo with all incoming edges set
    /// * `debug` - Enable debug output
    pub fn finalize(
        builder: &mut crate::mir::builder::MirBuilder,
        info: &LoopHeaderPhiInfo,
        debug: bool,
    ) -> Result<(), String> {
        let trace = trace::trace();
        let dev_debug = debug || crate::config::env::joinir_dev_enabled();
        if debug {
            trace.stderr_if(
                &format!(
                    "[cf_loop/joinir] Phase 33-16: Finalizing header PHIs at {:?}",
                    info.header_block
                ),
                true,
            );
        }

        // Validate all latch incoming are set
        for (name, entry) in &info.carrier_phis {
            if entry.latch_incoming.is_none() {
                return Err(format!(
                    "Phase 33-16: Carrier '{}' has no latch incoming set",
                    name
                ));
            }
        }

        // Get the header block from current function
        let current_func = builder
            .scope_ctx
            .current_function
            .as_mut()
            .ok_or("Phase 33-16: No current function when finalizing header PHIs")?;

        let header_block = current_func
            .blocks
            .get_mut(&info.header_block)
            .ok_or_else(|| {
                format!(
                    "Phase 33-16: Header block {:?} not found in current function",
                    info.header_block
                )
            })?;

        // Insert PHIs at the beginning of the header block (before other instructions)
        // Sorted by carrier name for determinism
        let mut phi_instructions: Vec<MirInstruction> = Vec::new();

        for (name, entry) in &info.carrier_phis {
            let (entry_block, entry_val) = entry.entry_incoming;
            let (latch_block, latch_val) = entry.latch_incoming.unwrap();

            let phi = MirInstruction::Phi {
                dst: entry.phi_dst,
                inputs: vec![(entry_block, entry_val), (latch_block, latch_val)],
                type_hint: None,
            };

            phi_instructions.push(phi);

            if dev_debug {
                trace.stderr_if(
                    &format!(
                        "[joinir/header-phi]   Finalized carrier '{}' PHI: {:?} = phi [({:?}, {:?}), ({:?}, {:?})]",
                        name, entry.phi_dst, entry_block, entry_val, latch_block, latch_val
                    ),
                    true,
                );
            }
        }

        // Prepend PHIs to existing instructions
        let mut new_instructions = phi_instructions;
        new_instructions.append(&mut header_block.instructions);
        header_block.instructions = new_instructions;

        // Also prepend spans for the PHIs
        let mut new_spans: Vec<crate::ast::Span> = (0..info.carrier_phis.len())
            .map(|_| crate::ast::Span::unknown())
            .collect();
        new_spans.append(&mut header_block.instruction_spans);
        header_block.instruction_spans = new_spans;

        if dev_debug {
            trace.stderr_if(
                &format!(
                    "[joinir/header-phi]   Header block now has {} instructions",
                    header_block.instructions.len()
                ),
                true,
            );
        }

        Ok(())
    }
}
