/// Check command — type-check and analyze without compiling.
/// Outputs Elm-style diagnostics for any errors found.

use std::fs;
use std::path::Path;

pub fn cmd_check(file: &Path) {
    println!("🔍 DreamStack check");
    println!("   file: {}", file.display());

    let source = match fs::read_to_string(file) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("❌ Could not read {}: {}", file.display(), e);
            std::process::exit(1);
        }
    };

    let mut diagnostics: Vec<ds_diagnostic::Diagnostic> = Vec::new();

    // Lex
    let mut lexer = ds_parser::Lexer::new(&source);
    let tokens = lexer.tokenize();

    for tok in &tokens {
        if let ds_parser::TokenKind::Error(msg) = &tok.kind {
            diagnostics.push(ds_diagnostic::Diagnostic::error(
                msg.clone(),
                ds_parser::Span {
                    start: 0,
                    end: 0,
                    line: tok.line,
                    col: tok.col,
                },
            ).with_code("E0000"));
        }
    }

    // Parse (resilient — collect multiple errors)
    let mut parser = ds_parser::Parser::with_source(tokens, &source);
    let parse_result = parser.parse_program_resilient();

    // Convert parse errors → diagnostics
    for err in &parse_result.errors {
        diagnostics.push(ds_diagnostic::Diagnostic::from(err.clone()));
    }

    let program = parse_result.program;

    // Type check
    let mut checker = ds_types::TypeChecker::new();
    checker.check_program(&program);
    if checker.has_errors() {
        diagnostics.extend(checker.errors_as_diagnostics());
    }

    // Analyze
    let graph = ds_analyzer::SignalGraph::from_program(&program);
    let views = ds_analyzer::SignalGraph::analyze_views(&program);

    // Cycle detection diagnostics
    let (topo, cycle_diags) = graph.topological_order();
    diagnostics.extend(cycle_diags);

    // Sort all diagnostics: errors first, then by line
    ds_diagnostic::sort_diagnostics(&mut diagnostics);

    // Render diagnostics
    let error_count = diagnostics.iter().filter(|d| d.severity == ds_diagnostic::Severity::Error).count();
    let warning_count = diagnostics.iter().filter(|d| d.severity == ds_diagnostic::Severity::Warning).count();

    if !diagnostics.is_empty() {
        println!();
        for diag in &diagnostics {
            eprintln!("{}", ds_diagnostic::render(diag, &source));
        }
    }

    // Signal graph report
    println!();
    println!("   📊 Signal Graph:");
    for node in &graph.nodes {
        let kind_str = match &node.kind {
            ds_analyzer::SignalKind::Source => "source",
            ds_analyzer::SignalKind::Derived => "derived",
            ds_analyzer::SignalKind::Handler { .. } => "handler",
        };
        let deps: Vec<&str> = node.dependencies.iter().map(|d| d.signal_name.as_str()).collect();
        if deps.is_empty() {
            println!("     {} [{}]", node.name, kind_str);
        } else {
            println!("     {} [{}] ← depends on: {}", node.name, kind_str, deps.join(", "));
        }
    }

    println!();
    println!("   🖼️  Views:");
    for view in &views {
        println!("     {} ({} bindings)", view.name, view.bindings.len());
        for binding in &view.bindings {
            match &binding.kind {
                ds_analyzer::BindingKind::TextContent { signal } => {
                    println!("       📝 text bound to: {signal}");
                }
                ds_analyzer::BindingKind::EventHandler { element_tag, event, .. } => {
                    println!("       ⚡ {element_tag}.{event}");
                }
                ds_analyzer::BindingKind::Conditional { condition_signals } => {
                    println!("       ❓ conditional on: {}", condition_signals.join(", "));
                }
                ds_analyzer::BindingKind::StaticContainer { kind, child_count } => {
                    println!("       📦 {kind} ({child_count} children)");
                }
                ds_analyzer::BindingKind::StaticText { text } => {
                    println!("       📄 static: \"{text}\"");
                }
            }
        }
    }

    println!();
    println!("   🔄 Propagation order: {:?}", topo.iter().map(|&id| &graph.nodes[id].name).collect::<Vec<_>>());

    // Summary
    println!();
    if error_count == 0 && warning_count == 0 {
        println!("✅ No errors found");
    } else if error_count == 0 {
        println!("⚠️  {} warning(s)", warning_count);
    } else {
        eprintln!("❌ {} error(s), {} warning(s)", error_count, warning_count);
        std::process::exit(1);
    }
}

/// Run the check pipeline on source code without file I/O or process::exit.
/// Returns diagnostics for testing.
pub fn check_source(source: &str) -> Vec<ds_diagnostic::Diagnostic> {
    let mut diagnostics: Vec<ds_diagnostic::Diagnostic> = Vec::new();

    let mut lexer = ds_parser::Lexer::new(source);
    let tokens = lexer.tokenize();

    for tok in &tokens {
        if let ds_parser::TokenKind::Error(msg) = &tok.kind {
            diagnostics.push(ds_diagnostic::Diagnostic::error(
                msg.clone(),
                ds_parser::Span { start: 0, end: 0, line: tok.line, col: tok.col },
            ).with_code("E0000"));
        }
    }

    let mut parser = ds_parser::Parser::with_source(tokens, source);
    let parse_result = parser.parse_program_resilient();
    for err in &parse_result.errors {
        diagnostics.push(ds_diagnostic::Diagnostic::from(err.clone()));
    }
    let program = parse_result.program;

    let mut checker = ds_types::TypeChecker::new();
    checker.check_program(&program);
    if checker.has_errors() {
        diagnostics.extend(checker.errors_as_diagnostics());
    }

    let graph = ds_analyzer::SignalGraph::from_program(&program);
    let (_topo, cycle_diags) = graph.topological_order();
    diagnostics.extend(cycle_diags);

    ds_diagnostic::sort_diagnostics(&mut diagnostics);
    diagnostics
}

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

    #[test]
    fn test_check_valid_program() {
        let diags = check_source("let count = 0\nview main = text count");
        let errors: Vec<_> = diags.iter()
            .filter(|d| d.severity == ds_diagnostic::Severity::Error)
            .collect();
        assert!(errors.is_empty(), "valid program should have no errors: {:?}",
            errors.iter().map(|d| &d.message).collect::<Vec<_>>());
    }

    #[test]
    fn test_check_parse_error() {
        let diags = check_source("let 123 = bad syntax");
        assert!(!diags.is_empty(), "parse error should produce diagnostics");
    }

    #[test]
    fn test_check_cycle_detection() {
        // Ensure the full pipeline doesn't crash on programs with potential cycles
        let diags = check_source("let a = 0\nlet b = a + 1\nview main = text b");
        let err_count = diags.iter()
            .filter(|d| d.severity == ds_diagnostic::Severity::Error)
            .count();
        assert_eq!(err_count, 0, "valid DAG should not produce cycle errors");
    }

    #[test]
    fn test_check_multi_error_sorted() {
        let diags = check_source("let 1bad = 0\nlet 2bad = 0");
        // Multiple errors should be sorted by severity then line
        if diags.len() >= 2 {
            // Errors should come before warnings/hints (sorted by severity)
            let first_sev = diags[0].severity;
            let last_sev = diags[diags.len() - 1].severity;
            assert!(first_sev >= last_sev, "diagnostics should be sorted by severity");
        }
    }
}
