use std::collections::HashSet;
use std::path::Path;

use crate::error::Result;

// embedded at compile time - cannot be removed without recompiling
const BUILTIN_LIST: &str = include_str!("../../../data/banned_words.txt");

pub struct Blocklist {
    words: HashSet<String>,
}

fn normalize_leet(s: &str) -> String {
    s.chars().map(|c| match c {
        '0' | 'O' => 'O',
        '1' | 'l' | '!' | '|' => 'I',
        '3' => 'E',
        '4' | '@' => 'A',
        '5' | '$' => 'S',
        '7' => 'T',
        '8' => 'B',
        '9' => 'G',
        _ => c.to_ascii_uppercase(),
    }).collect()
}

fn parse_lines(content: &str) -> impl Iterator<Item = String> + '_ {
    content.lines()
        .map(|l| l.trim().to_uppercase())
        .filter(|l| !l.is_empty() && !l.starts_with('#'))
}

impl Blocklist {
    pub fn empty() -> Self {
        // "empty" still includes the built-in list
        let words = parse_lines(BUILTIN_LIST).collect();
        Self { words }
    }

    pub fn load(path: &Path) -> Result<Self> {
        // start with built-in, then add from file (additive only)
        let mut words: HashSet<String> = parse_lines(BUILTIN_LIST).collect();
        if let Ok(content) = std::fs::read_to_string(path) {
            for w in parse_lines(&content) {
                words.insert(w);
            }
        }
        Ok(Self { words })
    }

    pub fn is_blocked(&self, word: &str) -> bool {
        self.words.contains(word)
    }

    pub fn add_word(&mut self, word: &str) {
        let w = word.trim().to_uppercase();
        if !w.is_empty() {
            self.words.insert(w);
        }
    }

    pub fn extra_count(&self) -> usize {
        let builtin: HashSet<String> = parse_lines(BUILTIN_LIST).collect();
        self.words.len().saturating_sub(builtin.len())
    }

    pub fn contains_blocked(&self, text: &str) -> bool {
        let upper = text.to_uppercase();
        let normalized = normalize_leet(text);
        let stripped: String = upper.chars().filter(|c| c.is_alphabetic()).collect();
        let stripped_leet: String = normalized.chars().filter(|c| c.is_alphabetic()).collect();

        for word in &self.words {
            if word.len() < 3 { continue; }
            if upper.contains(word.as_str()) { return true; }
            if normalized.contains(word.as_str()) { return true; }
            if stripped.contains(word.as_str()) { return true; }
            if stripped_leet.contains(word.as_str()) { return true; }
        }
        false
    }
}
