//! Phase 286 P2.3: Pattern9 (AccumConstLoop) Extraction
//!
//! Minimal subset extractor for Pattern9 Plan line.
//!
//! # Supported subset (PoC safety)
//!
//! - Loop condition: `<var> < <int_lit>`
//! - Body: 2 assignments only (順序固定)
//!   1. `<acc_var> = <acc_var> + <expr>` (const OR var accumulation)
//!   2. `<loop_var> = <loop_var> + <int_lit>` (increment)
//! - No break/continue/if/return
//!
//! Returns Ok(None) for unsupported patterns → legacy fallback

use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern9AccumConstLoopPlan};

// Phase 286 P2.3: Use common_helpers
use super::common_helpers::{extract_loop_increment_plan, has_control_flow_statement};

/// Phase 286 P2.3: Minimal subset extractor for Pattern9 Plan line
///
/// # Detection Criteria
///
/// 1. **Condition**: `<var> < <int_lit>`
/// 2. **Body**: Exactly 2 assignments in fixed order
///    - 1st: `<acc_var> = <acc_var> + <expr>` (accumulation)
///    - 2nd: `<loop_var> = <loop_var> + <int_lit>` (increment)
/// 3. **No control flow**: No break/continue/if-else
///
/// # Returns
///
/// - `Ok(Some(plan))`: Pattern9 match confirmed
/// - `Ok(None)`: Not Pattern9 (構造不一致 or unsupported)
/// - `Err(msg)`: Logic bug (malformed AST)
pub(crate) fn extract_pattern9_plan(
    condition: &ASTNode,
    body: &[ASTNode],
) -> Result<Option<DomainPlan>, String> {
    // Step 1: Validate loop condition is `<var> < <int_lit>`
    let loop_var = match validate_loop_condition_plan(condition) {
        Some(var) => var,
        None => return Ok(None), // Unsupported condition format
    };

    // Step 2: Reject control flow (break/continue/if-else)
    if has_control_flow_statement(body) {
        return Ok(None); // Has break/continue → Not Pattern9 for Plan line
    }

    // Step 3: Validate body has exactly 2 assignments
    if body.len() != 2 {
        return Ok(None); // Must be exactly 2 statements
    }

    // Step 4: Extract accumulator update (1st statement)
    // Supports: `<acc_var> = <acc_var> + <expr>` (const OR var)
    let (acc_var, acc_update) = match extract_accum_update(&body[0], &loop_var) {
        Some((var, update)) => (var, update),
        None => return Ok(None), // Not a valid accumulator update
    };

    // Step 5: Extract loop increment (2nd statement)
    // Uses common_helpers::extract_loop_increment_plan
    let loop_increment = match extract_loop_increment_plan(&body[1..], &loop_var)? {
        Some(inc) => inc,
        None => return Ok(None), // No loop increment found
    };

    Ok(Some(DomainPlan::Pattern9AccumConstLoop(Pattern9AccumConstLoopPlan {
        loop_var,
        acc_var,
        condition: condition.clone(),
        acc_update,
        loop_increment,
    })))
}

/// Validate loop condition: supports `<var> < <int_lit>` only
fn validate_loop_condition_plan(cond: &ASTNode) -> Option<String> {
    if let ASTNode::BinaryOp { operator, left, right, .. } = cond {
        if !matches!(operator, BinaryOperator::Less) {
            return None; // Only < supported for PoC
        }

        // Left must be a variable
        let var_name = if let ASTNode::Variable { name, .. } = left.as_ref() {
            name.clone()
        } else {
            return None;
        };

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

        Some(var_name)
    } else {
        None
    }
}

/// Extract accumulator update from assignment
///
/// Supports: `<acc_var> = <acc_var> + <expr>` where <expr> is const OR var
///
/// Returns: Some((acc_var, update_expr)) or None
fn extract_accum_update(stmt: &ASTNode, loop_var: &str) -> Option<(String, ASTNode)> {
    if let ASTNode::Assignment { target, value, .. } = stmt {
        // target must be a variable (different from loop_var)
        if let ASTNode::Variable { name: target_var, .. } = target.as_ref() {
            if target_var == loop_var {
                return None; // This is the loop increment, not accumulator
            }

            // value must be `<acc_var> + <expr>`
            if let ASTNode::BinaryOp { operator, left, right, .. } = value.as_ref() {
                if !matches!(operator, BinaryOperator::Add) {
                    return None; // Only + supported
                }

                // Left must be same as target (acc_var = acc_var + ...)
                if let ASTNode::Variable { name: left_var, .. } = left.as_ref() {
                    if left_var != target_var {
                        return None; // Not a self-update
                    }
                } else {
                    return None;
                }

                // Right can be const OR var (ChatGPT feedback: both OK)
                // Accept: Literal (const) or Variable (var)
                match right.as_ref() {
                    ASTNode::Literal { value: LiteralValue::Integer(_), .. } => {
                        // const accumulation: sum = sum + 1
                        return Some((target_var.clone(), value.as_ref().clone()));
                    }
                    ASTNode::Variable { .. } => {
                        // var accumulation: sum = sum + i
                        return Some((target_var.clone(), value.as_ref().clone()));
                    }
                    _ => return None, // Complex expression not supported
                }
            }
        }
    }
    None
}

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

    fn make_condition(var: &str, bound: 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(bound),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        }
    }

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

    fn make_accum_var_update(acc_var: &str, rhs_var: &str) -> ASTNode {
        ASTNode::Assignment {
            target: Box::new(ASTNode::Variable {
                name: acc_var.to_string(),
                span: Span::unknown(),
            }),
            value: Box::new(ASTNode::BinaryOp {
                operator: BinaryOperator::Add,
                left: Box::new(ASTNode::Variable {
                    name: acc_var.to_string(),
                    span: Span::unknown(),
                }),
                right: Box::new(ASTNode::Variable {
                    name: rhs_var.to_string(),
                    span: Span::unknown(),
                }),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        }
    }

    fn make_loop_increment(var: &str, step: i64) -> 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(step),
                    span: Span::unknown(),
                }),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        }
    }

    #[test]
    fn test_extract_pattern9_const_accum_success() {
        // loop(i < 3) { sum = sum + 1; i = i + 1 }
        let condition = make_condition("i", 3);
        let body = vec![
            make_accum_const_update("sum", 1),
            make_loop_increment("i", 1),
        ];

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

        if let Some(DomainPlan::Pattern9AccumConstLoop(p)) = plan {
            assert_eq!(p.loop_var, "i");
            assert_eq!(p.acc_var, "sum");
        } else {
            panic!("Expected Pattern9AccumConstLoop");
        }
    }

    #[test]
    fn test_extract_pattern9_var_accum_success() {
        // loop(i < 3) { sum = sum + i; i = i + 1 }
        let condition = make_condition("i", 3);
        let body = vec![
            make_accum_var_update("sum", "i"),
            make_loop_increment("i", 1),
        ];

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

        if let Some(DomainPlan::Pattern9AccumConstLoop(p)) = plan {
            assert_eq!(p.loop_var, "i");
            assert_eq!(p.acc_var, "sum");
        } else {
            panic!("Expected Pattern9AccumConstLoop");
        }
    }

    #[test]
    fn test_extract_pattern9_wrong_order_returns_none() {
        // loop(i < 3) { i = i + 1; sum = sum + 1 } <- wrong order
        let condition = make_condition("i", 3);
        let body = vec![
            make_loop_increment("i", 1),
            make_accum_const_update("sum", 1),
        ];

        let result = extract_pattern9_plan(&condition, &body);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // Wrong order → None
    }

    #[test]
    fn test_extract_pattern9_with_break_returns_none() {
        let condition = make_condition("i", 3);
        let body = vec![
            ASTNode::Break { span: Span::unknown() },
            make_accum_const_update("sum", 1),
            make_loop_increment("i", 1),
        ];

        let result = extract_pattern9_plan(&condition, &body);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // Has break → None
    }

    #[test]
    fn test_extract_pattern9_single_stmt_returns_none() {
        let condition = make_condition("i", 3);
        let body = vec![make_loop_increment("i", 1)];

        let result = extract_pattern9_plan(&condition, &body);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none()); // Only 1 statement → None
    }
}
