use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;

use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use tokio::sync::{mpsc, RwLock};
use tracing::{info, warn};

use crate::error::DaemonError;

/// Watches a templates directory for HTML changes and calls
/// `Tera::full_reload()` after a debounce window. Runs independently of the NCL
/// file watcher.
pub struct TemplateWatcher {
    _watcher: RecommendedWatcher,
    _task: tokio::task::JoinHandle<()>,
}

impl TemplateWatcher {
    /// Start watching `templates_dir` recursively for `*.html` changes.
    /// On any change, reloads all Tera templates after a 150ms debounce.
    pub fn start(
        templates_dir: &Path,
        tera: Arc<RwLock<ui::TeraEnv>>,
    ) -> Result<Self, DaemonError> {
        let (tx, rx) = mpsc::channel::<PathBuf>(64);

        let mut watcher = RecommendedWatcher::new(
            move |res: std::result::Result<Event, notify::Error>| match res {
                Ok(event) => {
                    for path in event.paths {
                        if path.extension().is_some_and(|ext| ext == "html") {
                            let _ = tx.try_send(path);
                        }
                    }
                }
                Err(e) => warn!(error = %e, "template watcher error"),
            },
            Config::default(),
        )
        .map_err(|e| DaemonError::Watcher(e.to_string()))?;

        watcher
            .watch(templates_dir, RecursiveMode::Recursive)
            .map_err(|e| DaemonError::Watcher(e.to_string()))?;

        info!(dir = %templates_dir.display(), "template watcher started");

        let task = tokio::spawn(reload_loop(rx, tera));

        Ok(Self {
            _watcher: watcher,
            _task: task,
        })
    }
}

async fn reload_loop(mut rx: mpsc::Receiver<PathBuf>, tera: Arc<RwLock<ui::TeraEnv>>) {
    let debounce = Duration::from_millis(150);

    loop {
        // Block until at least one change arrives
        let Some(first) = rx.recv().await else {
            return;
        };

        // Drain additional events within the debounce window
        let mut changed = vec![first];
        tokio::time::sleep(debounce).await;
        while let Ok(path) = rx.try_recv() {
            changed.push(path);
        }

        let names: Vec<String> = changed
            .iter()
            .filter_map(|p| p.file_name())
            .map(|n| n.to_string_lossy().into_owned())
            .collect();

        let mut guard = tera.write().await;
        match guard.full_reload() {
            Ok(()) => info!(files = %names.join(", "), "templates reloaded"),
            Err(e) => warn!(error = %e, "template reload failed — keeping previous state"),
        }
    }
}
