//! Minimal CLI for dirigent_anth — validate parsing and search sessions.
//!
//! Usage:
//!   cargo run --package dirigent_anth --bin ant             # validate all sessions
//!   cargo run --package dirigent_anth --bin ant -- search "query"  # search user messages
//!   cargo run --package dirigent_anth --bin ant -- stats    # show statistics

use dirigent_anth::*;
use std::io::BufRead;

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();

    let home = match discover_claude_home() {
        Ok(h) => h,
        Err(e) => {
            eprintln!("Could not find Claude home: {e}");
            std::process::exit(1);
        }
    };

    let projects = match discover_projects(&home) {
        Ok(p) => p,
        Err(e) => {
            eprintln!("Could not discover projects: {e}");
            std::process::exit(1);
        }
    };

    match args.first().map(|s| s.as_str()) {
        Some("search") => {
            let query = args.get(1).map(|s| s.as_str()).unwrap_or("");
            if query.is_empty() {
                eprintln!("Usage: ant search <query>");
                std::process::exit(1);
            }
            cmd_search(&projects, query);
        }
        Some("stats") => cmd_stats(&projects),
        Some("validate") | None => cmd_validate(&projects),
        Some(other) => {
            eprintln!("Unknown command: {other}");
            eprintln!("Commands: validate (default), search <query>, stats");
            std::process::exit(1);
        }
    }
}

/// Validate that the parser can handle all sessions without errors.
fn cmd_validate(projects: &[ClaudeProject]) {
    let mut total_sessions = 0;
    let mut total_ok = 0;
    let mut total_messages = 0;
    let mut total_skipped_lines = 0;
    let mut errors: Vec<(String, String)> = Vec::new();

    for project in projects {
        println!(
            "Project: {} ({} sessions)",
            project.original_path,
            project.sessions.len()
        );

        for session in &project.sessions {
            total_sessions += 1;

            // Raw line-level validation: count how many lines parse vs skip
            let (_raw_ok, raw_skip) = validate_lines(&session.jsonl_path);
            total_skipped_lines += raw_skip;

            // Full pipeline validation
            match load_session(session) {
                Ok(parsed) => {
                    total_ok += 1;
                    total_messages += parsed.messages.len();
                    let tools = parsed.tool_exchanges.len();
                    let subs = parsed.subagents.len();
                    let branches = if parsed.tree.is_linear() {
                        "linear"
                    } else {
                        "branched"
                    };

                    if raw_skip > 0 {
                        println!(
                            "  {} — {} msgs, {} tools, {} subagents, {} | {raw_skip} lines skipped",
                            &session.id[..8.min(session.id.len())],
                            parsed.messages.len(),
                            tools,
                            subs,
                            branches,
                        );
                    }
                }
                Err(e) => {
                    errors.push((session.id.clone(), e.to_string()));
                    eprintln!("  {} — ERROR: {e}", &session.id[..8.min(session.id.len())]);
                }
            }
        }
    }

    println!("\n--- Validation Summary ---");
    println!("Projects:  {}", projects.len());
    println!("Sessions:  {total_sessions} ({total_ok} ok, {} errors)", errors.len());
    println!("Messages:  {total_messages}");
    if total_skipped_lines > 0 {
        println!("Skipped:   {total_skipped_lines} unparseable lines");
    }

    if !errors.is_empty() {
        println!("\nErrors:");
        for (id, err) in &errors {
            println!("  {id}: {err}");
        }
        std::process::exit(1);
    }
}

/// Count parseable vs skipped lines in a JSONL file.
fn validate_lines(path: &camino::Utf8Path) -> (usize, usize) {
    let file = match std::fs::File::open(path.as_std_path()) {
        Ok(f) => f,
        Err(_) => return (0, 0),
    };
    let reader = std::io::BufReader::new(file);
    let mut ok = 0;
    let mut skip = 0;

    for (i, line) in reader.lines().enumerate() {
        let line = match line {
            Ok(l) => l,
            Err(_) => {
                skip += 1;
                continue;
            }
        };
        if line.trim().is_empty() {
            continue;
        }
        if parse_line(&line, i + 1).is_some() {
            ok += 1;
        } else {
            skip += 1;
        }
    }

    (ok, skip)
}

/// Search user messages for a query string (case-insensitive).
fn cmd_search(projects: &[ClaudeProject], query: &str) {
    let query_lower = query.to_lowercase();
    let mut hits = 0;

    for project in projects {
        for session in &project.sessions {
            let messages = match parse_session_deduped(&session.jsonl_path) {
                Ok(m) => m,
                Err(_) => continue,
            };

            for msg in &messages {
                let text = match msg {
                    types::RawMessage::User(u) => match &u.message.content {
                        types::Content::Text(s) => s.clone(),
                        types::Content::Blocks(_) => continue,
                    },
                    types::RawMessage::Assistant(a) => {
                        let mut parts = Vec::new();
                        for block in &a.message.content {
                            if let types::ContentBlock::Text { text } = block {
                                parts.push(text.as_str());
                            }
                        }
                        parts.join(" ")
                    }
                    _ => continue,
                };

                if text.to_lowercase().contains(&query_lower) {
                    let role = match msg {
                        types::RawMessage::User(_) => "user",
                        types::RawMessage::Assistant(_) => "assistant",
                        _ => "other",
                    };
                    let preview = truncate(&text, 120);
                    println!(
                        "[{}] {} {} | {}",
                        &project.original_path,
                        &session.id[..8.min(session.id.len())],
                        role,
                        preview
                    );
                    hits += 1;
                }
            }
        }
    }

    println!("\n{hits} matches for \"{query}\"");
}

/// Show aggregate statistics across all sessions.
fn cmd_stats(projects: &[ClaudeProject]) {
    let mut total_sessions = 0;
    let mut total_messages = 0;
    let mut total_tools = 0;
    let mut total_subagents = 0;
    let mut tool_counts: std::collections::HashMap<String, usize> = std::collections::HashMap::new();

    for project in projects {
        for session in &project.sessions {
            total_sessions += 1;
            if let Ok(parsed) = load_session(session) {
                total_messages += parsed.messages.len();
                total_tools += parsed.tool_exchanges.len();
                total_subagents += parsed.subagents.len();

                for ex in &parsed.tool_exchanges {
                    let name = format!("{:?}", ex.call.name);
                    *tool_counts.entry(name).or_default() += 1;
                }
            }
        }
    }

    println!("--- Statistics ---");
    println!("Projects:    {}", projects.len());
    println!("Sessions:    {total_sessions}");
    println!("Messages:    {total_messages}");
    println!("Tool calls:  {total_tools}");
    println!("Sub-agents:  {total_subagents}");

    if !tool_counts.is_empty() {
        println!("\nTool usage:");
        let mut sorted: Vec<_> = tool_counts.into_iter().collect();
        sorted.sort_by(|a, b| b.1.cmp(&a.1));
        for (name, count) in sorted.iter().take(15) {
            println!("  {name:20} {count}");
        }
    }
}

fn truncate(s: &str, max: usize) -> String {
    let s = s.replace('\n', " ").replace('\r', "");
    if s.len() <= max {
        s
    } else {
        format!("{}...", &s[..max])
    }
}
