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

// "cache miss, executing abc123def" / "cache hit, replaying output abc123def"
static CACHE_HASH_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"\b(executing|output)\s+[0-9a-f]{8,}\b").unwrap());
// Per-task empty relay lines: "app:build: " with nothing after
static EMPTY_RELAY_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^[a-zA-Z0-9_\-./]+:[a-zA-Z0-9_\-]+:\s*$").unwrap());
// Spinner chars on their own line
static SPINNER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s*[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s*[^\n]*\n?").unwrap());
// "Creating an optimized production build..." (Next.js inside turbo)
static NEXT_NOISE_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^[a-zA-Z0-9_\-./]+:[a-zA-Z0-9_\-]+: Creating an optimized[^\n]*\n?").unwrap()
});

// Lines that are pure noise across all turbo runs
const NOISE_PREFIXES: &[&str] = &[
    "• Remote caching",
    "• Daemon",
    "• Running target",
    ">>> Finished", // replaced by the Tasks: summary block
];

const NOISE_CONTAINS: &[&str] = &["No cached artifacts found", "No cache entry", "Full Turbo"];

pub fn compress_turbo(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = SPINNER_RE.replace_all(&cleaned, "");
    let s = NEXT_NOISE_RE.replace_all(&s, "");
    let s = EMPTY_RELAY_RE.replace_all(&s, "");

    let mut out: Vec<String> = Vec::new();
    let mut in_summary = false;

    for line in s.lines() {
        let t = line.trim();

        // Always keep the Tasks / Cached / Time summary block at the end
        if t.starts_with("Tasks:") || t.starts_with("Cached:") || t.starts_with("Time:") {
            in_summary = true;
        }
        if in_summary {
            out.push(line.to_string());
            continue;
        }

        // Strip pure-noise lines
        if NOISE_PREFIXES.iter().any(|p| t.starts_with(p)) {
            continue;
        }
        if NOISE_CONTAINS.iter().any(|p| t.contains(p)) {
            continue;
        }

        // Compact cache status lines: strip hash suffix
        let line_out = CACHE_HASH_RE.replace_all(line, "$1 …").to_string();
        out.push(line_out);
    }

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

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

    #[test]
    fn strips_remote_caching_noise() {
        let raw = "• Packages in scope: app, docs\n• Running build in 2 packages\n• Remote caching disabled\n\napp:build: cache miss, executing abcdef123456\napp:build: ✓ Compiled successfully\n\nTasks:    2 successful, 2 total\n";
        let out = compress_turbo(raw);
        assert!(!out.contains("Remote caching"), "{out}");
        assert!(out.contains("Packages in scope"), "{out}");
        assert!(out.contains("Tasks:"), "{out}");
    }

    #[test]
    fn strips_cache_hash_suffix() {
        let raw = "app:build: cache miss, executing abc123def456\ndocs:build: cache hit, replaying output 789abcdef0\n";
        let out = compress_turbo(raw);
        assert!(!out.contains("abc123def456"), "{out}");
        assert!(!out.contains("789abcdef0"), "{out}");
        assert!(out.contains("cache miss"), "{out}");
        assert!(out.contains("cache hit"), "{out}");
    }

    #[test]
    fn strips_empty_relay_lines() {
        let raw = "app:build: \napp:build: \napp:build: > tsc\napp:build: \n";
        let out = compress_turbo(raw);
        let empty_relays = out
            .lines()
            .filter(|l| l.trim_end().ends_with(':') || l.trim().is_empty())
            .count();
        // At most one blank line between sections
        assert!(empty_relays <= 1, "too many empty relay lines: {out}");
    }

    #[test]
    fn keeps_error_output() {
        let raw = "app:build: cache miss, executing abc123\napp:build: error TS2304: Cannot find name 'foo'.\napp:build: src/index.ts(10,5): error TS2304\n\nTasks:    0 successful, 1 failed\n";
        let out = compress_turbo(raw);
        assert!(out.contains("error TS2304"), "{out}");
        assert!(out.contains("Tasks:"), "{out}");
    }

    #[test]
    fn keeps_summary_block() {
        let raw = "app:build: ✓ done\n\nTasks:    3 successful, 3 total\nCached:    1 cached, 3 total\n  Time:   5.432s >>> FULL TURBO\n";
        let out = compress_turbo(raw);
        assert!(out.contains("Tasks:    3 successful"), "{out}");
        assert!(out.contains("Cached:"), "{out}");
        assert!(out.contains("Time:"), "{out}");
    }
}
