use anyhow::Result;
use std::collections::HashSet;
use std::path::Path;
use parking_lot::Mutex;

use crate::processors::flush_words;
use crate::graph::Product;

/// Shared word-file management for spell-checking processors (aspell, zspell).
///
/// Handles loading custom words, collecting misspelled words in auto-add mode,
/// and flushing new words to disk. Also provides the shared execute/batch pattern
/// where files are checked and words are flushed afterward.
pub(crate) struct WordManager {
    custom_words: HashSet<String>,
    words_to_add: Mutex<HashSet<String>>,
    words_file: String,
    header_line: Option<&'static str>,
}

impl WordManager {
    pub fn new(
        custom_words: HashSet<String>,
        words_file: String,
        header_line: Option<&'static str>,
    ) -> Self {
        Self {
            custom_words,
            words_to_add: Mutex::new(HashSet::new()),
            words_file,
            header_line,
        }
    }

    /// Check if a word is in the custom words set.
    pub fn is_known(&self, word: &str) -> bool {
        self.custom_words.contains(word)
    }

    /// Handle misspelled words: collect them if auto_add_words is true, or return an error.
    pub fn handle_misspelled(
        &self,
        misspelled: &[impl AsRef<str>],
        file: &Path,
        auto_add_words: bool,
    ) -> Result<()> {
        if misspelled.is_empty() {
            return Ok(());
        }
        if auto_add_words {
            let mut words_to_add = self.words_to_add.lock();
            for word in misspelled {
                words_to_add.insert(word.as_ref().to_lowercase());
            }
            Ok(())
        } else {
            let words: Vec<&str> = misspelled.iter().map(|w| w.as_ref()).collect();
            anyhow::bail!(
                "Misspelled words in {}:\n{}",
                file.display(),
                words.join("\n"),
            )
        }
    }

    /// Flush collected words to the words file.
    pub fn flush(&self) -> Result<()> {
        let words_to_add = self.words_to_add.lock();
        let words_path = Path::new(&self.words_file);
        flush_words(
            &self.custom_words,
            &words_to_add,
            words_path,
            self.header_line,
        )
    }

    /// Execute a single product with auto-flush: check the file, then flush if auto_add_words.
    pub fn execute_with_flush(
        &self,
        product: &Product,
        auto_add_words: bool,
        check_fn: impl FnOnce(&Path) -> Result<()>,
        processor_name: &str,
    ) -> Result<()> {
        let result = check_fn(product.primary_input());
        if auto_add_words
            && let Err(e) = self.flush()
        {
            eprintln!("Warning: failed to flush {} words file: {}", processor_name, e);
        }
        result
    }

    /// Execute a batch of products with auto-flush: check all files, then flush once.
    pub fn execute_batch_with_flush(
        &self,
        products: &[&Product],
        auto_add_words: bool,
        check_fn: impl Fn(&Path) -> Result<()>,
        processor_name: &str,
    ) -> Vec<Result<()>> {
        let results: Vec<Result<()>> = products
            .iter()
            .map(|p| check_fn(p.primary_input()))
            .collect();

        if auto_add_words
            && let Err(e) = self.flush()
        {
            eprintln!("Warning: failed to flush {} words file: {}", processor_name, e);
        }

        results
    }
}
