//! Thin wrapper around `calcpad_engine::SheetContext` for the iced UI.
//!
//! Exposes a simple `evaluate_sheet(text) -> Vec<LineResult>` interface so
//! the UI layer does not interact with the engine types directly.

use calcpad_engine::{CalcResult, CalcValue, ResultType, SheetContext};

/// A pre-formatted result for a single line, ready for display.
#[derive(Debug, Clone)]
pub struct LineResult {
    /// The display string shown in the results column.
    /// Empty string means the line produced no visible result (comment, blank, heading).
    pub display: String,
    /// Whether this result is an error.
    pub is_error: bool,
}

/// Evaluate a full sheet of text, returning one `LineResult` per line.
///
/// Uses `SheetContext` for full dependency resolution, variables,
/// aggregators, unit conversions, and all engine features.
pub fn evaluate_sheet(text: &str) -> Vec<LineResult> {
    if text.is_empty() {
        return vec![LineResult {
            display: String::new(),
            is_error: false,
        }];
    }

    let lines: Vec<&str> = text.lines().collect();
    let mut ctx = SheetContext::new();

    for (i, line) in lines.iter().enumerate() {
        ctx.set_line(i, line);
    }

    let results = ctx.eval();

    let mut output: Vec<LineResult> = results.into_iter().map(|r| format_result(&r)).collect();

    // Ensure we have at least as many results as lines
    while output.len() < lines.len() {
        output.push(LineResult {
            display: String::new(),
            is_error: false,
        });
    }

    output
}

/// Format a `CalcResult` into a `LineResult`.
fn format_result(result: &CalcResult) -> LineResult {
    match &result.value {
        CalcValue::Error { message, .. } => {
            // Suppress "noise" errors from empty/comment lines
            let suppressed = message == "Empty expression"
                || message == "no expression found"
                || message == "No result";

            if suppressed {
                LineResult {
                    display: String::new(),
                    is_error: false,
                }
            } else {
                LineResult {
                    display: result.metadata.display.clone(),
                    is_error: true,
                }
            }
        }
        _ => LineResult {
            display: result.metadata.display.clone(),
            is_error: result.metadata.result_type == ResultType::Error,
        },
    }
}

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

    #[test]
    fn test_empty_input() {
        let results = evaluate_sheet("");
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].display, "");
        assert!(!results[0].is_error);
    }

    #[test]
    fn test_single_expression() {
        let results = evaluate_sheet("2 + 2");
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].display, "4");
        assert!(!results[0].is_error);
    }

    #[test]
    fn test_multiple_lines() {
        let results = evaluate_sheet("10\n20\n30");
        assert_eq!(results.len(), 3);
        assert_eq!(results[0].display, "10");
        assert_eq!(results[1].display, "20");
        assert_eq!(results[2].display, "30");
    }

    #[test]
    fn test_variables() {
        let results = evaluate_sheet("x = 10\nx * 2");
        assert_eq!(results.len(), 2);
        assert_eq!(results[0].display, "10");
        assert_eq!(results[1].display, "20");
    }

    #[test]
    fn test_variable_dependency() {
        let results = evaluate_sheet("price = 100\ntax = 15\nprice + tax");
        assert_eq!(results.len(), 3);
        assert_eq!(results[0].display, "100");
        assert_eq!(results[1].display, "15");
        assert_eq!(results[2].display, "115");
    }

    #[test]
    fn test_aggregator_total() {
        let results = evaluate_sheet("10\n20\n30\ntotal");
        assert_eq!(results.len(), 4);
        assert_eq!(results[3].display, "60");
    }

    #[test]
    fn test_empty_lines_produce_empty_results() {
        let results = evaluate_sheet("10\n\n20");
        assert_eq!(results.len(), 3);
        assert_eq!(results[0].display, "10");
        assert_eq!(results[1].display, "");
        assert_eq!(results[2].display, "20");
    }

    #[test]
    fn test_comment_lines() {
        let results = evaluate_sheet("// this is a comment\n42");
        assert_eq!(results.len(), 2);
        assert_eq!(results[0].display, "");
        assert_eq!(results[1].display, "42");
    }

    #[test]
    fn test_complex_expressions() {
        let results = evaluate_sheet("(2 + 3) * 4");
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].display, "20");
    }

    #[test]
    fn test_non_math_text() {
        let results = evaluate_sheet("hello world");
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].display, "");
    }

    #[test]
    fn test_native_types_no_serialization() {
        // Verify results come back as native Rust types through SheetContext.
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "2 + 2");
        let results = ctx.eval();
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].value, CalcValue::Number { value: 4.0 });
    }

    #[test]
    fn test_full_sheet_with_dependencies() {
        let mut ctx = SheetContext::new();
        ctx.set_line(0, "base = 100");
        ctx.set_line(1, "rate = 0.15");
        ctx.set_line(2, "base * rate");
        let results = ctx.eval();
        assert_eq!(results.len(), 3);
        assert_eq!(results[0].value, CalcValue::Number { value: 100.0 });
        assert_eq!(results[1].value, CalcValue::Number { value: 0.15 });
        assert_eq!(results[2].value, CalcValue::Number { value: 15.0 });
    }

    #[test]
    fn test_performance_1000_lines() {
        let lines: Vec<String> = (0..1000)
            .map(|i| format!("{} + {} * {}", i, i + 1, i + 2))
            .collect();
        let sheet = lines.join("\n");

        let start = std::time::Instant::now();
        let results = evaluate_sheet(&sheet);
        let elapsed = start.elapsed();

        assert_eq!(results.len(), 1000);
        for r in &results {
            assert!(!r.display.is_empty(), "Every line should produce a result");
        }

        // Must complete 1000 evaluations within one 60fps frame budget (16ms).
        assert!(
            elapsed.as_millis() < 100, // generous for CI, expect <16ms locally
            "1000 evaluations took {}ms",
            elapsed.as_millis()
        );
    }
}
