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

// " ✔  nx run app:build  [existing outputs match the cache, left as is]"
static CACHE_HIT_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s+✔\s+nx run\s+(\S+)\s+\[existing outputs[^\]]*\]").unwrap());
// " ✔  nx run app:build (2s)"  — successful run with timing
static SUCCESS_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s+✔\s+nx run\s+(\S+)\s+\(([^)]+)\)").unwrap());
// " ✖  nx run app:build" — failure header
static FAIL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s+✖\s+nx run\s+(\S+)").unwrap());
// Horizontal rule separators "  ————————..."
static HR_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s+[─—]{10,}\s*$").unwrap());
// "Nx read the output from the cache instead of running..." verbose explanation lines
static CACHE_EXPLAIN_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s+Nx (read|restored|will use|skipped)[^\n]*\n?").unwrap());

pub fn compress_nx(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Collapse cache-hit lines: "✔ app:build (cached)"
    let s = CACHE_HIT_RE.replace_all(&cleaned, "  ✔  $1 (cached)");
    // Collapse success lines: "✔ app:build (2s)"
    let s = SUCCESS_RE.replace_all(&s, "  ✔  $1 ($2)");
    // Collapse fail lines: "✖ app:build"
    let s = FAIL_RE.replace_all(&s, "  ✖  $1");
    // Strip horizontal rules and verbose cache explanations
    let s = HR_RE.replace_all(&s, "");
    let s = CACHE_EXPLAIN_RE.replace_all(&s, "");

    let out: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            // Strip the "NX  Running target X for N projects" banner (keep summary lines below)
            !(t.starts_with(">  NX") && t.contains("Running target"))
        })
        .collect();

    compactor::collapse_blanks(&out.join("\n"))
}

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

    #[test]
    fn collapses_cache_hit_lines() {
        let raw = "   ✔  nx run app:build  [existing outputs match the cache, left as is]\n   ✔  nx run docs:build (2s)\n";
        let out = compress_nx(raw);
        assert!(out.contains("app:build (cached)"), "{out}");
        assert!(out.contains("docs:build (2s)"), "{out}");
        assert!(!out.contains("existing outputs match"), "{out}");
    }

    #[test]
    fn strips_horizontal_rules() {
        let raw = "   ✔  nx run app:build (1s)\n\n ────────────────────────────────────────\n\n >  NX   Successfully ran target build\n";
        let out = compress_nx(raw);
        assert!(!out.contains("────"), "{out}");
        assert!(out.contains("Successfully ran target"), "{out}");
    }

    #[test]
    fn strips_verbose_cache_explanation() {
        let raw = "   ✔  nx run app:build (1s)\n   Nx read the output from the cache instead of running the command for 1 out of 3 tasks.\n\n >  NX   Successfully ran\n";
        let out = compress_nx(raw);
        assert!(!out.contains("Nx read the output from the cache"), "{out}");
        assert!(out.contains("app:build"), "{out}");
    }

    #[test]
    fn keeps_failure_output() {
        let raw = "   ✖  nx run app:build\n\n > app@0.0.1 build\n > tsc --noEmit\n\n error TS2304: Cannot find name 'foo'.\n\n ✖    1/3 targets failed\n";
        let out = compress_nx(raw);
        assert!(out.contains("✖") && out.contains("app:build"), "{out}");
        assert!(out.contains("error TS2304"), "{out}");
        assert!(out.contains("1/3"), "{out}");
    }

    #[test]
    fn strips_running_target_banner() {
        let raw = "\n >  NX  Running target build for 3 projects:\n\n   - app\n   - docs\n";
        let out = compress_nx(raw);
        assert!(!out.contains("Running target build for"), "{out}");
    }
}
