//! NormalizationPlanBox: Pattern detection for Normalized shadow (Phase 134 P0)
//!
//! ## Responsibility
//!
//! - Detect block suffix patterns that can be normalized
//! - Return plan specifying what to consume and how to lower
//! - SSOT for "what to normalize" decision
//!
//! ## Contract
//!
//! - Returns Ok(Some(plan)): Pattern detected, proceed with normalization
//! - Returns Ok(None): Not a normalized pattern, use legacy fallback
//! - Returns Err(_): Internal error (malformed AST)

use crate::ast::{ASTNode, LiteralValue};
use crate::mir::builder::MirBuilder;
use super::plan::{NormalizationPlan, PlanKind};

/// Box-First: Pattern detection for Normalized shadow
pub struct NormalizationPlanBox;

impl NormalizationPlanBox {
    /// Detect Normalized pattern in block suffix
    ///
    /// Returns:
    /// - Ok(Some(plan)): Normalized pattern detected
    /// - Ok(None): Not a normalized pattern (use legacy)
    /// - Err(_): Internal error
    pub fn plan_block_suffix(
        _builder: &MirBuilder,
        remaining: &[ASTNode],
        func_name: &str,
        debug: bool,
    ) -> Result<Option<NormalizationPlan>, String> {
        let trace = crate::mir::builder::control_flow::joinir::trace::trace();

        if debug {
            trace.routing(
                "normalization/plan",
                func_name,
                &format!("Checking {} remaining statements", remaining.len()),
            );
        }

        // Empty suffix - not normalized
        if remaining.is_empty() {
            return Ok(None);
        }

        // First statement must be a loop with condition `true`
        // Phase 131-136 ONLY support loop(true), not loop(i < n) etc.
        let is_loop_true = match &remaining[0] {
            ASTNode::Loop { condition, .. } => {
                // Only accept loop(true) - literal Bool true
                matches!(
                    condition.as_ref(),
                    ASTNode::Literal { value: LiteralValue::Bool(true), .. }
                )
            }
            _ => false,
        };
        if !is_loop_true {
            if debug {
                trace.routing(
                    "normalization/plan",
                    func_name,
                    "First statement is not loop(true), returning None (not a normalized pattern)",
                );
            }
            return Ok(None);
        }

        // Phase 131: Loop-only pattern (single statement)
        if remaining.len() == 1 {
            if debug {
                trace.routing(
                    "normalization/plan",
                    func_name,
                    "Detected Phase 131 pattern: loop-only",
                );
            }
            return Ok(Some(NormalizationPlan::loop_only()));
        }

        // Phase 132-135: Loop + (assign*) + return
        // Count consecutive assignments after the loop
        let mut post_assign_count = 0;
        for i in 1..remaining.len() {
            if matches!(&remaining[i], ASTNode::Assignment { .. }) {
                post_assign_count += 1;
            } else {
                break;
            }
        }

        // After assignments (0 or more), we need a return statement
        let return_index = 1 + post_assign_count;
        if return_index >= remaining.len() {
            // No statement after assignments - not a post pattern
            if debug {
                trace.routing(
                    "normalization/plan",
                    func_name,
                    &format!(
                        "No statement after {} assignments, not a valid post pattern",
                        post_assign_count
                    ),
                );
            }
            // If there's a non-assignment statement after loop but no return, treat as not a pattern
            return Ok(None);
        }

        let has_return = matches!(&remaining[return_index], ASTNode::Return { .. });
        if !has_return {
            // Statement after loop (and optional assignments) is not return - not a post pattern
            if debug {
                trace.routing(
                    "normalization/plan",
                    func_name,
                    &format!(
                        "Statement after loop and {} assignments is not return, not a valid post pattern",
                        post_assign_count
                    ),
                );
            }
            return Ok(None);
        }

        // Valid Phase 132-135 pattern: loop + N assignments (N >= 0) + return
        if debug {
            trace.routing(
                "normalization/plan",
                func_name,
                &format!(
                    "Detected Phase 132-135 pattern: loop + {} post assigns + return",
                    post_assign_count
                ),
            );
        }

        Ok(Some(NormalizationPlan::loop_with_post(post_assign_count)))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator};

    fn make_span() -> Span {
        Span::unknown()
    }

    fn make_loop() -> ASTNode {
        ASTNode::Loop {
            condition: Box::new(ASTNode::Literal {
                value: LiteralValue::Bool(true),
                span: make_span(),
            }),
            body: vec![
                ASTNode::Assignment {
                    target: Box::new(ASTNode::Variable {
                        name: "x".to_string(),
                        span: make_span(),
                    }),
                    value: Box::new(ASTNode::Literal {
                        value: LiteralValue::Integer(1),
                        span: make_span(),
                    }),
                    span: make_span(),
                },
                ASTNode::Break { span: make_span() },
            ],
            span: make_span(),
        }
    }

    fn make_assignment(var: &str, value: i64) -> ASTNode {
        ASTNode::Assignment {
            target: Box::new(ASTNode::Variable {
                name: var.to_string(),
                span: make_span(),
            }),
            value: Box::new(ASTNode::BinaryOp {
                left: Box::new(ASTNode::Variable {
                    name: var.to_string(),
                    span: make_span(),
                }),
                operator: BinaryOperator::Add,
                right: Box::new(ASTNode::Literal {
                    value: LiteralValue::Integer(value),
                    span: make_span(),
                }),
                span: make_span(),
            }),
            span: make_span(),
        }
    }

    fn make_return(var: &str) -> ASTNode {
        ASTNode::Return {
            value: Some(Box::new(ASTNode::Variable {
                name: var.to_string(),
                span: make_span(),
            })),
            span: make_span(),
        }
    }

    #[test]
    fn test_plan_block_suffix_phase131_loop_only() {
        use crate::mir::builder::MirBuilder;

        let remaining = vec![make_loop()];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        assert!(plan.is_some());
        let plan = plan.unwrap();
        assert_eq!(plan.consumed, 1);
        assert_eq!(plan.kind, PlanKind::LoopOnly);
        assert!(!plan.requires_return);
    }

    #[test]
    fn test_plan_block_suffix_phase132_loop_post_single() {
        use crate::mir::builder::MirBuilder;

        let remaining = vec![
            make_loop(),
            make_assignment("x", 2),
            make_return("x"),
        ];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        assert!(plan.is_some());
        let plan = plan.unwrap();
        assert_eq!(plan.consumed, 3); // loop + 1 assign + return
        assert_eq!(plan.kind, PlanKind::LoopWithPost { post_assign_count: 1 });
        assert!(plan.requires_return);
    }

    #[test]
    fn test_plan_block_suffix_phase133_loop_post_multi() {
        use crate::mir::builder::MirBuilder;

        let remaining = vec![
            make_loop(),
            make_assignment("x", 2),
            make_assignment("x", 3),
            make_return("x"),
        ];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        assert!(plan.is_some());
        let plan = plan.unwrap();
        assert_eq!(plan.consumed, 4); // loop + 2 assigns + return
        assert_eq!(plan.kind, PlanKind::LoopWithPost { post_assign_count: 2 });
        assert!(plan.requires_return);
    }

    #[test]
    fn test_plan_block_suffix_return_boundary() {
        use crate::mir::builder::MirBuilder;

        // Pattern with unreachable statement after return
        let remaining = vec![
            make_loop(),
            make_assignment("x", 2),
            make_return("x"),
            make_assignment("y", 999), // Unreachable
        ];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        assert!(plan.is_some());
        let plan = plan.unwrap();
        // Should consume only up to return (not the unreachable statement)
        assert_eq!(plan.consumed, 3); // loop + 1 assign + return
        assert_eq!(plan.kind, PlanKind::LoopWithPost { post_assign_count: 1 });
    }

    #[test]
    fn test_plan_block_suffix_no_match_empty() {
        use crate::mir::builder::MirBuilder;

        let remaining: Vec<ASTNode> = vec![];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        assert!(plan.is_none());
    }

    #[test]
    fn test_plan_block_suffix_no_match_not_loop() {
        use crate::mir::builder::MirBuilder;

        let remaining = vec![
            make_assignment("x", 1),
            make_return("x"),
        ];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        assert!(plan.is_none());
    }

    #[test]
    fn test_plan_block_suffix_no_match_no_return() {
        use crate::mir::builder::MirBuilder;

        let remaining = vec![
            make_loop(),
            make_assignment("x", 2),
            // Missing return
        ];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        // No return means not a valid post pattern
        assert!(plan.is_none());
    }

    #[test]
    fn test_plan_block_suffix_phase135_loop_return_only() {
        use crate::mir::builder::MirBuilder;

        // Phase 135: loop + return (0 post assignments)
        let remaining = vec![
            make_loop(),
            make_return("x"),
        ];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        assert!(plan.is_some());
        let plan = plan.unwrap();
        assert_eq!(plan.consumed, 2); // loop + return
        assert_eq!(plan.kind, PlanKind::LoopWithPost { post_assign_count: 0 });
        assert!(plan.requires_return);
    }

    #[test]
    fn test_plan_block_suffix_return_boundary_with_trailing() {
        use crate::mir::builder::MirBuilder;

        // Pattern with unreachable statement after return (Phase 135 variation)
        let remaining = vec![
            make_loop(),
            make_return("x"),
            make_assignment("y", 999), // Unreachable
        ];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        assert!(plan.is_some());
        let plan = plan.unwrap();
        // Should consume only up to return (not the unreachable statement)
        assert_eq!(plan.consumed, 2); // loop + return
        assert_eq!(plan.kind, PlanKind::LoopWithPost { post_assign_count: 0 });
    }

    #[test]
    fn test_plan_block_suffix_no_match_loop_not_true() {
        use crate::mir::builder::MirBuilder;

        // Phase 97 regression test: loop(i < n) should NOT match
        // Normalized shadow only supports loop(true)
        let loop_conditional = ASTNode::Loop {
            condition: Box::new(ASTNode::BinaryOp {
                left: Box::new(ASTNode::Variable {
                    name: "i".to_string(),
                    span: make_span(),
                }),
                operator: BinaryOperator::Less,
                right: Box::new(ASTNode::Variable {
                    name: "n".to_string(),
                    span: make_span(),
                }),
                span: make_span(),
            }),
            body: vec![
                ASTNode::Assignment {
                    target: Box::new(ASTNode::Variable {
                        name: "x".to_string(),
                        span: make_span(),
                    }),
                    value: Box::new(ASTNode::Literal {
                        value: LiteralValue::Integer(1),
                        span: make_span(),
                    }),
                    span: make_span(),
                },
                ASTNode::Break { span: make_span() },
            ],
            span: make_span(),
        };

        let remaining = vec![loop_conditional, make_return("x")];

        let builder = MirBuilder::new();
        let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
            .expect("Should not error");

        // loop(i < n) is NOT supported by Normalized - should return None
        assert!(plan.is_none(), "loop(i < n) should NOT match Normalized pattern");
    }
}
