//! Phase 282 P4: Pattern2 (Loop with Conditional Break) Extraction
//! Phase 282 P9a: Integrated with common_helpers
//! Phase 286 P3.1: Added extract_pattern2_plan() for Plan/Frag SSOT
//! Phase 29ai P10: Moved to plan-layer SSOT

use crate::ast::{ASTNode, BinaryOperator, LiteralValue};

// Phase 282 P9a: Use common_helpers
use crate::mir::builder::control_flow::plan::extractors::common_helpers::{
    count_control_flow, has_continue_statement as common_has_continue,
    has_return_statement as common_has_return, ControlFlowDetector,
};

#[derive(Debug, Clone)]
pub(crate) struct Pattern2Parts {
    pub loop_var: String,      // Loop variable name (empty for loop(true))
    pub is_loop_true: bool,    // true if loop(true), false otherwise
    pub break_count: usize,    // Number of break statements (validation)
    // Note: AST reused from ctx - no duplication
}

/// Extract Pattern2 (Loop with Conditional Break) parts
///
/// # Detection Criteria (Lightweight - SSOT minimal)
///
/// 1. **Condition**: 比較演算 (left=variable) OR loop(true)
/// 2. **Body**: MUST have at least 1 break statement
/// 3. **Body**: NO continue statements (Pattern4 territory)
/// 4. **Body**: NO return statements (Pattern2 = break-only)
///
/// # Four-Phase Validation
///
/// **Phase 1**: Validate condition (比較演算 or loop(true))
/// **Phase 2**: Validate HAS break (required for Pattern2)
/// **Phase 3**: Validate NO continue/return (break-only)
/// **Phase 4**: Extract core info
///
/// # Fail-Fast Rules
///
/// - `Ok(Some(parts))`: Pattern2 match confirmed
/// - `Ok(None)`: Not Pattern2 (no break, has continue/return, or Pattern1)
/// - `Err(msg)`: Logic bug (malformed AST)
///
/// # Note
///
/// Carrier update validation is deferred to can_lower() via CommonPatternInitializer (SSOT).
pub(crate) fn extract_loop_with_break_parts(
    condition: &ASTNode,
    body: &[ASTNode],
) -> Result<Option<Pattern2Parts>, String> {
    // Phase 1: Validate condition
    let (loop_var, is_loop_true) = validate_condition_for_pattern2(condition);

    // Phase 2: Validate HAS break (required)
    let break_count = count_break_statements(body);
    if break_count == 0 {
        return Ok(None); // No break → Pattern1 territory
    }

    // Phase 3: Validate NO continue/return (break-only)
    if has_continue_statement(body) {
        return Ok(None); // Has continue → Pattern4 territory
    }
    if has_return_statement(body) {
        return Ok(None); // Has return → Pattern2 is break-only
    }

    // Phase 4: Return extracted info
    Ok(Some(Pattern2Parts {
        loop_var,
        is_loop_true,
        break_count,
    }))
}

/// Validate condition: 比較演算 or loop(true)
fn validate_condition_for_pattern2(condition: &ASTNode) -> (String, bool) {
    // Case 1: loop(true)
    if matches!(
        condition,
        ASTNode::Literal {
            value: LiteralValue::Bool(true),
            ..
        }
    ) {
        return ("".to_string(), true); // loop_var determined in can_lower()
    }

    // Case 2: 比較演算 (same as Pattern1)
    match condition {
        ASTNode::BinaryOp { operator, left, .. } => {
            if matches!(
                operator,
                BinaryOperator::Less
                    | BinaryOperator::LessEqual
                    | BinaryOperator::Greater
                    | BinaryOperator::GreaterEqual
                    | BinaryOperator::Equal
                    | BinaryOperator::NotEqual
            ) {
                if let ASTNode::Variable { name, .. } = left.as_ref() {
                    return (name.clone(), false);
                }
            }
        }
        _ => {}
    }

    // Unknown condition - let can_lower() decide
    ("".to_string(), false)
}

/// Count break statements recursively
///
/// # Phase 282 P9a: Delegates to common_helpers::count_control_flow
fn count_break_statements(body: &[ASTNode]) -> usize {
    count_control_flow(body, ControlFlowDetector::default()).break_count
}

/// Check for continue statements
///
/// # Phase 282 P9a: Delegates to common_helpers::has_continue_statement
fn has_continue_statement(body: &[ASTNode]) -> bool {
    common_has_continue(body)
}

/// Check for return statements (Pattern2 = break-only)
///
/// # Phase 282 P9a: Delegates to common_helpers::has_return_statement
fn has_return_statement(body: &[ASTNode]) -> bool {
    common_has_return(body)
}

// ============================================================================
// Phase 286 P3.1: Plan line extraction
// ============================================================================

/// Extract Pattern2 Plan for Plan/Frag SSOT migration
///
/// # PoC Subset (Strict - returns Ok(None) for unsupported forms)
///
/// Supported form:
/// ```hako
/// loop(i < N) {
///     if (break_cond) { [carrier = expr;] break }
///     carrier = carrier + expr
///     i = i + 1
/// }
/// ```
///
/// Returns Ok(None) for:
/// - loop_increment not extractable (complex structure)
/// - break_cond not single if (nested, multiple conditions)
/// - break_then has multiple statements and carrier update not identifiable
/// - Multiple carriers (PoC is single carrier only)
/// - Body carrier update not identifiable
///
/// This prevents Fail-Fast regression by falling back to legacy JoinIR line.
pub(crate) fn extract_pattern2_plan(
    condition: &ASTNode,
    body: &[ASTNode],
) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String> {
    use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern2BreakPlan};

    // Step 1: Validate via existing extractor (has break, no continue/return)
    let parts = extract_loop_with_break_parts(condition, body)?;
    if parts.is_none() {
        return Ok(None); // Not Pattern2 → legacy fallback
    }
    let parts = parts.unwrap();

    // Step 2: PoC constraint - only support non-loop(true) for now
    if parts.is_loop_true {
        return Ok(None); // loop(true) is complex → legacy fallback
    }

    // Step 3: PoC constraint - only support single break
    if parts.break_count != 1 {
        return Ok(None); // Multiple breaks → legacy fallback
    }

    // Step 4: Body structure must be: if { ... break } + carrier_update + loop_increment
    // Expected structure:
    //   body[0] = If { then_body contains break }
    //   body[1] = carrier update assignment
    //   body[2] = loop_var update assignment
    if body.len() != 3 {
        return Ok(None); // Unexpected body length → legacy fallback
    }

    // Step 4.1: First element must be an If with break in then_body
    let (break_condition, carrier_update_in_break) = match &body[0] {
        ASTNode::If {
            condition: if_cond,
            then_body,
            else_body,
            ..
        } => {
            // PoC: No else branch allowed
            if else_body.is_some() {
                return Ok(None);
            }

            // Extract break condition
            let break_cond = if_cond.as_ref().clone();

            // Check if then_body ends with break
            let has_break_at_end = then_body
                .last()
                .map(|n| matches!(n, ASTNode::Break { .. }))
                .unwrap_or(false);
            if !has_break_at_end {
                return Ok(None);
            }

            // Extract carrier update in break (if any)
            // If then_body is [break], no update
            // If then_body is [assignment, break], extract assignment
            let carrier_update = if then_body.len() == 1 {
                None // Just break, no update
            } else if then_body.len() == 2 {
                // Should be [assignment, break]
                match &then_body[0] {
                    ASTNode::Assignment { value, .. } => Some(value.as_ref().clone()),
                    _ => return Ok(None), // Not an assignment → legacy
                }
            } else {
                return Ok(None); // Complex then_body → legacy
            };

            (break_cond, carrier_update)
        }
        _ => return Ok(None), // First element not If → legacy
    };

    // Step 4.2: Extract carrier update in body (second element)
    let (carrier_var, carrier_update_in_body) = match &body[1] {
        ASTNode::Assignment { target, value, .. } => {
            let carrier_name = match target.as_ref() {
                ASTNode::Variable { name, .. } => name.clone(),
                _ => return Ok(None),
            };
            (carrier_name, value.as_ref().clone())
        }
        _ => return Ok(None),
    };

    // Step 4.3: Extract loop increment (third element)
    let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &parts.loop_var)? {
        Some(inc) => inc,
        None => return Ok(None), // No loop increment found → legacy
    };

    // Step 5: Validate loop condition is `<var> < <int_lit>` (same as Pattern1)
    if !validate_loop_condition_for_plan(condition, &parts.loop_var) {
        return Ok(None);
    }

    Ok(Some(DomainPlan::Pattern2Break(Pattern2BreakPlan {
        loop_var: parts.loop_var,
        carrier_var,
        loop_condition: condition.clone(),
        break_condition,
        carrier_update_in_break,
        carrier_update_in_body,
        loop_increment,
        promotion: None,
    })))
}

/// Validate loop condition: supports `<var> < <int_lit>` only (same as Pattern1)
fn validate_loop_condition_for_plan(cond: &ASTNode, loop_var: &str) -> bool {
    if let ASTNode::BinaryOp {
        operator,
        left,
        right,
        ..
    } = cond
    {
        if !matches!(operator, BinaryOperator::Less) {
            return false; // Only < supported for PoC
        }

        // Left must be the loop variable
        if let ASTNode::Variable { name, .. } = left.as_ref() {
            if name != loop_var {
                return false;
            }
        } else {
            return false;
        }

        // Right must be integer literal
        if !matches!(
            right.as_ref(),
            ASTNode::Literal {
                value: LiteralValue::Integer(_),
                ..
            }
        ) {
            return false;
        }

        true
    } else {
        false
    }
}

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

    #[test]
    fn test_extract_with_break_success() {
        // loop(i < 10) { if (i == 5) { break } i = i + 1 }
        let condition = ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left: Box::new(ASTNode::Variable {
                name: "i".to_string(),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(10),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        };

        let body = vec![
            ASTNode::If {
                condition: Box::new(ASTNode::BinaryOp {
                    operator: BinaryOperator::Equal,
                    left: Box::new(ASTNode::Variable {
                        name: "i".to_string(),
                        span: Span::unknown(),
                    }),
                    right: Box::new(ASTNode::Literal {
                        value: LiteralValue::Integer(5),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }),
                then_body: vec![ASTNode::Break {
                    span: Span::unknown(),
                }],
                else_body: None,
                span: Span::unknown(),
            },
            ASTNode::Assignment {
                target: Box::new(ASTNode::Variable {
                    name: "i".to_string(),
                    span: Span::unknown(),
                }),
                value: Box::new(ASTNode::BinaryOp {
                    operator: BinaryOperator::Add,
                    left: Box::new(ASTNode::Variable {
                        name: "i".to_string(),
                        span: Span::unknown(),
                    }),
                    right: Box::new(ASTNode::Literal {
                        value: LiteralValue::Integer(1),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }),
                span: Span::unknown(),
            },
        ];

        let result = extract_loop_with_break_parts(&condition, &body);
        assert!(result.is_ok());
        let parts = result.unwrap();
        assert!(parts.is_some());
        let parts = parts.unwrap();
        assert_eq!(parts.loop_var, "i");
        assert_eq!(parts.is_loop_true, false);
        assert_eq!(parts.break_count, 1);
    }

    #[test]
    fn test_extract_no_break_returns_none() {
        // loop(i < 10) { i = i + 1 }
        let condition = ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left: Box::new(ASTNode::Variable {
                name: "i".to_string(),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(10),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        };

        let body = vec![ASTNode::Assignment {
            target: Box::new(ASTNode::Variable {
                name: "i".to_string(),
                span: Span::unknown(),
            }),
            value: Box::new(ASTNode::BinaryOp {
                operator: BinaryOperator::Add,
                left: Box::new(ASTNode::Variable {
                    name: "i".to_string(),
                    span: Span::unknown(),
                }),
                right: Box::new(ASTNode::Literal {
                    value: LiteralValue::Integer(1),
                    span: Span::unknown(),
                }),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        }];

        let result = extract_loop_with_break_parts(&condition, &body);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // No break → Pattern1 territory
    }

    #[test]
    fn test_extract_with_continue_returns_none() {
        // loop(i < 10) { if (i == 5) { continue } i = i + 1 }
        let condition = ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left: Box::new(ASTNode::Variable {
                name: "i".to_string(),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(10),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        };

        let body = vec![ASTNode::If {
            condition: Box::new(ASTNode::BinaryOp {
                operator: BinaryOperator::Equal,
                left: Box::new(ASTNode::Variable {
                    name: "i".to_string(),
                    span: Span::unknown(),
                }),
                right: Box::new(ASTNode::Literal {
                    value: LiteralValue::Integer(5),
                    span: Span::unknown(),
                }),
                span: Span::unknown(),
            }),
            then_body: vec![ASTNode::Continue {
                span: Span::unknown(),
            }],
            else_body: None,
            span: Span::unknown(),
        }];

        let result = extract_loop_with_break_parts(&condition, &body);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // Has continue → Pattern4 territory
    }

    #[test]
    fn test_extract_loop_true_with_break_success() {
        // loop(true) { if (i == 5) { break } i = i + 1 }
        let condition = ASTNode::Literal {
            value: LiteralValue::Bool(true),
            span: Span::unknown(),
        };

        let body = vec![
            ASTNode::If {
                condition: Box::new(ASTNode::BinaryOp {
                    operator: BinaryOperator::Equal,
                    left: Box::new(ASTNode::Variable {
                        name: "i".to_string(),
                        span: Span::unknown(),
                    }),
                    right: Box::new(ASTNode::Literal {
                        value: LiteralValue::Integer(5),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }),
                then_body: vec![ASTNode::Break {
                    span: Span::unknown(),
                }],
                else_body: None,
                span: Span::unknown(),
            },
            ASTNode::Assignment {
                target: Box::new(ASTNode::Variable {
                    name: "i".to_string(),
                    span: Span::unknown(),
                }),
                value: Box::new(ASTNode::BinaryOp {
                    operator: BinaryOperator::Add,
                    left: Box::new(ASTNode::Variable {
                        name: "i".to_string(),
                        span: Span::unknown(),
                    }),
                    right: Box::new(ASTNode::Literal {
                        value: LiteralValue::Integer(1),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }),
                span: Span::unknown(),
            },
        ];

        let result = extract_loop_with_break_parts(&condition, &body);
        assert!(result.is_ok());
        let parts = result.unwrap();
        assert!(parts.is_some());
        let parts = parts.unwrap();
        assert_eq!(parts.loop_var, ""); // Empty for loop(true)
        assert_eq!(parts.is_loop_true, true);
        assert_eq!(parts.break_count, 1);
    }
}
