use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;

// "file.ts:10:5 lint/suspicious/noExplicitAny ━━━━"
static FINDING_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^([^\s:]+\.[a-zA-Z]+:\d+:\d+)\s+(lint/[^\s]+|format/[^\s]+)").unwrap()
});
// Rule explanation lines after the ─ separator
static RULE_EXPLAIN_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^  ─+[^\n]*\n?").unwrap());
// "Checked N files in Xms"
static CHECKED_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Checked (\d+) files in [^\n]+\n?").unwrap());
// "Found N diagnostics"
static FOUND_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^Found [^\n]+\n?").unwrap());

pub fn compress_biome(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("format") && raw.contains("Formatted") {
        return compress_format(raw);
    }
    compress_lint(raw)
}

fn compress_lint(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);

    // No issues
    if cleaned.contains("Found 0 diagnostics") || cleaned.trim().is_empty() {
        let checked = CHECKED_RE
            .captures(&cleaned)
            .and_then(|c| c.get(1))
            .map(|m| m.as_str())
            .unwrap_or("?");
        return format!("biome: no issues ({checked} files checked)");
    }

    // Strip rule explanations (the ─── divider lines and code context)
    let s = RULE_EXPLAIN_RE.replace_all(&cleaned, "");

    // Group findings by file
    let mut by_file: std::collections::HashMap<String, Vec<String>> =
        std::collections::HashMap::new();
    let mut summary_lines: Vec<String> = Vec::new();
    let mut current_file = String::new();

    for line in s.lines() {
        let t = line.trim();
        if t.is_empty() {
            continue;
        }

        // Capture "Checked N files" and "Found N diagnostics" summary lines
        if CHECKED_RE.is_match(t) || FOUND_RE.is_match(t) {
            summary_lines.push(t.to_string());
            continue;
        }

        if let Some(caps) = FINDING_RE.captures(t) {
            let loc = &caps[1];
            let rule = &caps[2];
            // Extract file from loc "file.ts:10:5"
            let file = loc.split(':').next().unwrap_or(loc).to_string();
            current_file = file.clone();
            by_file
                .entry(file)
                .or_default()
                .push(format!("{loc} {rule}"));
            continue;
        }

        // Message lines following a finding
        if !current_file.is_empty()
            && (t.starts_with('×')
                || t.starts_with('!')
                || t.starts_with("error")
                || t.starts_with("warn"))
        {
            if let Some(v) = by_file.get_mut(&current_file) {
                if let Some(last) = v.last_mut() {
                    last.push_str(&format!(" — {t}"));
                }
            }
        }
    }

    let mut result: Vec<String> = Vec::new();
    let mut files: Vec<&String> = by_file.keys().collect();
    files.sort();
    for file in files {
        let findings = &by_file[file];
        result.push(format!("{file} — {} finding(s)", findings.len()));
        for f in findings.iter().take(5) {
            result.push(format!("  {f}"));
        }
        if findings.len() > 5 {
            result.push(format!("  … {} more", findings.len() - 5));
        }
    }
    result.extend(summary_lines);
    if result.is_empty() {
        return compactor::collapse_blanks(&cleaned);
    }
    result.join("\n")
}

fn compress_format(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // "Formatted N files" summary — suppress individual file names
    let formatted: Vec<&str> = cleaned
        .lines()
        .filter(|l| l.trim().starts_with("Formatted"))
        .collect();
    let errors: Vec<&str> = cleaned
        .lines()
        .filter(|l| l.to_lowercase().contains("error"))
        .collect();
    let mut result: Vec<&str> = formatted;
    result.extend(errors);
    if result.is_empty() {
        return compactor::collapse_blanks(&cleaned);
    }
    result.join("\n")
}

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

    #[test]
    fn no_issues_returns_clean() {
        let raw = "Checked 42 files in 1234ms\nFound 0 diagnostics\n";
        let out = compress_biome("check", raw);
        assert!(out.contains("no issues"), "{out}");
        assert!(out.contains("42"), "{out}");
    }

    #[test]
    fn groups_findings_by_file() {
        let raw = "src/foo.ts:10:5 lint/suspicious/noExplicitAny ━━━━\n× Unexpected any\nsrc/foo.ts:20:1 lint/complexity/noUselessFragments ━━━━\n× Remove fragment\nsrc/bar.ts:5:3 lint/style/noVar ━━━━\n× Use let/const\nChecked 2 files in 200ms\nFound 3 diagnostics\n";
        let out = compress_biome("check", &raw);
        assert!(out.contains("foo.ts"), "{out}");
        assert!(out.contains("bar.ts"), "{out}");
        // Should group by file
        assert!(
            out.contains("finding") || out.contains("diagnostic"),
            "{out}"
        );
    }

    #[test]
    fn format_write_keeps_summary() {
        let raw = "Formatted 12 files\n";
        let out = compress_biome("format", raw);
        assert!(out.contains("Formatted 12"), "{out}");
    }

    #[test]
    fn strips_rule_explanation_dividers() {
        let raw = "src/a.ts:1:1 lint/suspicious/noExplicitAny ━━━━━━━━━━\n  ─────────────────────────────────────────\n  × avoid using any\n  ─────────────────────────────────────────\nFound 1 diagnostic\n";
        let out = compress_biome("lint", &raw);
        assert!(!out.contains("━━━━━━━━━━") || out.contains("a.ts"), "{out}");
    }
}
