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

static RESTORE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^  Determining projects to restore\.\.\.\n?").unwrap());
static PACKAGE_DOWNLOAD_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^  Downloading [^\n]+\n?").unwrap());
static BUILD_PROGRESS_RE: Lazy<Regex> = Lazy::new(||
    // "Build succeeded." or progress lines like "  myproject -> /path/to/bin/Debug/myproject.dll"
    Regex::new(r"(?m)^  [^\n]+ -> [^\n]+\.dll\n?").unwrap());
static TEST_PASS_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s+Passed [^\n]+\n?").unwrap());

// ── dotnet build / publish ────────────────────────────────────────────────────

pub fn compress_build(raw: &str, exit_code: i32) -> String {
    let cleaned = compactor::normalise(raw);
    let s = RESTORE_RE.replace_all(&cleaned, "");
    let s = PACKAGE_DOWNLOAD_RE.replace_all(&s, "");
    if exit_code == 0 {
        // Strip DLL output paths, keep warning summary and "Build succeeded."
        let s = BUILD_PROGRESS_RE.replace_all(&s, "");
        return compactor::collapse_blanks(&s);
    }
    // On failure: keep error/warning lines
    let kept: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && (t.contains("error")
                    || t.contains("Error")
                    || t.contains("warning")
                    || t.contains("Build FAILED")
                    || t.contains("Build succeeded"))
        })
        .collect();
    if kept.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    kept.join("\n")
}

// ── dotnet test ───────────────────────────────────────────────────────────────

pub fn compress_test(raw: &str, exit_code: i32) -> String {
    let cleaned = compactor::normalise(raw);
    let s = RESTORE_RE.replace_all(&cleaned, "");
    if exit_code == 0 {
        let s = TEST_PASS_RE.replace_all(&s, "");
        // Keep summary: "Passed!", "Test Run Successful.", timing
        let kept: Vec<&str> = s
            .lines()
            .filter(|l| {
                let t = l.trim();
                t.is_empty()
                    || t.contains("Test Run Successful")
                    || t.contains("Passed!")
                    || t.contains("Total:")
                    || t.contains("Duration:")
                    || t.starts_with("Build succeeded")
            })
            .collect();
        if !kept.is_empty() {
            return kept.join("\n");
        }
    }
    // On failure: strip passing tests, keep failures + summary
    let s = TEST_PASS_RE.replace_all(&s, "");
    compactor::collapse_blanks(&s)
}

// ── dotnet restore / add ──────────────────────────────────────────────────────

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

// ── top-level dispatcher ──────────────────────────────────────────────────────

pub fn compress_dotnet(subcmd: &str, raw: &str, exit_code: i32) -> String {
    let sub = subcmd.trim();
    match sub {
        "build" | "publish" | "run" | "pack" => compress_build(raw, exit_code),
        "test" => compress_test(raw, exit_code),
        "restore" | "add" | "remove" => compress_restore(raw),
        _ => {
            let cleaned = compactor::normalise(raw);
            let s = RESTORE_RE.replace_all(&cleaned, "");
            compactor::collapse_blanks(&s)
        }
    }
}

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

    #[test]
    fn build_success_strips_dll_lines() {
        let raw = "  Determining projects to restore...\n  myproject -> /home/user/project/bin/Debug/net8.0/myproject.dll\nBuild succeeded.\n  0 Warning(s)\n  0 Error(s)\n";
        let out = compress_build(raw, 0);
        assert!(out.contains("Build succeeded"), "{out}");
        assert!(!out.contains("Determining projects"), "{out}");
    }

    #[test]
    fn build_failure_keeps_errors() {
        let raw = "Program.cs(10,5): error CS0103: The name 'Foo' does not exist\nBuild FAILED.\n";
        let out = compress_build(raw, 1);
        assert!(out.contains("error CS0103"), "{out}");
        assert!(out.contains("FAILED"), "{out}");
    }

    #[test]
    fn test_success_strips_passed_lines() {
        let raw = "  Passed TestFoo [< 1 ms]\n  Passed TestBar [2 ms]\nTest Run Successful.\n  Total: 2, Passed: 2, Duration: 0.05s\n";
        let out = compress_test(raw, 0);
        assert!(out.contains("Test Run Successful"), "{out}");
        assert!(!out.contains("Passed TestFoo"), "{out}");
    }
}
