use super::CoreEffectPlan;
use crate::mir::builder::MirBuilder;
use crate::mir::{BasicBlockId, BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType, ValueId};
use std::collections::BTreeMap;

// ============================================================================
// Phase 286 P2.8: Normalizer Hygiene Helpers
// ============================================================================

/// Standard 5-block layout for simple loops (Pattern1/4/8/9)
///
/// CFG: preheader → header → body → step → header (back-edge)
///                      ↓
///                   after
#[derive(Debug, Clone, Copy)]
pub(super) struct LoopBlocksStandard5 {
    pub(super) preheader_bb: BasicBlockId,
    pub(super) header_bb: BasicBlockId,
    pub(super) body_bb: BasicBlockId,
    pub(super) step_bb: BasicBlockId,
    pub(super) after_bb: BasicBlockId,
}

impl LoopBlocksStandard5 {
    /// Allocate 5 blocks for a standard loop
    pub(super) fn allocate(builder: &mut MirBuilder) -> Result<Self, String> {
        let preheader_bb = builder
            .current_block
            .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
        let header_bb = builder.next_block_id();
        let body_bb = builder.next_block_id();
        let step_bb = builder.next_block_id();
        let after_bb = builder.next_block_id();
        Ok(Self {
            preheader_bb,
            header_bb,
            body_bb,
            step_bb,
            after_bb,
        })
    }
}

/// Extended 8-block layout for if-phi loops (Pattern3)
///
/// CFG: preheader → header → body → then/else → merge → step → header
///                      ↓
///                   after
#[derive(Debug, Clone, Copy)]
pub(super) struct LoopBlocksWithIfPhi {
    pub(super) preheader_bb: BasicBlockId,
    pub(super) header_bb: BasicBlockId,
    pub(super) body_bb: BasicBlockId,
    pub(super) then_bb: BasicBlockId,
    pub(super) else_bb: BasicBlockId,
    pub(super) merge_bb: BasicBlockId,
    pub(super) step_bb: BasicBlockId,
    pub(super) after_bb: BasicBlockId,
}

impl LoopBlocksWithIfPhi {
    /// Allocate 8 blocks for an if-phi loop
    pub(super) fn allocate(builder: &mut MirBuilder) -> Result<Self, String> {
        let preheader_bb = builder
            .current_block
            .ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
        let header_bb = builder.next_block_id();
        let body_bb = builder.next_block_id();
        let then_bb = builder.next_block_id();
        let else_bb = builder.next_block_id();
        let merge_bb = builder.next_block_id();
        let step_bb = builder.next_block_id();
        let after_bb = builder.next_block_id();
        Ok(Self {
            preheader_bb,
            header_bb,
            body_bb,
            then_bb,
            else_bb,
            merge_bb,
            step_bb,
            after_bb,
        })
    }
}

/// Create phi_bindings map from variable name-ValueId pairs
///
/// phi_bindings are used to override variable_map lookups during AST lowering,
/// ensuring loop variables reference PHI destinations instead of initial values.
pub(super) fn create_phi_bindings(bindings: &[(&str, ValueId)]) -> BTreeMap<String, ValueId> {
    bindings
        .iter()
        .map(|(name, id)| (name.to_string(), *id))
        .collect()
}

impl super::PlanNormalizer {
    /// Helper: Lower Compare AST to (lhs ValueId, op, rhs ValueId, const_effects)
    pub(super) fn lower_compare_ast(
        ast: &crate::ast::ASTNode,
        builder: &mut MirBuilder,
        phi_bindings: &BTreeMap<String, ValueId>,
    ) -> Result<(ValueId, CompareOp, ValueId, Vec<CoreEffectPlan>), String> {
        use crate::ast::{ASTNode, BinaryOperator};

        match ast {
            ASTNode::BinaryOp { operator, left, right, .. } => {
                let op = match operator {
                    BinaryOperator::Less => CompareOp::Lt,
                    BinaryOperator::LessEqual => CompareOp::Le,
                    BinaryOperator::Greater => CompareOp::Gt,
                    BinaryOperator::GreaterEqual => CompareOp::Ge,
                    BinaryOperator::Equal => CompareOp::Eq,
                    BinaryOperator::NotEqual => CompareOp::Ne,
                    _ => {
                        return Err(format!(
                            "[normalizer] Unsupported compare operator: {:?}",
                            operator
                        ))
                    }
                };

                let (lhs, mut lhs_consts) = Self::lower_value_ast(left, builder, phi_bindings)?;
                let (rhs, rhs_consts) = Self::lower_value_ast(right, builder, phi_bindings)?;

                lhs_consts.extend(rhs_consts);

                Ok((lhs, op, rhs, lhs_consts))
            }
            _ => Err(format!(
                "[normalizer] Expected BinaryOp for compare, got: {:?}",
                ast
            )),
        }
    }

    /// Helper: Lower BinOp AST to (lhs ValueId, op, rhs ValueId, const_effects)
    pub(super) fn lower_binop_ast(
        ast: &crate::ast::ASTNode,
        builder: &mut MirBuilder,
        phi_bindings: &BTreeMap<String, ValueId>,
    ) -> Result<(ValueId, BinaryOp, ValueId, Vec<CoreEffectPlan>), String> {
        use crate::ast::{ASTNode, BinaryOperator};

        match ast {
            ASTNode::BinaryOp { operator, left, right, .. } => {
                let op = match operator {
                    BinaryOperator::Add => BinaryOp::Add,
                    BinaryOperator::Subtract => BinaryOp::Sub,
                    BinaryOperator::Multiply => BinaryOp::Mul,
                    BinaryOperator::Divide => BinaryOp::Div,
                    _ => {
                        return Err(format!(
                            "[normalizer] Unsupported binary operator: {:?}",
                            operator
                        ))
                    }
                };

                let (lhs, mut lhs_consts) = Self::lower_value_ast(left, builder, phi_bindings)?;
                let (rhs, rhs_consts) = Self::lower_value_ast(right, builder, phi_bindings)?;

                lhs_consts.extend(rhs_consts);

                Ok((lhs, op, rhs, lhs_consts))
            }
            _ => Err(format!("[normalizer] Expected BinOp, got: {:?}", ast)),
        }
    }

    /// Helper: Lower value AST to (ValueId, const_effects)
    /// Returns the ValueId and any Const instructions needed to define literals
    ///
    /// phi_bindings: PHI dst for loop variables (takes precedence over variable_map)
    pub(super) fn lower_value_ast(
        ast: &crate::ast::ASTNode,
        builder: &mut MirBuilder,
        phi_bindings: &BTreeMap<String, ValueId>,
    ) -> Result<(ValueId, Vec<CoreEffectPlan>), String> {
        use crate::ast::{ASTNode, LiteralValue};

        match ast {
            ASTNode::Variable { name, .. } => {
                if let Some(&phi_dst) = phi_bindings.get(name) {
                    return Ok((phi_dst, vec![]));
                }
                if let Some(&value_id) = builder.variable_ctx.variable_map.get(name) {
                    Ok((value_id, vec![]))
                } else {
                    Err(format!("[normalizer] Variable {} not found", name))
                }
            }
            ASTNode::Literal { value, .. } => {
                let value_id = builder.next_value_id();
                let const_value = match value {
                    LiteralValue::Integer(n) => ConstValue::Integer(*n),
                    LiteralValue::String(s) => ConstValue::String(s.clone()),
                    LiteralValue::Bool(b) => ConstValue::Bool(*b),
                    _ => {
                        return Err(format!(
                            "[normalizer] Unsupported literal type: {:?}",
                            value
                        ))
                    }
                };

                builder.type_ctx.value_types.insert(value_id, MirType::Integer);

                let const_effect = CoreEffectPlan::Const {
                    dst: value_id,
                    value: const_value,
                };

                Ok((value_id, vec![const_effect]))
            }
            ASTNode::MethodCall { object, method, arguments, .. } => {
                let object_name = match object.as_ref() {
                    ASTNode::Variable { name, .. } => name.clone(),
                    _ => {
                        return Err(format!(
                            "[normalizer] Method call on non-variable object: {:?}",
                            object
                        ))
                    }
                };

                let object_id = if let Some(&phi_dst) = phi_bindings.get(&object_name) {
                    phi_dst
                } else if let Some(&value_id) = builder.variable_ctx.variable_map.get(&object_name) {
                    value_id
                } else {
                    return Err(format!(
                        "[normalizer] Method call object {} not found",
                        object_name
                    ));
                };

                let mut arg_ids = Vec::new();
                let mut arg_effects = Vec::new();
                for arg in arguments {
                    let (arg_id, mut effects) = Self::lower_value_ast(arg, builder, phi_bindings)?;
                    arg_ids.push(arg_id);
                    arg_effects.append(&mut effects);
                }

                let result_id = builder.next_value_id();
                builder.type_ctx.value_types.insert(result_id, MirType::Integer);

                arg_effects.push(CoreEffectPlan::MethodCall {
                    dst: Some(result_id),
                    object: object_id,
                    method: method.clone(),
                    args: arg_ids,
                    effects: EffectMask::PURE.add(Effect::Io),
                });

                Ok((result_id, arg_effects))
            }
            _ => Err(format!("[normalizer] Unsupported value AST: {:?}", ast)),
        }
    }
}
