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

// "* Getting foo (Hex package)" style dep fetch lines
static GETTING_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\* Getting [^\n]+\n?").unwrap());
// "==> Compiling X" verbose compile lines
static COMPILING_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^==> Compiling [^\n]+\n?").unwrap());
// "Generated X (from Y)" code-gen lines
static GENERATED_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^Generated [^\n]+\n?").unwrap());

// ── mix deps.get / deps.update ────────────────────────────────────────────────

pub fn compress_deps(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = GETTING_RE.replace_all(&cleaned, "");
    compactor::collapse_blanks(&s)
}

// ── mix compile ───────────────────────────────────────────────────────────────

pub fn compress_compile(raw: &str, exit_code: i32) -> String {
    let cleaned = compactor::normalise(raw);
    let s = COMPILING_RE.replace_all(&cleaned, "");
    let s = GENERATED_RE.replace_all(&s, "");
    if exit_code == 0 {
        return compactor::collapse_blanks(&s);
    }
    // Keep error/warning lines
    s.lines()
        .filter(|l| l.contains("error") || l.contains("warning") || l.trim().is_empty())
        .collect::<Vec<_>>()
        .join("\n")
}

// ── mix test ──────────────────────────────────────────────────────────────────

pub fn compress_test(raw: &str, exit_code: i32) -> String {
    let cleaned = compactor::normalise(raw);
    let s = COMPILING_RE.replace_all(&cleaned, "");
    if exit_code == 0 {
        // Keep "X tests, 0 failures" summary
        let kept: Vec<&str> = s
            .lines()
            .filter(|l| l.contains("tests,") || l.contains("failures") || l.contains("Finished"))
            .collect();
        if !kept.is_empty() {
            return kept.join("\n");
        }
    }
    // Failure: keep failure lines + summary
    s.lines()
        .filter(|l| !l.trim().starts_with("..") && !l.trim().starts_with('.'))
        .collect::<Vec<_>>()
        .join("\n")
}

// ── mix format (check) ────────────────────────────────────────────────────────

pub fn compress_format(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Only keep changed files
    cleaned
        .lines()
        .filter(|l| !l.trim().is_empty())
        .collect::<Vec<_>>()
        .join("\n")
}

pub fn compress_mix(subcmd: &str, raw: &str, exit_code: i32) -> String {
    let sub = subcmd.trim();
    match sub {
        "deps.get" | "deps.update" | "hex.outdated" => compress_deps(raw),
        "compile" | "ecto.migrate" => compress_compile(raw, exit_code),
        "test" => compress_test(raw, exit_code),
        "format" => compress_format(raw),
        _ => compactor::normalise(raw),
    }
}

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

    #[test]
    fn deps_strips_getting_lines() {
        let raw = "* Getting phoenix (Hex package)\n* Getting ecto (Hex package)\nAll dependencies up to date\n";
        let out = compress_deps(raw);
        assert!(!out.contains("Getting phoenix"), "{out}");
        assert!(out.contains("All dependencies"), "{out}");
    }

    #[test]
    fn compile_strips_compiling_lines() {
        let raw = "==> Compiling 12 files (.ex)\n==> Compiling phoenix_live_view\n";
        let out = compress_compile(raw, 0);
        assert!(!out.contains("==> Compiling"), "{out}");
    }

    #[test]
    fn test_success_keeps_summary() {
        let raw = "==> Compiling\n...\n50 tests, 0 failures\n";
        let out = compress_test(raw, 0);
        assert!(out.contains("50 tests"), "{out}");
    }
}
