//! Common Extraction Helpers for Pattern1-5
//!
//! Phase 282 P9a: Extracted from Pattern1-5 extractors to eliminate common duplication.
//! Phase 29ai P10: Moved to plan-layer SSOT (JoinIR keeps a wrapper path).
//!
//! # Design Principles
//!
//! - **Pure Functions**: No side effects, no builder mutations
//! - **Fail-Fast**: Err for logic bugs, Ok(None) for non-matches
//! - **Configurability**: ControlFlowDetector for pattern-specific behavior
//! - **Scope-Limited**: Common detection only, pattern-specific logic excluded
//!
//! # Groups (P9a Scope-Limited)
//!
//! 1. Control Flow Counting (count_control_flow) - Universal counter
//! 2. Control Flow Detection (has_break_statement, has_continue_statement, etc.) - Common detection
//! 3. Condition Validation (extract_loop_variable, is_true_literal) - Condition helpers
//! 4. Loop Increment Extraction (extract_loop_increment_plan) - Common plan helper
//! 5. Pattern5-Specific Helpers (validate_continue_at_end, validate_break_in_simple_if) - NOT generalized
//!
//! **IMPORTANT**: Pattern-specific interpretation logic (e.g., Pattern3's nested_if) is EXCLUDED.
//! Such logic remains in individual pattern files to maintain clear SSOT boundaries.

#![allow(dead_code)]

use crate::ast::ASTNode;

/// ============================================================
/// Group 1: Control Flow Counting (汎用カウンター)
/// ============================================================

#[derive(Debug, Clone, Default)]
pub(crate) struct ControlFlowCounts {
    pub break_count: usize,
    pub continue_count: usize,
    pub return_count: usize,
    pub has_nested_loop: bool,
}

/// Control flow detection options
///
/// # P9a Scope-Limited
/// - `detect_nested_if` removed (Pattern3-specific, deferred to P9b)
/// - Only common detection options included
#[derive(Debug, Clone)]
pub(crate) struct ControlFlowDetector {
    /// Skip break/continue inside nested loops?
    pub skip_nested_control_flow: bool,
    /// Count return statements?
    pub count_returns: bool,
}

impl Default for ControlFlowDetector {
    fn default() -> Self {
        Self {
            skip_nested_control_flow: true,
            count_returns: false,
        }
    }
}

/// Universal control flow counter
///
/// # Examples (P9a Scope-Limited)
/// - Pattern1: default (skip_nested=true, count_returns=false)
/// - Pattern2: default (skip_nested=true, count_returns=false)
/// - Pattern4: default (skip_nested=true, count_returns=false)
/// - Pattern5: count_returns=true (returns Err if return found)
pub(crate) fn count_control_flow(
    body: &[ASTNode],
    detector: ControlFlowDetector,
) -> ControlFlowCounts {
    let mut counts = ControlFlowCounts::default();

    fn scan_node(
        node: &ASTNode,
        counts: &mut ControlFlowCounts,
        detector: &ControlFlowDetector,
        depth: usize,
    ) {
        match node {
            ASTNode::Break { .. } => {
                counts.break_count += 1;
            }
            ASTNode::Continue { .. } => {
                counts.continue_count += 1;
            }
            ASTNode::Return { .. } if detector.count_returns => {
                counts.return_count += 1;
            }
            ASTNode::ScopeBox { body, .. } => {
                for stmt in body {
                    scan_node(stmt, counts, detector, depth);
                }
            }
            ASTNode::Loop { body, .. }
            | ASTNode::While { body, .. }
            | ASTNode::ForRange { body, .. } => {
                counts.has_nested_loop = true;
                // Skip nested loop bodies if configured
                if detector.skip_nested_control_flow {
                    return;
                }
                for stmt in body {
                    scan_node(stmt, counts, detector, depth + 1);
                }
            }
            ASTNode::If {
                then_body,
                else_body,
                ..
            } => {
                // Recurse into if/else bodies
                for stmt in then_body {
                    scan_node(stmt, counts, detector, depth + 1);
                }
                if let Some(else_b) = else_body {
                    for stmt in else_b {
                        scan_node(stmt, counts, detector, depth + 1);
                    }
                }
            }
            _ => {}
        }
    }

    for stmt in body {
        scan_node(stmt, &mut counts, &detector, 0);
    }

    counts
}

/// ============================================================
/// Group 2: Control Flow Detection (真偽値判定)
/// ============================================================

/// Check if body has ANY break statement
pub(crate) fn has_break_statement(body: &[ASTNode]) -> bool {
    count_control_flow(body, ControlFlowDetector::default()).break_count > 0
}

/// Check if body has ANY continue statement
pub(crate) fn has_continue_statement(body: &[ASTNode]) -> bool {
    count_control_flow(body, ControlFlowDetector::default()).continue_count > 0
}

/// Check if body has ANY return statement
pub(crate) fn has_return_statement(body: &[ASTNode]) -> bool {
    let mut detector = ControlFlowDetector::default();
    detector.count_returns = true;
    count_control_flow(body, detector).return_count > 0
}

/// Check if body has ANY break or continue
pub(crate) fn has_control_flow_statement(body: &[ASTNode]) -> bool {
    let counts = count_control_flow(body, ControlFlowDetector::default());
    counts.break_count > 0 || counts.continue_count > 0
}

/// Phase 286 P2.6: Check if body has ANY if statement (recursive)
///
/// This is a supplementary helper for Pattern1 extraction to prevent
/// Pattern1 from incorrectly matching Pattern3 fixtures (if-else-phi).
pub(crate) fn has_if_statement(body: &[ASTNode]) -> bool {
    for node in body {
        match node {
            ASTNode::If { .. } => return true,
            ASTNode::ScopeBox { body, .. } => {
                if has_if_statement(body) {
                    return true;
                }
            }
            ASTNode::Loop { body, .. } => {
                if has_if_statement(body) {
                    return true;
                }
            }
            _ => {}
        }
    }
    false
}

/// Phase 286 P2.6: Check if body has ANY if-else statement (recursive)
///
/// This is more specific than has_if_statement - it only detects if statements
/// with else branches, which are Pattern3 territory (if-phi merge).
pub(crate) fn has_if_else_statement(body: &[ASTNode]) -> bool {
    for node in body {
        match node {
            ASTNode::If { else_body: Some(_), .. } => return true,
            ASTNode::ScopeBox { body, .. } => {
                if has_if_else_statement(body) {
                    return true;
                }
            }
            ASTNode::Loop { body, .. } => {
                if has_if_else_statement(body) {
                    return true;
                }
            }
            _ => {}
        }
    }
    false
}

/// Phase 286: Find first if-else statement in loop body (non-recursive)
pub(crate) fn find_if_else_statement(body: &[ASTNode]) -> Option<&ASTNode> {
    for stmt in body {
        if matches!(stmt, ASTNode::If { else_body: Some(_), .. }) {
            return Some(stmt); // Found first if-else
        }
    }
    None
}

/// ============================================================
/// Group 3: Condition Validation (比較演算検証)
/// ============================================================

use crate::ast::BinaryOperator;

/// Validate condition: 比較演算 (左辺が変数)
pub(crate) fn extract_loop_variable(condition: &ASTNode) -> Option<String> {
    match condition {
        ASTNode::BinaryOp { operator, left, .. } => {
            if !matches!(
                operator,
                BinaryOperator::Less
                    | BinaryOperator::LessEqual
                    | BinaryOperator::Greater
                    | BinaryOperator::GreaterEqual
                    | BinaryOperator::Equal
                    | BinaryOperator::NotEqual
            ) {
                return None;
            }

            if let ASTNode::Variable { name, .. } = left.as_ref() {
                return Some(name.clone());
            }

            None
        }
        _ => None,
    }
}

/// Check if condition is true literal
pub(crate) fn is_true_literal(condition: &ASTNode) -> bool {
    use crate::ast::LiteralValue;

    matches!(
        condition,
        ASTNode::Literal {
            value: LiteralValue::Bool(true),
            ..
        }
    )
}

/// ============================================================
/// Group 4: Loop Increment Extraction (Common for Plan line)
/// ============================================================

/// Phase 286 P2.2: Extract loop increment for Plan line patterns
///
/// Supports `<var> = <var> + <int_lit>` pattern only (PoC safety).
pub(crate) fn extract_loop_increment_plan(
    body: &[ASTNode],
    loop_var: &str,
) -> Result<Option<ASTNode>, String> {
    for stmt in body {
        if let ASTNode::Assignment { target, value, .. } = stmt {
            if let ASTNode::Variable { name, .. } = target.as_ref() {
                if name == loop_var {
                    // Check for `i = i + <int>`
                    if let ASTNode::BinaryOp {
                        operator,
                        left,
                        right,
                        ..
                    } = value.as_ref()
                    {
                        if matches!(operator, BinaryOperator::Add) {
                            if let ASTNode::Variable { name: lname, .. } = left.as_ref() {
                                if lname == loop_var {
                                    if matches!(right.as_ref(), ASTNode::Literal { .. }) {
                                        return Ok(Some(value.as_ref().clone()));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    Ok(None)
}

/// ============================================================
/// Group 5: Pattern5-Specific Helpers (NOT generalized)
/// ============================================================
///
/// **IMPORTANT**: These helpers are Pattern5-specific and intentionally NOT generalized.

/// Validate continue is at body end (Pattern5 specific)
pub(crate) fn validate_continue_at_end(body: &[ASTNode]) -> bool {
    matches!(body.last(), Some(ASTNode::Continue { .. }))
}

/// Validate break is in simple if pattern (Pattern5 specific)
pub(crate) fn validate_break_in_simple_if(body: &[ASTNode]) -> bool {
    for stmt in body {
        if let ASTNode::If {
            then_body,
            else_body,
            ..
        } = stmt
        {
            if then_body.len() == 1
                && matches!(then_body[0], ASTNode::Break { .. })
                && else_body.is_none()
            {
                return true;
            }
        }
    }
    false
}

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

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

    fn make_break() -> ASTNode {
        ASTNode::Break { span: Span::unknown() }
    }

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

    fn make_return() -> ASTNode {
        ASTNode::Return {
            value: None,
            span: Span::unknown(),
        }
    }

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

    fn make_nested_loop() -> ASTNode {
        ASTNode::Loop {
            condition: Box::new(ASTNode::Variable {
                name: "cond".to_string(),
                span: Span::unknown(),
            }),
            body: vec![make_break()],
            span: Span::unknown(),
        }
    }

    #[test]
    fn test_count_control_flow_break() {
        let body = vec![make_break()];
        let counts = count_control_flow(&body, ControlFlowDetector::default());
        assert_eq!(counts.break_count, 1);
        assert_eq!(counts.continue_count, 0);
    }

    #[test]
    fn test_has_break_statement() {
        let body = vec![make_if_with_break()];
        assert!(has_break_statement(&body));
    }

    #[test]
    fn test_has_continue_statement() {
        let body = vec![make_continue()];
        assert!(has_continue_statement(&body));
    }

    #[test]
    fn test_has_continue_statement_false() {
        let body = vec![make_break()];
        assert!(!has_continue_statement(&body));
    }

    #[test]
    fn test_has_return_statement() {
        let body = vec![make_return()];
        assert!(has_return_statement(&body));
    }

    #[test]
    fn test_has_return_statement_false() {
        let body = vec![make_break()];
        assert!(!has_return_statement(&body));
    }

    #[test]
    fn test_has_control_flow_statement_break() {
        let body = vec![make_if_with_break()];
        assert!(has_control_flow_statement(&body));
    }

    #[test]
    fn test_has_control_flow_statement_continue() {
        let body = vec![make_continue()];
        assert!(has_control_flow_statement(&body));
    }

    #[test]
    fn test_has_control_flow_statement_false() {
        let body = vec![ASTNode::Variable {
            name: "x".to_string(),
            span: Span::unknown(),
        }];
        assert!(!has_control_flow_statement(&body));
    }

    #[test]
    fn test_count_control_flow_detects_nested_loop_at_top_level() {
        let body = vec![make_nested_loop()];
        let counts = count_control_flow(&body, ControlFlowDetector::default());
        assert!(counts.has_nested_loop);
    }

    #[test]
    fn test_has_control_flow_statement_break_in_scopebox() {
        let body = vec![ASTNode::ScopeBox {
            body: vec![make_break()],
            span: Span::unknown(),
        }];
        assert!(has_control_flow_statement(&body));
    }

    #[test]
    fn test_extract_loop_variable_success() {
        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(),
        };

        assert_eq!(extract_loop_variable(&condition), Some("i".to_string()));
    }

    #[test]
    fn test_extract_loop_variable_not_comparison() {
        let condition = 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(10),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        };

        assert_eq!(extract_loop_variable(&condition), None);
    }

    #[test]
    fn test_extract_loop_variable_not_variable() {
        let condition = ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(5),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(10),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        };

        assert_eq!(extract_loop_variable(&condition), None);
    }

    #[test]
    fn test_is_true_literal_success() {
        let condition = ASTNode::Literal {
            value: LiteralValue::Bool(true),
            span: Span::unknown(),
        };

        assert!(is_true_literal(&condition));
    }

    #[test]
    fn test_is_true_literal_false() {
        let condition = ASTNode::Literal {
            value: LiteralValue::Bool(false),
            span: Span::unknown(),
        };

        assert!(!is_true_literal(&condition));
    }

    #[test]
    fn test_is_true_literal_not_literal() {
        let condition = ASTNode::Variable {
            name: "x".to_string(),
            span: Span::unknown(),
        };

        assert!(!is_true_literal(&condition));
    }

    #[test]
    fn test_validate_continue_at_end_success() {
        let body = vec![make_break(), make_continue()];
        assert!(validate_continue_at_end(&body));
    }

    #[test]
    fn test_validate_continue_at_end_false() {
        let body = vec![make_continue(), make_break()];
        assert!(!validate_continue_at_end(&body));
    }

    #[test]
    fn test_validate_break_in_simple_if_success() {
        let body = vec![make_if_with_break()];
        assert!(validate_break_in_simple_if(&body));
    }

    #[test]
    fn test_validate_break_in_simple_if_with_else() {
        let body = vec![ASTNode::If {
            condition: Box::new(ASTNode::Variable {
                name: "done".to_string(),
                span: Span::unknown(),
            }),
            then_body: vec![make_break()],
            else_body: Some(vec![make_continue()]),
            span: Span::unknown(),
        }];
        assert!(!validate_break_in_simple_if(&body));
    }

    #[test]
    fn test_validate_break_in_simple_if_multiple_statements() {
        let body = vec![ASTNode::If {
            condition: Box::new(ASTNode::Variable {
                name: "done".to_string(),
                span: Span::unknown(),
            }),
            then_body: vec![make_break(), make_continue()],
            else_body: None,
            span: Span::unknown(),
        }];
        assert!(!validate_break_in_simple_if(&body));
    }
}
