use crate::context::EvalContext;
use crate::interpreter::evaluate;
use crate::lexer::tokenize;
use crate::parser::parse;
use crate::span::Span;
use crate::types::CalcResult;
use crate::variables::aggregators;

/// Evaluate a single line of input and return the result.
pub fn eval_line(input: &str, ctx: &mut EvalContext) -> CalcResult {
    let trimmed = input.trim();
    if trimmed.is_empty() {
        return CalcResult::empty(Span::new(0, 0));
    }

    // Detect headings (# Title) and aggregator keywords (sum, total, etc.)
    // before tokenizing — the lexer misinterprets `#` as a line reference prefix.
    if aggregators::is_heading(trimmed) || aggregators::detect_aggregator(trimmed).is_some() {
        return CalcResult::non_calculable(Span::new(0, trimmed.len()));
    }

    let tokens = tokenize(trimmed);

    // Check for text-only or comment-only lines
    let has_expr = tokens.iter().any(|t| {
        !matches!(
            t.kind,
            crate::token::TokenKind::Text(_)
                | crate::token::TokenKind::Comment(_)
                | crate::token::TokenKind::Eof
        )
    });
    if !has_expr {
        return CalcResult::non_calculable(Span::new(0, trimmed.len()));
    }

    match parse(tokens) {
        Ok(expr) => evaluate(&expr, ctx),
        Err(e) => CalcResult::error(&e.message, e.span),
    }
}

/// Evaluate multiple lines of input, sharing context across lines.
/// Variable assignments on earlier lines are visible to later lines.
pub fn eval_sheet(lines: &[&str], ctx: &mut EvalContext) -> Vec<CalcResult> {
    lines.iter().map(|line| eval_line(line, ctx)).collect()
}
