//! Phase 256 P0: Pattern 7 - Split/Tokenization with Variable Step
//!
//! **Status**: P0 Implementation - 1-char separator only
//!
//! ## Pattern Description
//!
//! Detects string splitting pattern with conditional step:
//! ```nyash
//! loop(i <= s.length() - separator.length()) {
//!     if s.substring(i, i + separator.length()) == separator {
//!         result.push(s.substring(start, i))  // Match: variable step
//!         start = i + separator.length()
//!         i = start
//!     } else {
//!         i = i + 1                          // No match: constant step
//!     }
//! }
//! if start <= s.length() {
//!     result.push(s.substring(start, s.length()))  // Final segment
//! }
//! ```
//!
//! Key features:
//! - Two carriers: i (loop index), start (segment start)
//! - Three invariants: s (haystack), separator, result (ArrayBox)
//! - Conditional step via Select instruction (Pattern 4 style)
//! - Side effects: result.push() in both loop and post-loop
//! - P0 restriction: 1-char separator only

#![allow(dead_code)]

use crate::ast::ASTNode;
use crate::mir::builder::control_flow::joinir::patterns::common::{
    finalize_extract, ContractViolation, ExtractDecision,
};

/// Phase 256 P0: Split/Scan pattern parts extractor
///
/// Holds all extracted variables and AST nodes needed for JoinIR lowering.
/// P0: Fixed-form parser only (Fail-Fast on mismatch).
#[derive(Debug, Clone)]
struct SplitScanParts {
    // Variables (5 total)
    s_var: String,           // haystack variable name
    sep_var: String,         // separator variable name
    result_var: String,      // accumulator (ArrayBox) variable name
    i_var: String,           // loop index variable name
    start_var: String,       // segment start position variable name

    // Extracted ASTs for JoinIR lowering
    loop_cond_ast: ASTNode,        // i <= s.length() - sep.length()
    match_if_cond_ast: ASTNode,    // s.substring(i, i + sep.length()) == sep
    then_push_ast: ASTNode,        // result.push(s.substring(start, i))
    then_start_next_ast: ASTNode,  // start = i + sep.length()
    then_i_next_ast: ASTNode,      // i = start
    else_i_next_ast: ASTNode,      // i = i + 1
    post_push_ast: Option<ASTNode>, // result.push(s.substring(start, s.length()))
}

/// Phase 273 P2: Extract SplitScanPlan from AST (pure)
///
/// Returns DomainPlan if pattern matches, Ok(None) if not.
pub(crate) fn extract_split_scan_plan(
    condition: &ASTNode,
    body: &[ASTNode],
    _post_loop_code: &[ASTNode],
) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String> {
    use crate::mir::builder::control_flow::plan::{DomainPlan, SplitScanPlan};

    // Try to extract using existing implementation
    let parts = finalize_extract(
        extract_split_scan_parts(condition, body, &[]),
        "phase29ab/pattern7/contract",
    )?;
    let plan = parts.map(|parts| SplitScanPlan {
        s_var: parts.s_var,
        sep_var: parts.sep_var,
        result_var: parts.result_var,
        i_var: parts.i_var,
        start_var: parts.start_var,
    });
    Ok(plan.map(DomainPlan::SplitScan))
}

/// Phase 256 P0: Extract SplitScanParts from AST
///
/// **P0 Strategy**: Fixed-form parser (Fail-Fast on mismatch)
/// - Accepts only the exact pattern shape
/// - Returns Err immediately on any deviation
/// - No fallback, no coercion (correctness first)
fn extract_split_scan_parts(
    condition: &ASTNode,
    body: &[ASTNode],
    post_loop_code: &[ASTNode],
) -> ExtractDecision<SplitScanParts> {
    // Step 1: Extract variables from loop condition
    // Expected: i <= s.length() - separator.length()
    let (i_var, s_var, sep_var) = match extract_loop_condition_vars(condition) {
        Ok(values) => values,
        Err(_) => return ExtractDecision::NotApplicable,
    };

    // Step 2: Find the if statement in loop body
    let if_stmt = match body.iter().find(|stmt| matches!(stmt, ASTNode::If { .. })) {
        Some(stmt) => stmt,
        None => return ExtractDecision::NotApplicable,
    };

    let (match_if_cond_ast, then_body, else_body) = match if_stmt {
        ASTNode::If {
            condition,
            then_body,
            else_body,
            ..
        } => (condition.as_ref().clone(), then_body, else_body),
        _ => return ExtractDecision::NotApplicable,
    };

    if let Err(err) = validate_separator_literal_len(&match_if_cond_ast) {
        return ExtractDecision::Contract(err);
    }

    // Step 3: Extract push operation from then branch
    let then_push_ast = match then_body
        .iter()
        .find(|stmt| matches!(stmt, ASTNode::MethodCall { method, .. } if method == "push"))
    {
        Some(stmt) => stmt.clone(),
        None => return ExtractDecision::NotApplicable,
    };

    // Step 4: Extract start assignment (start = i + separator.length())
    let then_start_next_ast = match then_body
        .iter()
        .find(|stmt| {
            matches!(stmt, ASTNode::Assignment { target, .. } if {
                matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "start")
            })
        }) {
        Some(stmt) => stmt.clone(),
        None => return ExtractDecision::NotApplicable,
    };

    // Step 5: Extract i assignment (i = start or i = variable)
    let then_i_next_ast = match then_body.iter().find(|stmt| {
        matches!(stmt, ASTNode::Assignment { target, value, .. } if {
            matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "i")
                && matches!(value.as_ref(), ASTNode::Variable { .. })
        })
    }) {
        Some(stmt) => stmt.clone(),
        None => return ExtractDecision::NotApplicable,
    };

    // Step 6: Extract else branch assignment (i = i + 1)
    let else_i_next_ast = if let Some(else_statements) = else_body {
        match else_statements.iter().find(|stmt| {
            matches!(stmt, ASTNode::Assignment { target, .. } if {
                matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "i")
            })
        }) {
            Some(stmt) => stmt.clone(),
            None => return ExtractDecision::NotApplicable,
        }
    } else {
        return ExtractDecision::NotApplicable;
    };

    if let Err(err) = validate_start_update(&then_start_next_ast, &i_var, &sep_var) {
        return ExtractDecision::Contract(err);
    }
    if let Err(err) = validate_i_set_to_start(&then_i_next_ast, &i_var) {
        return ExtractDecision::Contract(err);
    }
    if let Err(err) = validate_i_increment_by_one(&else_i_next_ast, &i_var) {
        return ExtractDecision::Contract(err);
    }

    // Step 7: Extract post-loop push (result.push(...))
    let post_push_ast = post_loop_code
        .iter()
        .find(|stmt| matches!(stmt, ASTNode::MethodCall { method, .. } if method == "push"))
        .cloned();

    // Step 8: Extract result variable from push statements
    let result_var = match extract_result_var(&then_push_ast) {
        Ok(value) => value,
        Err(_) => return ExtractDecision::NotApplicable,
    };

    ExtractDecision::Match(SplitScanParts {
        s_var,
        sep_var,
        result_var,
        i_var,
        start_var: "start".to_string(), // Fixed for P0
        loop_cond_ast: condition.clone(),
        match_if_cond_ast,
        then_push_ast,
        then_start_next_ast,
        then_i_next_ast,
        else_i_next_ast,
        post_push_ast,
    })
}

/// Extract i_var, s_var, sep_var from loop condition
/// Expected: i <= s.length() - separator.length()
fn extract_loop_condition_vars(condition: &ASTNode) -> Result<(String, String, String), String> {
    use crate::ast::BinaryOperator;

    // Loop condition: i <= s.length() - separator.length()
    // This is a BinaryOp node with operator LessEqual

    match condition {
        ASTNode::BinaryOp {
            left,
            operator,
            right,
            ..
        } if *operator == BinaryOperator::LessEqual => {
            // Left should be: Variable { name: "i" }
            let i_var = match left.as_ref() {
                ASTNode::Variable { name, .. } => name.clone(),
                _ => {
                    return Err("extract_loop_condition_vars: Left side not a variable".to_string());
                }
            };

            // Right should be: s.length() - separator.length()
            // This is a BinaryOp: minus
            let (s_var, sep_var) = extract_subtraction_vars(right)?;

            Ok((i_var, s_var, sep_var))
        }
        _ => Err("extract_loop_condition_vars: Not a <= comparison".to_string()),
    }
}

/// Extract s and sep from: s.length() - separator.length()
fn extract_subtraction_vars(expr: &ASTNode) -> Result<(String, String), String> {
    use crate::ast::BinaryOperator;

    match expr {
        ASTNode::BinaryOp {
            left,
            operator,
            right,
            ..
        } if *operator == BinaryOperator::Subtract => {
            // Left: s.length()
            let s_var = match left.as_ref() {
                ASTNode::MethodCall { object, method, .. } if method == "length" => {
                    match object.as_ref() {
                        ASTNode::Variable { name, .. } => name.clone(),
                        _ => return Err("extract_subtraction_vars: length() object not variable".to_string()),
                    }
                }
                _ => return Err("extract_subtraction_vars: Left not s.length()".to_string()),
            };

            // Right: separator.length()
            let sep_var = match right.as_ref() {
                ASTNode::MethodCall { object, method, .. } if method == "length" => {
                    match object.as_ref() {
                        ASTNode::Variable { name, .. } => name.clone(),
                        _ => return Err("extract_subtraction_vars: length() object not variable".to_string()),
                    }
                }
                _ => return Err("extract_subtraction_vars: Right not separator.length()".to_string()),
            };

            Ok((s_var, sep_var))
        }
        _ => Err("extract_subtraction_vars: Not a subtraction".to_string()),
    }
}

fn validate_start_update(
    assign: &ASTNode,
    i_var: &str,
    sep_var: &str,
) -> Result<(), ContractViolation> {
    use crate::ast::BinaryOperator;

    let hint = "use `start = i + separator.length()` in the then-branch";
    match assign {
        ASTNode::Assignment { target, value, .. } => {
            if !matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == "start") {
                return Err(ContractViolation::new(
                    "split scan contract: start target must be `start`",
                    hint,
                ));
            }

            match value.as_ref() {
                ASTNode::BinaryOp {
                    operator: BinaryOperator::Add,
                    left,
                    right,
                    ..
                } => {
                    let left_ok = matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == i_var);
                    let right_ok = matches!(right.as_ref(), ASTNode::MethodCall { object, method, arguments, .. }
                        if method == "length"
                            && arguments.is_empty()
                            && matches!(object.as_ref(), ASTNode::Variable { name, .. } if name == sep_var)
                    );

                    if left_ok && right_ok {
                        Ok(())
                    } else {
                        Err(ContractViolation::new(
                            "split scan contract: start update must be `i + separator.length()`",
                            hint,
                        ))
                    }
                }
                _ => Err(ContractViolation::new(
                    "split scan contract: start update must be `i + separator.length()`",
                    hint,
                )),
            }
        }
        _ => Err(ContractViolation::new(
            "split scan contract: expected start assignment",
            hint,
        )),
    }
}

fn validate_i_set_to_start(assign: &ASTNode, i_var: &str) -> Result<(), ContractViolation> {
    let hint = "use `i = start` in the then-branch";
    match assign {
        ASTNode::Assignment { target, value, .. } => {
            let target_ok = matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == i_var);
            let value_ok = matches!(value.as_ref(), ASTNode::Variable { name, .. } if name == "start");
            if target_ok && value_ok {
                Ok(())
            } else {
                Err(ContractViolation::new(
                    "split scan contract: then i update must be `i = start`",
                    hint,
                ))
            }
        }
        _ => Err(ContractViolation::new(
            "split scan contract: expected then i assignment",
            hint,
        )),
    }
}

fn validate_i_increment_by_one(
    assign: &ASTNode,
    i_var: &str,
) -> Result<(), ContractViolation> {
    use crate::ast::{BinaryOperator, LiteralValue};

    let hint = "use `i = i + 1` in the else-branch";
    match assign {
        ASTNode::Assignment { target, value, .. } => {
            let target_ok = matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == i_var);
            let value_ok = matches!(value.as_ref(), ASTNode::BinaryOp {
                operator: BinaryOperator::Add,
                left,
                right,
                ..
            } if matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == i_var)
                && matches!(right.as_ref(), ASTNode::Literal { value: LiteralValue::Integer(1), .. })
            );

            if target_ok && value_ok {
                Ok(())
            } else {
                Err(ContractViolation::new(
                    "split scan contract: else i update must be `i = i + 1`",
                    hint,
                ))
            }
        }
        _ => Err(ContractViolation::new(
            "split scan contract: expected else i assignment",
            hint,
        )),
    }
}

fn validate_separator_literal_len(cond: &ASTNode) -> Result<(), ContractViolation> {
    use crate::ast::{BinaryOperator, LiteralValue};

    let hint = "use a 1-char separator (e.g. \",\") or avoid split-scan patterns";
    let (left, right) = match cond {
        ASTNode::BinaryOp {
            operator: BinaryOperator::Equal,
            left,
            right,
            ..
        } => (left.as_ref(), right.as_ref()),
        _ => return Ok(()),
    };

    let literal = match (left, right) {
        (ASTNode::Literal { value: LiteralValue::String(s), .. }, _) => Some(s),
        (_, ASTNode::Literal { value: LiteralValue::String(s), .. }) => Some(s),
        _ => None,
    };

    if let Some(value) = literal {
        if value.chars().count() != 1 {
            return Err(ContractViolation::new(
                "split scan contract: separator must be 1 char (P0)",
                hint,
            ));
        }
    }

    Ok(())
}

/// Extract result variable name from push call
/// Expected: result.push(...)
fn extract_result_var(push_stmt: &ASTNode) -> Result<String, String> {
    match push_stmt {
        ASTNode::MethodCall { object, method, .. } if method == "push" => {
            match object.as_ref() {
                ASTNode::Variable { name, .. } => Ok(name.clone()),
                _ => Err("extract_result_var: push() object not a variable".to_string()),
            }
        }
        _ => Err("extract_result_var: Not a push() call".to_string()),
    }
}
