pub mod curl;
pub mod env;
pub mod jq;
pub mod mise;
pub mod sysinfo;
pub mod systemd;
pub mod wget;

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

static FIND_PERM_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^find: [^\n]*: Permission denied\n?").unwrap());

// ── ls ────────────────────────────────────────────────────────────────────────

pub fn compress_ls(raw: &str, max_lines: usize) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().collect();
    if lines.len() <= max_lines {
        return cleaned;
    }
    format!(
        "{}\n... [{} more entries]",
        lines[..max_lines].join("\n"),
        lines.len() - max_lines
    )
}

// ── find ──────────────────────────────────────────────────────────────────────

pub fn compress_find(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = FIND_PERM_RE.replace_all(&cleaned, "");
    let lines: Vec<&str> = s.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() <= 100 {
        return lines.join("\n");
    }
    format!(
        "{}\n... [{} more paths]",
        lines[..100].join("\n"),
        lines.len() - 100
    )
}

// ── grep / rg ─────────────────────────────────────────────────────────────────

pub fn compress_grep(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() <= 80 {
        return lines.join("\n");
    }
    format!(
        "{}\n... [{} more matches]",
        lines[..80].join("\n"),
        lines.len() - 80
    )
}

// ── log dedup (universal) ─────────────────────────────────────────────────────

pub fn log_dedup(raw: &str) -> String {
    let lines: Vec<&str> = raw.lines().collect();
    let mut out: Vec<String> = Vec::new();
    let mut prev: &str = "";
    let mut repeat_count: usize = 0;

    for line in &lines {
        if *line == prev && !line.trim().is_empty() {
            repeat_count += 1;
        } else {
            if repeat_count > 0 {
                out.push(format!("  [repeated ×{}]", repeat_count + 1));
                repeat_count = 0;
            }
            out.push(line.to_string());
            prev = line;
        }
    }
    if repeat_count > 0 {
        out.push(format!("  [repeated ×{}]", repeat_count + 1));
    }

    if out.len() > 200 {
        let skipped = out.len() - 200;
        let tail = out[out.len() - 200..].to_vec();
        return format!(
            "... [{} earlier lines omitted] ...\n{}",
            skipped,
            tail.join("\n")
        );
    }
    out.join("\n")
}

// ── env ───────────────────────────────────────────────────────────────────────

pub fn compress_env(raw: &str) -> String {
    env::compress_env(raw)
}

// ── jq ────────────────────────────────────────────────────────────────────────

pub fn compress_jq_output(raw: &str) -> String {
    jq::compress_jq(raw)
}

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

    #[test]
    fn ls_truncates_large_output() {
        let raw = (0..300)
            .map(|i| format!("file{i}.txt"))
            .collect::<Vec<_>>()
            .join("\n");
        let out = compress_ls(&raw, 200);
        assert!(out.contains("more entries"), "{out}");
    }

    #[test]
    fn find_strips_permission_denied() {
        let raw = "find: /root: Permission denied\n./src/main.rs\n./src/lib.rs\n";
        let out = compress_find(raw);
        assert!(!out.contains("Permission denied"), "{out}");
        assert!(out.contains("main.rs"), "{out}");
    }

    #[test]
    fn log_dedup_collapses_repeated_lines() {
        let raw = "line A\nline B\nline B\nline B\nline C\n";
        let out = log_dedup(raw);
        assert!(out.contains("repeated"), "{out}");
        assert!(!out.contains("line B\nline B"), "{out}");
    }

    #[test]
    fn log_dedup_tails_very_long_logs() {
        let raw = (0..300)
            .map(|i| format!("log line {i}"))
            .collect::<Vec<_>>()
            .join("\n");
        let out = log_dedup(&raw);
        assert!(out.contains("earlier lines omitted"), "{out}");
    }
}
