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

// "pulling manifest" / "pulling layer sha256:..." / "pulling ..." progress lines
static PULL_PROGRESS_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^pulling [^\n]*\n?").unwrap());
// "[=====>   ] 34% ▕ 1.2 GB/3.5 GB" progress bars
static PROGRESS_BAR_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s*[\[|▕][=>\s]+[\]|▏][^\n]*\n?").unwrap());
// "verifying sha256 digest" / "writing manifest" / "removing any unused layers"
static DIGEST_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(
        r"(?m)^(?:verifying sha256 digest|writing manifest|removing any unused layers)[^\n]*\n?",
    )
    .unwrap()
});

// ── ollama pull ───────────────────────────────────────────────────────────────

pub fn compress_pull(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = PULL_PROGRESS_RE.replace_all(&cleaned, "");
    let s = PROGRESS_BAR_RE.replace_all(&s, "");
    let s = DIGEST_RE.replace_all(&s, "");
    let result = compactor::collapse_blanks(&s);
    if result.trim().is_empty() {
        return "pulled successfully".to_string();
    }
    result
}

// ── ollama list ───────────────────────────────────────────────────────────────

pub fn compress_list(raw: &str) -> String {
    // Keep the header + model rows; strip trailing whitespace padding
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() > 20 {
        return format!(
            "{}\n… [{} more models]",
            lines[..20].join("\n"),
            lines.len() - 20
        );
    }
    lines.join("\n")
}

// ── ollama show ───────────────────────────────────────────────────────────────

pub fn compress_show(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Keep: Model, Parameters, Family, Format, Quantization, Context, License header
    let useful: Vec<&str> = cleaned
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && !t.starts_with("template")
                && !t.starts_with("system")
                && !t.starts_with("---")
        })
        .collect();
    if useful.len() > 30 {
        return format!("{}\n… [truncated]", useful[..30].join("\n"));
    }
    useful.join("\n")
}

// ── ollama serve (startup noise) ──────────────────────────────────────────────

pub fn compress_serve(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let useful: Vec<&str> = cleaned
        .lines()
        .filter(|l| {
            let t = l.trim();
            t.contains("Listening on")
                || t.contains("OLLAMA_")
                || t.contains("error")
                || t.contains("Error")
                || t.contains("serving")
        })
        .collect();
    if useful.is_empty() {
        return compactor::collapse_blanks(&cleaned);
    }
    useful.join("\n")
}

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

pub fn compress_ollama(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("pull") {
        return compress_pull(raw);
    }
    if sub.starts_with("list") || sub == "ls" {
        return compress_list(raw);
    }
    if sub.starts_with("show") {
        return compress_show(raw);
    }
    if sub.starts_with("serve") || sub.starts_with("start") {
        return compress_serve(raw);
    }
    // run / cp / rm / push — passthrough (conversation output is signal)
    compactor::collapse_blanks(&compactor::normalise(raw))
}

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

    #[test]
    fn pull_strips_progress_bars() {
        let raw = "pulling manifest\npulling layer sha256:abc123\n[=====>   ] 34% ▕ 1.2 GB/3.5 GB\nverifying sha256 digest\nwriting manifest\nremoving any unused layers\nsuccess\n";
        let out = compress_pull(raw);
        assert!(!out.contains("pulling manifest"), "{out}");
        assert!(!out.contains("verifying sha256"), "{out}");
        assert!(!out.contains("[=====>"), "{out}");
        assert!(
            out.contains("success") || out == "pulled successfully",
            "{out}"
        );
    }

    #[test]
    fn list_truncates_at_20() {
        let rows: Vec<String> = (0..25)
            .map(|i| format!("model-{i}:latest  abc123  4.1 GB  2 hours ago"))
            .collect();
        let out = compress_list(&rows.join("\n"));
        assert!(out.contains("more models"), "{out}");
    }

    #[test]
    fn serve_keeps_listen_line() {
        let raw = "time=2024-01-01T00:00:00Z level=INFO source=server.go msg=\"Listening on 127.0.0.1:11434\"\ntime=2024-01-01T00:00:00Z level=INFO source=gpu.go msg=\"no GPU detected\"\n";
        let out = compress_serve(raw);
        assert!(out.contains("Listening on"), "{out}");
    }

    #[test]
    fn dispatcher_routes_pull() {
        let raw = "pulling manifest\nsuccess\n";
        let out = compress_ollama("pull llama3", raw);
        assert!(!out.contains("pulling manifest"), "{out}");
    }
}
