const SPINNER_CHARS: &[char] = &['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];

pub fn apply(lines: Vec<String>) -> Vec<String> {
    lines
        .into_iter()
        .map(strip_ansi)
        .map(|s| s.replace('\r', ""))
        .filter(|l| !l.trim().is_empty())
        .filter(|l| !is_spinner(l))
        .filter(|l| !is_progress_bar(l))
        .filter(|l| !is_git_hint(l))
        .filter(|l| !is_npm_noise(l))
        .filter(|l| !is_vite_plugin_noise(l))
        .filter(|l| !is_node_modules_frame(l))
        .map(strip_log_timestamp)
        .collect()
}

fn strip_ansi(s: String) -> String {
    let mut out = String::with_capacity(s.len());
    let mut chars = s.chars().peekable();
    while let Some(c) = chars.next() {
        if c == '\x1b' && chars.peek() == Some(&'[') {
            chars.next();
            while let Some(&nc) = chars.peek() {
                chars.next();
                if nc.is_ascii_alphabetic() {
                    break;
                }
            }
        } else {
            out.push(c);
        }
    }
    out
}

fn is_spinner(s: &str) -> bool {
    let t = s.trim_start();
    t.starts_with(|c| SPINNER_CHARS.contains(&c))
}

fn is_progress_bar(s: &str) -> bool {
    let t = s.trim();
    (t.contains('█') || t.contains('░'))
        || (t.contains('[') && t.contains(']') && t.contains('=') && t.contains('>'))
        || (t.ends_with('%') && t.chars().any(|c| c.is_ascii_digit()))
}

fn is_git_hint(s: &str) -> bool {
    s.trim_start().starts_with("hint:")
}

fn is_npm_noise(s: &str) -> bool {
    let t = s.trim_start();
    t.starts_with("npm warn deprecated")
        || t.starts_with("WARN deprecated")
        || t.starts_with("npm notice")
        || t.starts_with("npm warn EBADENGINE")
}

fn is_vite_plugin_noise(s: &str) -> bool {
    let t = s.trim_start();
    // vite-tsconfig-paths deprecation warning block — repeats once per vitest run,
    // producing ~3KB of identical noise across 6 runs in the analyzed session.
    t.starts_with("[tsconfig-paths]")
        || t.starts_with("The plugin \"vite-tsconfig-paths\"")
        || t.starts_with("The plugin \"@vitejs/plugin-react\"")
        || t.starts_with("Vite now supports tsconfig paths resolution natively")
        || t.starts_with("You can remove the plugin and set resolve.tsconfigPaths")
        || t.starts_with("For new projects, use create-next-app to choose")
        || t.starts_with("`next lint` is deprecated")
}

fn is_node_modules_frame(s: &str) -> bool {
    s.contains("node_modules/") && (s.trim_start().starts_with("at ") || s.contains("(/."))
}

fn strip_log_timestamp(s: String) -> String {
    if s.starts_with('[') {
        if let Some(end) = s.find("] ") {
            let prefix = &s[1..end];
            if prefix.contains('-') && (prefix.contains('T') || prefix.contains(':')) {
                return s[end + 2..].to_string();
            }
        }
    }
    s
}
