pub mod biome;
pub mod cargo_deny;
pub mod cosign;
pub mod eslint;
pub mod flake8;
pub mod golangci;
pub mod grype;
pub mod hadolint;
pub mod mypy;
pub mod prettier;
pub mod pylint;
pub mod rubocop;
pub mod ruff;
pub mod semgrep;
pub mod stylelint;
pub mod swiftlint;
pub mod syft;
pub mod trivy;

use forge::signal::compactor;
use std::collections::HashMap;

/// Route linter output to the right compressor by program name.
pub fn compress_linter(prog: &str, subcmd: &str, raw: &str, exit_code: i32) -> String {
    match prog {
        "ruff" => ruff::compress_ruff_check(raw),
        "mypy" | "dmypy" => mypy::compress_mypy(raw),
        "flake8" => flake8::compress_flake8(raw),
        "pylint" => pylint::compress_pylint(raw),
        "eslint" => eslint::compress_eslint(raw),
        "stylelint" => stylelint::compress_stylelint(raw),
        "cargo-deny" => cargo_deny::compress_deny(raw),
        "prettier" => prettier::compress_prettier(raw, exit_code),
        "rubocop" => rubocop::compress_rubocop(raw, exit_code),
        "bundle-audit" => rubocop::compress_bundle_audit(raw),
        "golangci-lint" | "golangci" => golangci::compress_golangci(raw, exit_code),
        "biome" => biome::compress_biome(subcmd, raw),
        "hadolint" => hadolint::compress_hadolint(raw),
        "semgrep" => semgrep::compress_semgrep(subcmd, raw),
        "swiftlint" => swiftlint::compress_swiftlint(raw),
        "trivy" => trivy::compress_trivy(subcmd, raw),
        "grype" => grype::compress_grype(raw),
        "syft" => syft::compress_syft(raw),
        "cosign" => cosign::compress_cosign(subcmd, raw),
        _ => {
            let _ = subcmd;
            compress_lint_grouped(raw)
        }
    }
}

/// Compress linter output by grouping identical rule violations.
/// Used for cargo clippy and generic linters.
pub fn compress_lint_grouped(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let mut counts: HashMap<String, usize> = HashMap::new();
    let mut errors_and_misc = Vec::new();

    for line in cleaned.lines() {
        if line.contains("warning:") || line.contains("note:") {
            let key = line.split("-->").next().unwrap_or(line).trim().to_string();
            *counts.entry(key).or_insert(0) += 1;
        } else {
            errors_and_misc.push(line.to_string());
        }
    }

    let mut result = errors_and_misc;
    for (rule, count) in &counts {
        if *count > 1 {
            result.push(format!("{rule} (×{count})"));
        } else {
            result.push(rule.clone());
        }
    }
    result.join("\n")
}

/// Legacy 3-arg signature kept for existing callers.
#[allow(dead_code)]
pub fn compress_linter_compat(prog: &str, subcmd: &str, raw: &str) -> String {
    compress_linter(prog, subcmd, raw, 0)
}
