//! Loop Header PHI Information Structures
//!
//! Phase 33-17-B: Data structures for loop header PHI tracking.
//!
//! ## Overview
//!
//! These structures hold metadata about PHI nodes generated at loop headers
//! to track carrier variables. Used by:
//! - LoopHeaderPhiBuilder: to build PHIs
//! - exit_phi_builder: to reference current loop values
//! - ExitLineReconnector: to update variable_map with final values

use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
use crate::mir::{BasicBlockId, ValueId};
use std::collections::BTreeMap;

/// Information about loop header PHIs
///
/// Generated by LoopHeaderPhiBuilder and used by:
/// - exit_phi_builder: to reference the current loop value
/// - ExitLineReconnector: to update variable_map with final values
#[derive(Debug, Clone)]
pub struct LoopHeaderPhiInfo {
    /// The block where header PHIs are placed
    pub header_block: BasicBlockId,

    /// Carrier variable PHI mappings: carrier_name → PHI dst
    ///
    /// The PHI dst is the ValueId that represents the "current value"
    /// of this carrier during loop iteration.
    pub carrier_phis: BTreeMap<String, CarrierPhiEntry>,

    /// Phase 177-STRUCT-2: Carrier names in insertion order
    ///
    /// Preserves the order in which carriers were added (matches exit_bindings order).
    /// Used for index-based matching with loop_step params.
    ///
    /// Order: [loop_var, carrier1, carrier2, ...]
    pub carrier_order: Vec<String>,

    /// Expression result PHI dst (if loop is used as expression)
    ///
    /// For Pattern 2 (joinir_min_loop), this is the same as the loop
    /// variable's PHI dst. For carrier-only patterns (trim), this is None.
    pub expr_result_phi: Option<ValueId>,
}

/// Entry for a single carrier's header PHI
#[derive(Debug, Clone)]
pub struct CarrierPhiEntry {
    /// PHI destination ValueId (the "current value" during iteration)
    pub phi_dst: ValueId,

    /// Entry edge: (from_block, init_value)
    pub entry_incoming: (BasicBlockId, ValueId),

    /// Latch edge: (from_block, updated_value) - set after instruction rewrite
    pub latch_incoming: Option<(BasicBlockId, ValueId)>,

    /// Phase 227: Role of this carrier (LoopState or ConditionOnly)
    #[allow(dead_code)]
    pub role: CarrierRole,
}

impl LoopHeaderPhiInfo {
    /// Create empty LoopHeaderPhiInfo
    pub fn empty(header_block: BasicBlockId) -> Self {
        Self {
            header_block,
            carrier_phis: BTreeMap::new(),
            carrier_order: Vec::new(),
            expr_result_phi: None,
        }
    }

    /// Get the PHI dst for a carrier variable
    pub fn get_carrier_phi(&self, name: &str) -> Option<ValueId> {
        self.carrier_phis.get(name).map(|e| e.phi_dst)
    }

    /// Set latch incoming for a carrier
    ///
    /// Called from instruction_rewriter after processing tail call Copy instructions.
    pub fn set_latch_incoming(&mut self, name: &str, from_block: BasicBlockId, value: ValueId) {
        if let Some(entry) = self.carrier_phis.get_mut(name) {
            // Phase 287 P2 Fail-Fast: デバッグビルドのみ二重セット検知
            debug_assert!(
                entry.latch_incoming.is_none(),
                "Phase 287 P2 Fail-Fast: Double latch set for '{}'. Existing: {:?}, New: ({:?}, {:?})",
                name,
                entry.latch_incoming,
                from_block,
                value
            );
            entry.latch_incoming = Some((from_block, value));
        }
    }

    /// Check if all carriers have latch incoming set
    #[allow(dead_code)]
    pub fn all_latch_set(&self) -> bool {
        self.carrier_phis
            .values()
            .all(|e| e.latch_incoming.is_some())
    }

    /// Phase 201-A: Get reserved ValueIds (PHI dsts that must not be overwritten)
    ///
    /// Returns a set of ValueIds that are used as PHI destinations in the loop header.
    /// These IDs must not be reused for other purposes (e.g., Const instructions)
    /// to prevent corruption of carrier values.
    ///
    /// Includes:
    /// - All carrier PHI dst ValueIds
    /// - Expression result PHI dst (if present)
    pub fn reserved_value_ids(&self) -> std::collections::HashSet<ValueId> {
        let mut reserved = std::collections::HashSet::new();

        // Add all carrier PHI dsts
        for entry in self.carrier_phis.values() {
            reserved.insert(entry.phi_dst);
        }

        // Add expression result PHI dst (if present)
        if let Some(dst) = self.expr_result_phi {
            reserved.insert(dst);
        }

        reserved
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_loop_header_phi_info_creation() {
        let header = BasicBlockId(10);
        let info = LoopHeaderPhiInfo::empty(header);

        assert_eq!(info.header_block, header);
        assert!(info.carrier_phis.is_empty());
        assert!(info.expr_result_phi.is_none());
    }

    #[test]
    fn test_carrier_phi_entry() {
        let mut info = LoopHeaderPhiInfo::empty(BasicBlockId(10));

        info.carrier_phis.insert(
            "i".to_string(),
            CarrierPhiEntry {
                phi_dst: ValueId(100),
                entry_incoming: (BasicBlockId(1), ValueId(5)),
                latch_incoming: None,
                role: CarrierRole::LoopState,
            },
        );

        assert_eq!(info.get_carrier_phi("i"), Some(ValueId(100)));
        assert_eq!(info.get_carrier_phi("x"), None);
        assert!(!info.all_latch_set());

        info.set_latch_incoming("i", BasicBlockId(20), ValueId(50));
        assert!(info.all_latch_set());
    }

    #[test]
    #[should_panic(expected = "Double latch set")]
    fn set_latch_incoming_double_set_panics_in_debug() {
        let mut info = LoopHeaderPhiInfo::empty(BasicBlockId(9));
        info.carrier_phis.insert(
            "i".to_string(),
            CarrierPhiEntry {
                phi_dst: ValueId(100),
                entry_incoming: (BasicBlockId(1), ValueId(1)),
                latch_incoming: None,
                role: CarrierRole::LoopState,
            },
        );

        info.set_latch_incoming("i", BasicBlockId(2), ValueId(2));
        info.set_latch_incoming("i", BasicBlockId(3), ValueId(3));
    }
}
