use crate::ast::{Expr, ExprKind};
use crate::context::EvalContext;
use crate::interpreter;
use crate::lexer;
use crate::parser;
use crate::span::Span;
use crate::types::CalcResult;
use crate::variables::aggregators::{self, AggregatorKind};
use std::collections::{HashMap, HashSet};

/// A parsed line in the sheet.
#[derive(Debug, Clone)]
struct LineEntry {
    /// The raw source text.
    source: String,
    /// The parsed AST, or None if parse failed.
    parsed: Option<Expr>,
    /// The parse error, if any.
    parse_error: Option<String>,
    /// Variable name defined by this line (if assignment).
    defines_var: Option<String>,
    /// Variable names referenced by this line.
    references_vars: Vec<String>,
    /// Whether this line is a non-calculable text/comment line.
    is_text: bool,
    /// Whether this line is a heading (e.g., `## Title`).
    is_heading: bool,
    /// The aggregator kind, if this line is a standalone aggregator keyword.
    aggregator: Option<AggregatorKind>,
}

/// SheetContext holds all evaluation state for a multi-line sheet.
///
/// It manages variables, line results, dependency graphs, and supports
/// selective re-evaluation when lines change.
#[derive(Debug, Clone)]
pub struct SheetContext {
    /// Lines stored by index. Sparse -- not all indices need to be filled.
    lines: HashMap<usize, LineEntry>,
    /// Cached results per line from the last evaluation.
    results: HashMap<usize, CalcResult>,
    /// The maximum line index seen (for iteration order).
    max_line: usize,
    /// Tracks which lines have been modified since last eval.
    dirty_lines: HashSet<usize>,
    /// Whether a full evaluation has been performed at least once.
    has_evaluated: bool,
}

impl SheetContext {
    /// Create a new, empty SheetContext.
    pub fn new() -> Self {
        SheetContext {
            lines: HashMap::new(),
            results: HashMap::new(),
            max_line: 0,
            dirty_lines: HashSet::new(),
            has_evaluated: false,
        }
    }

    /// Set (or replace) a line at the given index.
    pub fn set_line(&mut self, index: usize, source: &str) {
        if index > self.max_line {
            self.max_line = index;
        }

        let trimmed = source.trim();

        // Detect headings and aggregator keywords before tokenizing
        let is_heading = aggregators::is_heading(trimmed);
        let aggregator = aggregators::detect_aggregator(trimmed);

        // Headings and aggregators are handled specially, not parsed as expressions
        if is_heading || aggregator.is_some() {
            let entry = LineEntry {
                source: source.to_string(),
                parsed: None,
                parse_error: None,
                defines_var: None,
                references_vars: Vec::new(),
                is_text: is_heading,
                is_heading,
                aggregator,
            };
            self.lines.insert(index, entry);
            self.dirty_lines.insert(index);
            return;
        }

        // Tokenize and parse through the real engine pipeline
        let tokens = lexer::tokenize(trimmed);

        // Check if this is a text-only or comment-only line
        let is_text = !tokens.iter().any(|t| {
            !matches!(
                t.kind,
                crate::token::TokenKind::Text(_)
                    | crate::token::TokenKind::Comment(_)
                    | crate::token::TokenKind::Eof
            )
        });

        let (parsed, parse_error) = if is_text || trimmed.is_empty() {
            (None, None)
        } else {
            match parser::parse(tokens) {
                Ok(expr) => (Some(expr), None),
                Err(e) => (None, Some(e.message)),
            }
        };

        let defines_var = parsed.as_ref().and_then(defined_variable);
        let references_vars = parsed
            .as_ref()
            .map(referenced_variables)
            .unwrap_or_default();

        let entry = LineEntry {
            source: source.to_string(),
            parsed,
            parse_error,
            defines_var,
            references_vars,
            is_text,
            is_heading: false,
            aggregator: None,
        };

        self.lines.insert(index, entry);
        self.dirty_lines.insert(index);
    }

    /// Remove a line at the given index.
    pub fn remove_line(&mut self, index: usize) {
        self.lines.remove(&index);
        self.results.remove(&index);
        self.dirty_lines.insert(index);
    }

    /// Get the number of lines in the sheet.
    pub fn line_count(&self) -> usize {
        self.lines.len()
    }

    /// Check if the sheet has any variables defined.
    pub fn has_variables(&self) -> bool {
        self.lines.values().any(|e| e.defines_var.is_some())
    }

    /// Evaluate all lines in the sheet, returning results in line-index order.
    ///
    /// This method performs dependency analysis and selective re-evaluation:
    /// - Lines whose dependencies haven't changed are not recomputed.
    /// - Circular dependencies are detected and reported as errors.
    /// - Line results are stored as `__line_N` variables for line references.
    /// - The `__prev` variable tracks the most recent numeric result for `prev`/`ans`.
    pub fn eval(&mut self) -> Vec<CalcResult> {
        let line_indices = self.sorted_line_indices();

        if line_indices.is_empty() {
            self.has_evaluated = true;
            self.dirty_lines.clear();
            return Vec::new();
        }

        // Build the dependency graph
        let var_to_line = self.build_var_to_line_map(&line_indices);

        // Detect circular dependencies
        let circular_lines = self.detect_circular_deps(&line_indices, &var_to_line);

        // Determine which lines need re-evaluation
        let lines_to_eval = if !self.has_evaluated {
            // First eval: evaluate everything
            line_indices.iter().copied().collect::<HashSet<_>>()
        } else {
            self.compute_dirty_set(&line_indices, &var_to_line)
        };

        // Build a shared EvalContext and evaluate in order.
        // We rebuild the context for the full pass so variables propagate correctly.
        let mut ctx = EvalContext::new();
        // Track subtotal values for grand total computation
        let mut subtotal_values: Vec<f64> = Vec::new();
        // Collect sources and results for aggregator section scanning
        let mut ordered_results: Vec<CalcResult> = Vec::new();
        let mut ordered_sources: Vec<String> = Vec::new();

        for &idx in &line_indices {
            if circular_lines.contains(&idx) {
                let result = CalcResult::error(
                    "Circular dependency detected",
                    Span::new(0, 1),
                );
                // Store line ref even for errors (as error)
                ctx.set_variable(&format!("__line_{}", idx + 1), result.clone());
                ordered_results.push(result.clone());
                ordered_sources.push(
                    self.lines.get(&idx).map(|e| e.source.clone()).unwrap_or_default(),
                );
                self.results.insert(idx, result);
                continue;
            }

            // Extract needed fields from entry to avoid borrow conflicts
            let entry = &self.lines[&idx];
            let entry_source = entry.source.clone();
            let entry_is_heading = entry.is_heading;
            let entry_aggregator = entry.aggregator;
            let entry_is_text = entry.is_text;
            let entry_parsed = entry.parsed.clone();
            let entry_parse_error = entry.parse_error.clone();
            let entry_defines_var = entry.defines_var.clone();
            // Drop the borrow of self.lines
            drop(entry);

            // Heading lines produce no result -- skip them
            if entry_is_heading {
                let result = CalcResult::non_calculable(Span::new(0, entry_source.len()));
                ordered_results.push(result.clone());
                ordered_sources.push(entry_source);
                self.results.insert(idx, result);
                continue;
            }

            // Aggregator lines — compute section aggregation
            if let Some(agg_kind) = entry_aggregator {
                let span = Span::new(0, entry_source.trim().len());
                let result = if agg_kind == AggregatorKind::GrandTotal {
                    aggregators::compute_grand_total(&subtotal_values, span)
                } else {
                    let section_line_idx = ordered_results.len();
                    let values = aggregators::collect_section_values(
                        &ordered_results,
                        &ordered_sources,
                        section_line_idx,
                    );
                    let agg_result = aggregators::compute_aggregation(agg_kind, &values, span);

                    // Track subtotal values for grand total
                    if agg_kind == AggregatorKind::Subtotal {
                        if let crate::types::CalcValue::Number { value } = &agg_result.value {
                            subtotal_values.push(*value);
                        }
                    }
                    agg_result
                };

                self.results.insert(idx, result.clone());
                // Store as __line_N for line reference support
                ctx.set_variable(&format!("__line_{}", idx + 1), result.clone());
                // Update __prev for prev/ans support
                if result.is_calculable() {
                    ctx.set_variable("__prev", result.clone());
                }
                ordered_results.push(result);
                ordered_sources.push(entry_source);
                continue;
            }

            // Text/empty lines produce no result -- skip them
            if entry_is_text || entry_source.trim().is_empty() {
                let result = if entry_source.trim().is_empty() {
                    CalcResult::empty(Span::new(0, 0))
                } else {
                    CalcResult::non_calculable(Span::new(0, entry_source.len()))
                };
                ordered_results.push(result.clone());
                ordered_sources.push(entry_source);
                self.results.insert(idx, result);
                continue;
            }

            if lines_to_eval.contains(&idx) {
                // Evaluate this line
                if let Some(ref expr) = entry_parsed {
                    let result = interpreter::evaluate(expr, &mut ctx);
                    self.results.insert(idx, result.clone());
                    // Store as __line_N for line reference support (1-indexed)
                    ctx.set_variable(&format!("__line_{}", idx + 1), result.clone());
                    // Update __prev for prev/ans support (only for non-error results)
                    if result.is_calculable() {
                        ctx.set_variable("__prev", result.clone());
                    }
                    ordered_results.push(result);
                    ordered_sources.push(entry_source);
                } else {
                    // Parse error
                    let err_msg = entry_parse_error
                        .as_deref()
                        .unwrap_or("Parse error");
                    let result = CalcResult::error(err_msg, Span::new(0, 1));
                    ordered_results.push(result.clone());
                    ordered_sources.push(entry_source);
                    self.results.insert(idx, result);
                }
            } else {
                // Reuse cached result, but still replay variable definitions into ctx
                if let Some(cached) = self.results.get(&idx).cloned() {
                    if let Some(ref var_name) = entry_defines_var {
                        ctx.set_variable(var_name, cached.clone());
                    }
                    // Replay line ref and prev for cached results too
                    ctx.set_variable(&format!("__line_{}", idx + 1), cached.clone());
                    if cached.is_calculable() {
                        ctx.set_variable("__prev", cached.clone());
                    }
                    ordered_results.push(cached);
                    ordered_sources.push(entry_source);
                } else {
                    ordered_results.push(CalcResult::error("No result", Span::new(0, 1)));
                    ordered_sources.push(entry_source);
                }
            }
        }

        self.has_evaluated = true;
        self.dirty_lines.clear();

        // Return results in line order
        line_indices
            .iter()
            .map(|idx| {
                self.results
                    .get(idx)
                    .cloned()
                    .unwrap_or_else(|| CalcResult::error("No result", Span::new(0, 1)))
            })
            .collect()
    }

    /// Get the dependency graph as a map of line index -> set of line indices it depends on.
    pub fn dependency_graph(&self) -> HashMap<usize, HashSet<usize>> {
        let line_indices = self.sorted_line_indices();
        let var_to_line = self.build_var_to_line_map(&line_indices);
        let mut graph: HashMap<usize, HashSet<usize>> = HashMap::new();

        for &idx in &line_indices {
            let mut deps = HashSet::new();
            if let Some(entry) = self.lines.get(&idx) {
                for var in &entry.references_vars {
                    if let Some(&def_line) = var_to_line.get(var) {
                        deps.insert(def_line);
                    }
                }
            }
            graph.insert(idx, deps);
        }

        graph
    }

    // --- Internal helpers ---

    fn sorted_line_indices(&self) -> Vec<usize> {
        let mut indices: Vec<usize> = self.lines.keys().copied().collect();
        indices.sort();
        indices
    }

    /// Build a map from variable name to the line index that defines it.
    /// Also maps `__line_N` references to the correct 0-based line index.
    fn build_var_to_line_map(&self, line_indices: &[usize]) -> HashMap<String, usize> {
        let mut map = HashMap::new();
        for &idx in line_indices {
            if let Some(entry) = self.lines.get(&idx) {
                if let Some(ref var_name) = entry.defines_var {
                    map.insert(var_name.clone(), idx);
                }
            }
            // Map __line_N (1-indexed) to line index (0-indexed)
            map.insert(format!("__line_{}", idx + 1), idx);
        }
        // __prev dependencies are handled dynamically during evaluation:
        // each line referencing __prev depends on the most recent line that produced
        // a non-error result. Since evaluation is always in line order, this works
        // without explicit dependency tracking.
        map
    }

    /// Detect circular dependencies using DFS cycle detection.
    fn detect_circular_deps(
        &self,
        line_indices: &[usize],
        var_to_line: &HashMap<String, usize>,
    ) -> HashSet<usize> {
        let mut circular = HashSet::new();

        // Build adjacency list: line -> lines it depends on
        let mut adj: HashMap<usize, Vec<usize>> = HashMap::new();
        for &idx in line_indices {
            let mut deps = Vec::new();
            if let Some(entry) = self.lines.get(&idx) {
                for var in &entry.references_vars {
                    if let Some(&dep_line) = var_to_line.get(var) {
                        deps.push(dep_line);
                    }
                }
            }
            adj.insert(idx, deps);
        }

        // DFS for each node
        let mut visited = HashSet::new();
        let mut in_stack = HashSet::new();

        for &idx in line_indices {
            if !visited.contains(&idx) {
                Self::dfs_cycle(idx, &adj, &mut visited, &mut in_stack, &mut circular);
            }
        }

        circular
    }

    fn dfs_cycle(
        node: usize,
        adj: &HashMap<usize, Vec<usize>>,
        visited: &mut HashSet<usize>,
        in_stack: &mut HashSet<usize>,
        circular: &mut HashSet<usize>,
    ) {
        visited.insert(node);
        in_stack.insert(node);

        if let Some(deps) = adj.get(&node) {
            for &dep in deps {
                if !visited.contains(&dep) {
                    Self::dfs_cycle(dep, adj, visited, in_stack, circular);
                } else if in_stack.contains(&dep) {
                    // Found a cycle -- mark both nodes
                    circular.insert(dep);
                    circular.insert(node);
                }
            }
        }

        in_stack.remove(&node);
    }

    /// Compute the set of lines that need re-evaluation based on dirty lines
    /// and their dependents (transitive closure).
    fn compute_dirty_set(
        &self,
        line_indices: &[usize],
        var_to_line: &HashMap<String, usize>,
    ) -> HashSet<usize> {
        let mut dirty = self.dirty_lines.clone();

        // Build reverse map: line -> lines that depend on it
        let mut dependents: HashMap<usize, Vec<usize>> = HashMap::new();
        for &idx in line_indices {
            if let Some(entry) = self.lines.get(&idx) {
                for var in &entry.references_vars {
                    if let Some(&def_line) = var_to_line.get(var) {
                        dependents
                            .entry(def_line)
                            .or_default()
                            .push(idx);
                    }
                }
            }
        }

        // BFS to propagate dirtiness
        let mut queue: Vec<usize> = dirty.iter().copied().collect();
        while let Some(line) = queue.pop() {
            if let Some(deps) = dependents.get(&line) {
                for &dep in deps {
                    if dirty.insert(dep) {
                        queue.push(dep);
                    }
                }
            }
        }

        dirty
    }
}

impl Default for SheetContext {
    fn default() -> Self {
        Self::new()
    }
}

// --- AST helper functions ---

/// Extract the variable name defined by an assignment expression.
fn defined_variable(expr: &Expr) -> Option<String> {
    match &expr.node {
        ExprKind::Assignment { name, .. } => Some(name.clone()),
        _ => None,
    }
}

/// Collect all variable names referenced (but not defined) by an expression.
fn referenced_variables(expr: &Expr) -> Vec<String> {
    let mut vars = Vec::new();
    collect_references(&expr.node, &mut vars);
    vars
}

fn collect_references(node: &ExprKind, vars: &mut Vec<String>) {
    match node {
        ExprKind::Identifier(name) => {
            if !vars.contains(name) {
                vars.push(name.clone());
            }
        }
        ExprKind::Assignment { value, .. } => {
            collect_references(&value.node, vars);
        }
        ExprKind::BinaryOp { left, right, .. } => {
            collect_references(&left.node, vars);
            collect_references(&right.node, vars);
        }
        ExprKind::UnaryNeg(inner) => {
            collect_references(&inner.node, vars);
        }
        ExprKind::PercentOp { base, .. } => {
            collect_references(&base.node, vars);
        }
        ExprKind::Conversion { expr, .. } => {
            collect_references(&expr.node, vars);
        }
        ExprKind::DateRange { from, to } => {
            collect_references(&from.node, vars);
            collect_references(&to.node, vars);
        }
        ExprKind::Comparison { left, right, .. } => {
            collect_references(&left.node, vars);
            collect_references(&right.node, vars);
        }
        ExprKind::LineRef(line_num) => {
            let key = format!("__line_{}", line_num);
            if !vars.contains(&key) {
                vars.push(key);
            }
        }
        ExprKind::PrevRef => {
            let key = "__prev".to_string();
            if !vars.contains(&key) {
                vars.push(key);
            }
        }
        ExprKind::FunctionCall { args, .. } => {
            for arg in args {
                collect_references(&arg.node, vars);
            }
        }
        // Leaf nodes with no variable references
        ExprKind::Number(_)
        | ExprKind::UnitNumber { .. }
        | ExprKind::CurrencyValue { .. }
        | ExprKind::Boolean(_)
        | ExprKind::DateLiteral { .. }
        | ExprKind::Today
        | ExprKind::Duration { .. } => {}
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{CalcValue, ResultType};

    // === AC: New SheetContext is empty ===
    #[test]
    fn test_new_sheet_context_is_empty() {
        let ctx = SheetContext::new();
        assert_eq!(ctx.line_count(), 0);
        assert!(!ctx.has_variables());
        let dep_graph = ctx.dependency_graph();
        assert!(dep_graph.is_empty());
    }

    // === AC: Basic two-line evaluation ===
    #[test]
    fn test_basic_two_line_eval() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "x = 10");
        ctx.set_line(1, "x * 2");
        let results = ctx.eval();
        assert_eq!(results.len(), 2);
        assert_eq!(results[0].value, CalcValue::Number { value: 10.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 20.0 });
    }

    // === AC: Variable update propagates ===
    #[test]
    fn test_variable_update_propagates() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "x = 10");
        ctx.set_line(2, "x");
        let results = ctx.eval();
        assert_eq!(results[1].value, CalcValue::Number { value: 10.0 });

        // Change x to 20
        ctx.set_line(0, "x = 20");
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 20.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 20.0 });
    }

    // === AC: Clone is deep copy ===
    #[test]
    fn test_clone_is_deep_copy() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "x = 10");
        ctx.set_line(1, "x * 2");
        ctx.eval();

        let mut clone = ctx.clone();
        clone.set_line(0, "x = 99");
        clone.eval();

        // Original should be unchanged
        let orig_results = ctx.eval();
        assert_eq!(orig_results[0].value, CalcValue::Number { value: 10.0 });
        assert_eq!(orig_results[1].value, CalcValue::Number { value: 20.0 });

        // Clone should have new values
        let clone_results = clone.eval();
        assert_eq!(clone_results[0].value, CalcValue::Number { value: 99.0 });
        assert_eq!(clone_results[1].value, CalcValue::Number { value: 198.0 });
    }

    // === AC: Clone performance for undo/redo ===
    #[test]
    fn test_clone_performance_100_lines() {
        let mut ctx = SheetContext::new();
        for i in 0..100 {
            ctx.set_line(i, &format!("v{} = {}", i, i * 10));
        }
        ctx.eval();

        let start = std::time::Instant::now();
        let _clone = ctx.clone();
        let elapsed = start.elapsed();

        // Sub-millisecond requirement
        assert!(
            elapsed.as_millis() < 1,
            "Clone took {}ms, expected < 1ms",
            elapsed.as_millis()
        );
    }

    // === AC: Selective re-evaluation ===
    #[test]
    fn test_selective_re_evaluation() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "a = 10");
        ctx.set_line(1, "b = 20");
        ctx.set_line(2, "c = a + 5");
        ctx.set_line(3, "d = b + 5");
        ctx.eval();

        // Modify only line 1 (b = 20 -> b = 30)
        ctx.set_line(1, "b = 30");
        let results = ctx.eval();

        // line 0 (a = 10) and line 2 (c = a + 5 = 15) should be unchanged
        assert_eq!(results[0].value, CalcValue::Number { value: 10.0 });
        assert_eq!(results[2].value, CalcValue::Number { value: 15.0 });
        // line 1 (b = 30) and line 3 (d = b + 5 = 35) should be updated
        assert_eq!(results[1].value, CalcValue::Number { value: 30.0 });
        assert_eq!(results[3].value, CalcValue::Number { value: 35.0 });
    }

    // === AC: Dependency graph tracks correctly ===
    #[test]
    fn test_dependency_graph() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "x = 10");
        ctx.set_line(1, "y = 20");
        ctx.set_line(2, "x + y");
        ctx.set_line(3, "x * 2");

        let graph = ctx.dependency_graph();

        // Line 0 and 1 depend on nothing
        assert!(graph[&0].is_empty());
        assert!(graph[&1].is_empty());
        // Line 2 depends on lines 0 and 1
        assert!(graph[&2].contains(&0));
        assert!(graph[&2].contains(&1));
        // Line 3 depends on line 0
        assert!(graph[&3].contains(&0));
        assert!(!graph[&3].contains(&1));
    }

    // === AC: Circular dependency detection ===
    #[test]
    fn test_circular_dependency_detection() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "a = b");
        ctx.set_line(1, "b = a");

        let results = ctx.eval();
        assert_eq!(results.len(), 2);

        // Both lines should be errors
        assert_eq!(results[0].result_type(), ResultType::Error);
        assert_eq!(results[1].result_type(), ResultType::Error);

        // Check that error messages mention circular dependency
        if let CalcValue::Error { message, .. } = &results[0].value {
            assert!(
                message.contains("ircular"),
                "Expected circular dependency error, got: {}",
                message
            );
        }
    }

    // === Additional tests ===

    #[test]
    fn test_empty_eval() {
        let mut ctx = SheetContext::new();
        let results = ctx.eval();
        assert!(results.is_empty());
    }

    #[test]
    fn test_parse_error_line() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "???");
        ctx.set_line(2, "20");

        let results = ctx.eval();
        assert_eq!(results.len(), 3);
        assert_eq!(results[0].value, CalcValue::Number { value: 10.0 });
        assert_eq!(results[1].result_type(), ResultType::Error);
        assert_eq!(results[2].value, CalcValue::Number { value: 20.0 });
    }

    #[test]
    fn test_chained_variables() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "a = 5");
        ctx.set_line(1, "b = a * 2");
        ctx.set_line(2, "c = b + a");

        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 5.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 10.0 });
        assert_eq!(results[2].value, CalcValue::Number { value: 15.0 });
    }

    #[test]
    fn test_remove_line() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "20");
        assert_eq!(ctx.line_count(), 2);

        ctx.remove_line(1);
        assert_eq!(ctx.line_count(), 1);
    }

    #[test]
    fn test_sparse_line_indices() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "x = 10");
        ctx.set_line(5, "x + 5");

        let results = ctx.eval();
        assert_eq!(results.len(), 2);
        assert_eq!(results[0].value, CalcValue::Number { value: 10.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 15.0 });
    }

    #[test]
    fn test_multiple_evals_without_changes() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "x = 10");
        ctx.set_line(1, "x * 2");

        let results1 = ctx.eval();
        let results2 = ctx.eval();

        assert_eq!(results1, results2);
    }

    #[test]
    fn test_no_infinite_loop_on_circular() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "a = b");
        ctx.set_line(1, "b = c");
        ctx.set_line(2, "c = a");

        // This should complete without hanging
        let results = ctx.eval();
        assert_eq!(results.len(), 3);
        // All lines in the cycle should be errors
        for r in &results {
            assert_eq!(r.result_type(), ResultType::Error);
        }
    }

    #[test]
    fn test_independent_lines_not_recomputed() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "a = 10");
        ctx.set_line(1, "b = 20");
        ctx.set_line(2, "c = a + 5");
        ctx.eval();

        // Modify only line 1
        ctx.set_line(1, "b = 30");

        // Check dirty set: only line 1 should be dirty
        // Line 0 and line 2 should NOT be dirty since they don't depend on b
        let line_indices = ctx.sorted_line_indices();
        let var_to_line = ctx.build_var_to_line_map(&line_indices);
        let dirty = ctx.compute_dirty_set(&line_indices, &var_to_line);

        assert!(dirty.contains(&1), "Line 1 should be dirty");
        assert!(!dirty.contains(&0), "Line 0 should NOT be dirty");
        assert!(!dirty.contains(&2), "Line 2 should NOT be dirty (doesn't depend on b)");
    }

    // =========================================================================
    // Line References (#N and lineN)
    // =========================================================================

    #[test]
    fn test_line_ref_hash_syntax() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "100");      // line 1
        ctx.set_line(1, "#1 * 2");   // refers to line 1
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 100.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 200.0 });
    }

    #[test]
    fn test_line_ref_line_syntax() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "50");        // line 1
        ctx.set_line(1, "line1 + 10"); // refers to line 1
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 50.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 60.0 });
    }

    #[test]
    fn test_line_ref_invalid_out_of_range() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "#99 + 5");
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 10.0 });
        assert_eq!(results[1].result_type(), ResultType::Error);
    }

    #[test]
    fn test_line_ref_with_variables() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "x = 10");   // line 1, x = 10
        ctx.set_line(1, "20");       // line 2 = 20
        ctx.set_line(2, "x + #2");   // x (10) + line2 (20) = 30
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 10.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 20.0 });
        assert_eq!(results[2].value, CalcValue::Number { value: 30.0 });
    }

    // =========================================================================
    // Previous Line Reference (prev / ans)
    // =========================================================================

    #[test]
    fn test_prev_basic() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "50");
        ctx.set_line(1, "prev * 2");
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 50.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 100.0 });
    }

    #[test]
    fn test_ans_basic() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "50");
        ctx.set_line(1, "ans + 10");
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 50.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 60.0 });
    }

    #[test]
    fn test_prev_on_first_line_is_error() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "prev * 2");
        let results = ctx.eval();
        assert_eq!(results[0].result_type(), ResultType::Error);
    }

    #[test]
    fn test_prev_chain() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "prev + 5");   // 15
        ctx.set_line(2, "prev * 2");   // 30
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 10.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 15.0 });
        assert_eq!(results[2].value, CalcValue::Number { value: 30.0 });
    }

    // =========================================================================
    // Function Calls
    // =========================================================================

    #[test]
    fn test_sqrt_function() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "sqrt(16)");
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 4.0 });
    }

    #[test]
    fn test_abs_function() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "abs(-5)");
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 5.0 });
    }

    #[test]
    fn test_round_function() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "round(3.7)");
        let results = ctx.eval();
        assert_eq!(results[0].value, CalcValue::Number { value: 4.0 });
    }

    // =========================================================================
    // Aggregators
    // =========================================================================

    #[test]
    fn test_sum_aggregator() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "20");
        ctx.set_line(2, "30");
        ctx.set_line(3, "40");
        ctx.set_line(4, "sum");
        let results = ctx.eval();
        assert_eq!(results[4].value, CalcValue::Number { value: 100.0 });
    }

    #[test]
    fn test_total_aggregator() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "20");
        ctx.set_line(2, "30");
        ctx.set_line(3, "total");
        let results = ctx.eval();
        assert_eq!(results[3].value, CalcValue::Number { value: 60.0 });
    }

    #[test]
    fn test_average_aggregator() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "20");
        ctx.set_line(2, "30");
        ctx.set_line(3, "average");
        let results = ctx.eval();
        assert_eq!(results[3].value, CalcValue::Number { value: 20.0 });
    }

    #[test]
    fn test_min_aggregator() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "5");
        ctx.set_line(1, "12");
        ctx.set_line(2, "3");
        ctx.set_line(3, "8");
        ctx.set_line(4, "min");
        let results = ctx.eval();
        assert_eq!(results[4].value, CalcValue::Number { value: 3.0 });
    }

    #[test]
    fn test_max_aggregator() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "5");
        ctx.set_line(1, "12");
        ctx.set_line(2, "3");
        ctx.set_line(3, "8");
        ctx.set_line(4, "max");
        let results = ctx.eval();
        assert_eq!(results[4].value, CalcValue::Number { value: 12.0 });
    }

    #[test]
    fn test_count_aggregator() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "5");
        ctx.set_line(1, "12");
        ctx.set_line(2, "3");
        ctx.set_line(3, "8");
        ctx.set_line(4, "count");
        let results = ctx.eval();
        assert_eq!(results[4].value, CalcValue::Number { value: 4.0 });
    }

    #[test]
    fn test_aggregator_with_heading_section() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "20");
        ctx.set_line(2, "## Monthly Costs");
        ctx.set_line(3, "100");
        ctx.set_line(4, "200");
        ctx.set_line(5, "sum");
        let results = ctx.eval();
        // sum should only include lines 3 and 4 (after heading), not lines 0 and 1
        assert_eq!(results[5].value, CalcValue::Number { value: 300.0 });
    }

    #[test]
    fn test_empty_section_aggregator() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "## Empty Section");
        ctx.set_line(1, "sum");
        let results = ctx.eval();
        assert_eq!(results[1].value, CalcValue::Number { value: 0.0 });
    }

    #[test]
    fn test_subtotal_and_grand_total() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "## Section A");
        ctx.set_line(1, "100");
        ctx.set_line(2, "200");
        ctx.set_line(3, "subtotal");        // 300
        ctx.set_line(4, "## Section B");
        ctx.set_line(5, "50");
        ctx.set_line(6, "75");
        ctx.set_line(7, "subtotal");        // 125
        ctx.set_line(8, "grand total");     // 300 + 125 = 425
        let results = ctx.eval();
        assert_eq!(results[3].value, CalcValue::Number { value: 300.0 });
        assert_eq!(results[7].value, CalcValue::Number { value: 125.0 });
        assert_eq!(results[8].value, CalcValue::Number { value: 425.0 });
    }

    #[test]
    fn test_aggregator_skips_comments_and_errors() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "// This is a comment");
        ctx.set_line(2, "20");
        ctx.set_line(3, "sum");
        let results = ctx.eval();
        // sum should include 10 and 20, skipping the comment
        assert_eq!(results[3].value, CalcValue::Number { value: 30.0 });
    }

    #[test]
    fn test_aggregator_case_insensitive() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "10");
        ctx.set_line(1, "20");
        ctx.set_line(2, "SUM");
        let results = ctx.eval();
        assert_eq!(results[2].value, CalcValue::Number { value: 30.0 });
    }
}
