//! Pattern5 (Infinite Loop with Early Exit) Extractor
//!
//! Phase 282 P7: ExtractionBased migration for Pattern5
//! Phase 282 P9a: Integrated with common_helpers
//!
//! # Pattern5 Characteristics
//!
//! - Infinite loop: `loop(true)`
//! - Exactly 1 break statement (in simple if pattern)
//! - Exactly 1 continue statement (at loop body end)
//! - No nested loops
//! - No return statements (Err for fail-fast)
//!
//! # USER GUIDANCE Applied
//!
//! - Q1: break-only, return → Err (not Ok(None))
//! - Q2: Test order - Integration → Quick → early_return
//! - return Err 文言統一 (Phase 284 参照)
//! - continue/break 両方必須チェック (再帰カウント + トップレベル末尾)

use crate::ast::ASTNode;

// Phase 282 P9a: Use common_helpers
use super::common_helpers::is_true_literal;

#[cfg(test)]
use super::common_helpers::{
    count_control_flow, validate_break_in_simple_if, validate_continue_at_end, ControlFlowDetector,
};

/// Pattern5 extracted parts (lightweight, no AST copies)
#[derive(Debug, Clone)]
#[cfg(test)]
pub(crate) struct Pattern5Parts {
    pub break_count: usize,        // Must be 1 for Pattern5
    pub continue_count: usize,     // Must be 1 for Pattern5
    pub return_count: usize,       // Must be 0 (Err if > 0, but kept for debug logging)
    pub has_nested_loop: bool,     // Must be false
    pub continue_at_end: bool,     // Must be true
    pub break_in_simple_if: bool,  // Must be true
}

/// Extract Pattern5 (Infinite Loop with Early Exit) parts
///
/// # Pattern5 Shape Guard (from existing implementation)
///
/// - Condition MUST be `true` literal (infinite loop)
/// - MUST have exactly 1 break statement
/// - MUST have exactly 1 continue statement
/// - Continue MUST be at loop body end
/// - Break MUST be in simple if pattern
/// - NO nested loops
/// - NO return statements (Err for fail-fast)
///
/// # Validation Steps (9 checks, grouped into 4-5 blocks for readability)
///
/// **Phase 1**: Check condition is `true` literal
/// **Phase 2**: Count break/continue/return recursively
/// **Phase 3**: Check for return (Err if found - USER GUIDANCE!)
/// **Phase 4**: Validate break count == 1
/// **Phase 5**: Validate continue count == 1 (recursive, with nested loop exclusion)
/// **Phase 6**: Check for nested loops (Ok(None) if found)
/// **Phase 7**: Validate continue position (must be at end, top-level)
/// **Phase 8**: Validate break pattern (simple if)
/// **Phase 9**: Return extracted parts
///
/// Note: Implementation groups these into 4-5 logical blocks for readability
///
/// # Fail-Fast Rules (USER GUIDANCE)
///
/// - `Ok(Some(parts))`: Pattern5 match confirmed
/// - `Ok(None)`: Not Pattern5 (structural mismatch, try other patterns)
/// - `Err(msg)`: Close-but-unsupported (e.g., return found)
#[cfg(test)]
pub(crate) fn extract_infinite_early_exit_parts(
    condition: &ASTNode,
    body: &[ASTNode],
) -> Result<Option<Pattern5Parts>, String> {
    // ========================================================================
    // Block 1: Check condition is `true` literal (infinite loop)
    // ========================================================================
    if !is_true_literal(condition) {
        return Ok(None); // Not infinite loop → Not Pattern5
    }

    // ========================================================================
    // Block 2: Count control flow recursively (break/continue/return/nested)
    // ========================================================================
    let (break_count, continue_count, return_count, has_nested_loop) =
        count_control_flow_recursive(body);

    // Phase 284 P1: Return check removed - now handled by return_collector SSOT
    // in conversion_pipeline.rs (JoinIR line common entry point)
    // Note: return_count is still tracked for debug logging

    // ========================================================================
    // Block 3: Validate break/continue counts (exactly 1 each)
    // ========================================================================
    if break_count != 1 {
        return Ok(None); // Not exactly 1 break → Not Pattern5
    }

    // CRITICAL: 再帰カウント（if内continueも含める、nested loop内は除外）
    // Phase 7 とセットで両方必須（count==1 AND 末尾continue）
    if continue_count != 1 {
        return Ok(None); // Not exactly 1 continue (recursive) → Not Pattern5
    }

    if has_nested_loop {
        return Ok(None); // Nested loops not supported in Pattern5
    }

    // ========================================================================
    // Block 4: Validate continue position & break pattern
    // ========================================================================

    // CRITICAL: トップレベル末尾continue（body.last()==Continue）
    // Phase 5 とセットで両方必須（count==1 AND 末尾continue）
    let continue_at_end = validate_continue_at_end(body);
    if !continue_at_end {
        return Ok(None); // Continue not at end (top-level) → Not Pattern5 shape
    }

    let break_in_simple_if = validate_break_in_simple_if(body);
    if !break_in_simple_if {
        return Ok(None); // Break not in simple if → Not Pattern5 shape
    }

    // ========================================================================
    // Block 5: Return extracted parts (all checks passed)
    // ========================================================================
    Ok(Some(Pattern5Parts {
        break_count,
        continue_count,
        return_count,
        has_nested_loop,
        continue_at_end,
        break_in_simple_if,
    }))
}

// ============================================================================
// Helper Functions (Internal)
// ============================================================================

// Phase 282 P9a: is_true_literal moved to common_helpers

/// Count break/continue/return recursively (Pattern5 version)
///
/// # Phase 282 P9a: Delegates to common_helpers::count_control_flow
///
/// - Uses ControlFlowDetector with count_returns=true
/// - Skips nested loop bodies (default behavior)
#[cfg(test)]
fn count_control_flow_recursive(body: &[ASTNode]) -> (usize, usize, usize, bool) {
    let mut detector = ControlFlowDetector::default();
    detector.count_returns = true;

    let counts = count_control_flow(body, detector);
    (
        counts.break_count,
        counts.continue_count,
        counts.return_count,
        counts.has_nested_loop,
    )
}

// Phase 282 P9a: validate_continue_at_end moved to common_helpers
// Phase 282 P9a: validate_break_in_simple_if moved to common_helpers

// ============================================================================
// Phase 286 P3.2: Plan line extractor (PoC subset)
// ============================================================================

use crate::mir::builder::control_flow::plan::{
    DomainPlan, Pattern5InfiniteEarlyExitPlan, Pattern5ExitKind,
};

/// Extract variable name and increment expression from assignment
///
/// Returns (variable_name, increment_expr) for `var = expr` form
fn extract_assignment_parts(stmt: &ASTNode) -> Option<(String, ASTNode)> {
    if let ASTNode::Assignment { target, value, .. } = stmt {
        if let ASTNode::Variable { name, .. } = target.as_ref() {
            return Some((name.clone(), value.as_ref().clone()));
        }
    }
    None
}

/// Extract Pattern5 Plan (Phase 286 P3.2)
///
/// # PoC Subset (strict)
///
/// - `loop(true)` literal ONLY (not `loop(1)` or truthy)
/// - Return version: `if (cond) { return <expr> }` + `i = i + 1`
/// - Break version: `if (cond) { break }` + `sum = sum + 1` + `i = i + 1` (carrier_update required)
///
/// # Returns
///
/// - `Ok(Some(DomainPlan))`: Pattern5 Plan match
/// - `Ok(None)`: Not PoC subset (fall back to legacy or other patterns)
/// - `Err(msg)`: Close-but-unsupported (Fail-Fast)
pub(crate) fn extract_pattern5_plan(
    condition: &ASTNode,
    body: &[ASTNode],
) -> Result<Option<DomainPlan>, String> {
    // ========================================================================
    // Block 1: Check condition is `true` literal (loop(true) ONLY)
    // ========================================================================
    if !is_true_literal(condition) {
        return Ok(None); // Not loop(true) → Not Pattern5 Plan
    }

    // ========================================================================
    // Block 2: Find exit statement (if at first position)
    // ========================================================================
    // PoC subset: first statement must be `if (cond) { return/break }`
    if body.is_empty() {
        return Ok(None);
    }

    let first_stmt = &body[0];
    let (exit_kind, exit_condition, exit_value) = match first_stmt {
        ASTNode::If { condition: if_cond, then_body, else_body: None, .. } => {
            // Must be single-statement then body
            if then_body.len() != 1 {
                return Ok(None);
            }
            match &then_body[0] {
                ASTNode::Return { value, .. } => {
                    (Pattern5ExitKind::Return, if_cond.as_ref().clone(), value.clone())
                }
                ASTNode::Break { .. } => {
                    (Pattern5ExitKind::Break, if_cond.as_ref().clone(), None)
                }
                _ => return Ok(None),
            }
        }
        _ => return Ok(None),
    };

    // ========================================================================
    // Block 3: Parse remaining body for carrier update and loop increment
    // ========================================================================
    let remaining = &body[1..];

    match exit_kind {
        Pattern5ExitKind::Return => {
            // Return version: just need loop increment
            // Expected: [increment]
            if remaining.len() != 1 {
                return Ok(None);
            }

            let (loop_var, loop_increment) = match extract_assignment_parts(&remaining[0]) {
                Some(result) => result,
                None => return Ok(None),
            };

            // Unbox exit_value if present
            let exit_value_unboxed = exit_value.map(|boxed| boxed.as_ref().clone());

            Ok(Some(DomainPlan::Pattern5InfiniteEarlyExit(Pattern5InfiniteEarlyExitPlan {
                loop_var,
                exit_kind,
                exit_condition,
                exit_value: exit_value_unboxed,
                carrier_var: None,
                carrier_update: None,
                loop_increment,
            })))
        }
        Pattern5ExitKind::Break => {
            // Break version: need carrier update + loop increment
            // Expected: [carrier_update, increment]
            if remaining.len() != 2 {
                return Ok(None);
            }

            // Parse carrier update (sum = sum + 1)
            let (carrier_var, carrier_update) = match extract_assignment_parts(&remaining[0]) {
                Some(result) => result,
                None => return Ok(None),
            };

            // Parse loop increment (i = i + 1)
            let (loop_var, loop_increment) = match extract_assignment_parts(&remaining[1]) {
                Some(result) => result,
                None => return Ok(None),
            };

            Ok(Some(DomainPlan::Pattern5InfiniteEarlyExit(Pattern5InfiniteEarlyExitPlan {
                loop_var,
                exit_kind,
                exit_condition,
                exit_value: None,
                carrier_var: Some(carrier_var),
                carrier_update: Some(carrier_update),
                loop_increment,
            })))
        }
    }
}

// ============================================================================
// Unit Tests
// ============================================================================

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

    #[test]
    fn test_pattern5_infinite_loop_success() {
        // loop(true) { if (counter == 3) { break } counter = counter + 1; continue }
        let condition = make_true_literal();
        let body = vec![
            make_if_break_on_counter(), // if (counter == 3) { break }
            make_increment("counter"),   // counter = counter + 1
            make_continue(),             // continue
        ];

        let result = extract_infinite_early_exit_parts(&condition, &body);
        assert!(result.is_ok());
        let parts = result.unwrap();
        assert!(parts.is_some());

        let parts = parts.unwrap();
        assert_eq!(parts.break_count, 1);
        assert_eq!(parts.continue_count, 1);
        assert_eq!(parts.return_count, 0);
        assert!(!parts.has_nested_loop);
        assert!(parts.continue_at_end);
        assert!(parts.break_in_simple_if);
    }

    #[test]
    fn test_pattern5_not_infinite_returns_none() {
        // loop(i < 10) { ... } → Not Pattern5 (not infinite)
        let condition = make_condition("i", 10);
        let body = vec![make_if_break(), make_continue()];

        let result = extract_infinite_early_exit_parts(&condition, &body);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // Not infinite → Not Pattern5
    }

    #[test]
    fn test_pattern5_with_return_is_allowed() {
        // Phase 284 P1: loop(true) { if (...) { return } if (done) { break } continue }
        // Return check moved to return_collector SSOT in conversion_pipeline.rs
        // Note: Pattern5 requires exactly 1 break, so we need to add one
        let condition = make_true_literal();
        let body = vec![
            make_if_return(),  // if (...) { return }
            make_if_break(),   // if (done) { break } - required for Pattern5
            make_continue(),
        ];

        let result = extract_infinite_early_exit_parts(&condition, &body);
        assert!(result.is_ok()); // Phase 284 P1: Now allowed at extractor level
        let parts = result.unwrap();
        assert!(parts.is_some()); // Pattern5 extraction succeeds
        let parts = parts.unwrap();
        assert_eq!(parts.break_count, 1);
        assert_eq!(parts.continue_count, 1);
        assert_eq!(parts.return_count, 1); // Return is now tracked, not rejected
    }

    #[test]
    fn test_pattern5_multiple_breaks_returns_none() {
        // loop(true) { if (...) { break } if (...) { break } continue }
        // → Not Pattern5 (break_count != 1)
        let condition = make_true_literal();
        let body = vec![make_if_break(), make_if_break(), make_continue()];

        let result = extract_infinite_early_exit_parts(&condition, &body);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // break_count != 1 → Not Pattern5
    }

    #[test]
    fn test_pattern5_continue_not_at_end_returns_none() {
        // loop(true) { if (...) { break } continue; i = i + 1 }
        // → Not Pattern5 (continue not at end)
        let condition = make_true_literal();
        let body = vec![
            make_if_break(),
            make_continue(),
            make_increment("i"), // continue が末尾じゃない
        ];

        let result = extract_infinite_early_exit_parts(&condition, &body);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // continue not at end → Not Pattern5
    }

    // ========================================================================
    // Helper functions for test fixtures
    // ========================================================================

    fn make_true_literal() -> ASTNode {
        ASTNode::Literal {
            value: LiteralValue::Bool(true),
            span: Span::unknown(),
        }
    }

    fn make_condition(var: &str, limit: i64) -> ASTNode {
        ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left: Box::new(ASTNode::Variable {
                name: var.to_string(),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(limit),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        }
    }

    fn make_if_break() -> ASTNode {
        ASTNode::If {
            condition: Box::new(ASTNode::Variable {
                name: "done".to_string(),
                span: Span::unknown(),
            }),
            then_body: vec![ASTNode::Break {
                span: Span::unknown(),
            }],
            else_body: None,
            span: Span::unknown(),
        }
    }

    fn make_if_break_on_counter() -> ASTNode {
        ASTNode::If {
            condition: Box::new(ASTNode::BinaryOp {
                operator: BinaryOperator::Equal,
                left: Box::new(ASTNode::Variable {
                    name: "counter".to_string(),
                    span: Span::unknown(),
                }),
                right: Box::new(ASTNode::Literal {
                    value: LiteralValue::Integer(3),
                    span: Span::unknown(),
                }),
                span: Span::unknown(),
            }),
            then_body: vec![ASTNode::Break {
                span: Span::unknown(),
            }],
            else_body: None,
            span: Span::unknown(),
        }
    }

    fn make_if_return() -> ASTNode {
        ASTNode::If {
            condition: Box::new(ASTNode::Variable {
                name: "error".to_string(),
                span: Span::unknown(),
            }),
            then_body: vec![ASTNode::Return {
                value: None,
                span: Span::unknown(),
            }],
            else_body: None,
            span: Span::unknown(),
        }
    }

    fn make_continue() -> ASTNode {
        ASTNode::Continue {
            span: Span::unknown(),
        }
    }

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