//! Phase 29ai P11: Pattern2BreakFacts (Facts SSOT)

use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
use crate::mir::builder::control_flow::plan::planner::Freeze;
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, extract_loop_increment_plan,
};

#[derive(Debug, Clone)]
pub(in crate::mir::builder) struct Pattern2BreakFacts {
    pub loop_var: String,
    pub carrier_var: String,
    pub loop_condition: ASTNode,
    pub break_condition: ASTNode,
    pub carrier_update_in_break: Option<ASTNode>,
    pub carrier_update_in_body: ASTNode,
    pub loop_increment: ASTNode,
}

pub(in crate::mir::builder) fn try_extract_pattern2_break_facts(
    condition: &ASTNode,
    body: &[ASTNode],
) -> Result<Option<Pattern2BreakFacts>, Freeze> {
    let Some(loop_var) = extract_loop_var_for_plan_subset(condition) else {
        return Ok(None);
    };

    let break_count = count_control_flow(body, ControlFlowDetector::default()).break_count;
    if break_count != 1 {
        return Ok(None);
    }
    if has_continue_statement(body) {
        return Ok(None);
    }
    if has_return_statement(body) {
        return Ok(None);
    }

    if body.len() != 3 {
        return Ok(None);
    }

    let (break_condition, carrier_update_in_break) =
        match extract_break_if_parts(&body[0]) {
            Some(parts) => parts,
            None => return Ok(None),
        };

    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),
    };

    let loop_increment = match extract_loop_increment_plan(body, &loop_var) {
        Ok(Some(inc)) => inc,
        _ => return Ok(None),
    };

    Ok(Some(Pattern2BreakFacts {
        loop_var,
        carrier_var,
        loop_condition: condition.clone(),
        break_condition,
        carrier_update_in_break,
        carrier_update_in_body,
        loop_increment,
    }))
}

fn extract_loop_var_for_plan_subset(condition: &ASTNode) -> Option<String> {
    let ASTNode::BinaryOp {
        operator: BinaryOperator::Less,
        left,
        right,
        ..
    } = condition
    else {
        return None;
    };
    let ASTNode::Variable { name, .. } = left.as_ref() else {
        return None;
    };
    if !matches!(
        right.as_ref(),
        ASTNode::Literal {
            value: LiteralValue::Integer(_),
            ..
        }
    ) {
        return None;
    }

    Some(name.clone())
}

fn extract_break_if_parts(stmt: &ASTNode) -> Option<(ASTNode, Option<ASTNode>)> {
    let ASTNode::If {
        condition,
        then_body,
        else_body,
        ..
    } = stmt
    else {
        return None;
    };
    if else_body.is_some() {
        return None;
    }

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

    let carrier_update_in_break = if then_body.len() == 1 {
        None
    } else if then_body.len() == 2 {
        match &then_body[0] {
            ASTNode::Assignment { value, .. } => Some(value.as_ref().clone()),
            _ => return None,
        }
    } else {
        return None;
    };

    Some((condition.as_ref().clone(), carrier_update_in_break))
}

fn has_continue_statement(body: &[ASTNode]) -> bool {
    common_has_continue(body)
}

fn has_return_statement(body: &[ASTNode]) -> bool {
    common_has_return(body)
}
