//! File system watcher for manifest invalidation
//!
//! Watches build.ninja files and their includes for changes.

use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use parking_lot::RwLock;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::time::Duration;
use tracing::{debug, error, info, warn};

/// Events from the file watcher
#[derive(Debug, Clone)]
pub enum WatchEvent {
    /// A watched file was modified
    Modified(PathBuf),
    /// A watched file was deleted
    Deleted(PathBuf),
    /// A watched file was created (for new includes)
    Created(PathBuf),
    /// Watcher error
    Error(String),
}

/// Lifecycle state of the file watcher
enum WatcherState {
    /// Inactive - watcher has not been started
    Inactive {
        event_tx: Sender<WatchEvent>,
        event_rx: Receiver<WatchEvent>,
    },
    /// Active - watcher is running and receiving events
    Active {
        watcher: RecommendedWatcher,
        event_rx: Receiver<WatchEvent>,
    },
    /// Active but receiver has been taken (events still generated by watcher)
    ActiveNoRx { watcher: RecommendedWatcher },
    /// Stopped - watcher has been shut down
    Stopped,
}

/// File system watcher for build manifests
pub struct FileWatcher {
    /// The current lifecycle state
    state: RwLock<WatcherState>,

    /// Paths currently being watched
    watched_paths: RwLock<HashSet<PathBuf>>,
}

impl FileWatcher {
    /// Create a new file watcher in the inactive state.
    pub fn new() -> anyhow::Result<Self> {
        let (event_tx, event_rx) = channel();

        Ok(Self {
            state: RwLock::new(WatcherState::Inactive { event_tx, event_rx }),
            watched_paths: RwLock::new(HashSet::new()),
        })
    }

    /// Start the watcher, transitioning from Inactive to Active.
    ///
    /// Returns an error if the watcher is already Active or Stopped.
    pub fn start(&mut self) -> anyhow::Result<()> {
        let mut guard = self.state.write();
        let old_state = std::mem::replace(&mut *guard, WatcherState::Stopped);
        match old_state {
            WatcherState::Inactive { event_tx, event_rx } => {
                let tx_clone = event_tx.clone();
                let watcher = RecommendedWatcher::new(
                    move |result: Result<Event, notify::Error>| match result {
                        Ok(event) => {
                            Self::handle_event(&tx_clone, event);
                        }
                        Err(e) => {
                            let _ = tx_clone.send(WatchEvent::Error(e.to_string()));
                        }
                    },
                    Config::default().with_poll_interval(Duration::from_secs(1)),
                )?;

                *guard = WatcherState::Active { watcher, event_rx };
                Ok(())
            }
            other => {
                let msg = match &other {
                    WatcherState::Active { .. } => "FileWatcher is already active",
                    WatcherState::Stopped => "FileWatcher has been stopped and cannot be restarted",
                    _ => unreachable!(),
                };
                *guard = other;
                anyhow::bail!(msg)
            }
        }
    }

    /// Handle a notify event
    fn handle_event(tx: &Sender<WatchEvent>, event: Event) {
        use notify::EventKind;

        for path in event.paths {
            let watch_event = match event.kind {
                EventKind::Create(_) => WatchEvent::Created(path),
                EventKind::Modify(_) => WatchEvent::Modified(path),
                EventKind::Remove(_) => WatchEvent::Deleted(path),
                _ => continue,
            };

            if let Err(e) = tx.send(watch_event) {
                error!("Failed to send watch event: {}", e);
            }
        }
    }

    /// Watch a path
    pub fn watch(&mut self, path: &PathBuf) -> anyhow::Result<()> {
        let mut guard = self.state.write();
        let watcher = match &mut *guard {
            WatcherState::Active { watcher, .. } | WatcherState::ActiveNoRx { watcher } => watcher,
            _ => {
                anyhow::bail!("Cannot watch path: FileWatcher is not active")
            }
        };

        // Only watch if not already watching
        if self.watched_paths.read().contains(path) {
            return Ok(());
        }

        debug!("Watching: {}", path.display());
        watcher.watch(path, RecursiveMode::NonRecursive)?;
        self.watched_paths.write().insert(path.clone());
        Ok(())
    }

    /// Unwatch a path
    pub fn unwatch(&mut self, path: &PathBuf) -> anyhow::Result<()> {
        let mut guard = self.state.write();
        let watcher = match &mut *guard {
            WatcherState::Active { watcher, .. } | WatcherState::ActiveNoRx { watcher } => watcher,
            _ => {
                anyhow::bail!("Cannot unwatch path: FileWatcher is not active")
            }
        };

        if self.watched_paths.read().contains(path) {
            debug!("Unwatching: {}", path.display());
            watcher.unwatch(path)?;
            self.watched_paths.write().remove(path);
        }
        Ok(())
    }

    /// Watch multiple paths
    pub fn watch_paths(&mut self, paths: &[PathBuf]) -> anyhow::Result<()> {
        for path in paths {
            if let Err(e) = self.watch(path) {
                warn!("Failed to watch {}: {}", path.display(), e);
            }
        }
        Ok(())
    }

    /// Get the number of watched paths
    pub fn watched_count(&self) -> usize {
        self.watched_paths.read().len()
    }

    /// Take the event receiver (can only be called once).
    /// Only valid when the watcher is Active.
    pub fn take_event_receiver(&mut self) -> Option<Receiver<WatchEvent>> {
        let mut guard = self.state.write();
        let old_state = std::mem::replace(&mut *guard, WatcherState::Stopped);
        match old_state {
            WatcherState::Active { watcher, event_rx } => {
                *guard = WatcherState::ActiveNoRx { watcher };
                Some(event_rx)
            }
            other => {
                *guard = other;
                None
            }
        }
    }

    /// Stop the watcher, transitioning to Stopped.
    pub fn stop(&mut self) {
        let mut guard = self.state.write();
        *guard = WatcherState::Stopped;
    }
}

impl Default for FileWatcher {
    fn default() -> Self {
        Self::new().expect("FileWatcher::new should not fail")
    }
}

/// Watcher event processor that integrates with DaemonState
pub struct WatcherProcessor {
    event_rx: Receiver<WatchEvent>,
}

impl WatcherProcessor {
    /// Create a new watcher processor from a file watcher
    pub fn new(watcher: &mut FileWatcher) -> Option<Self> {
        watcher
            .take_event_receiver()
            .map(|rx| Self { event_rx: rx })
    }

    /// Process events, returning paths that need invalidation
    pub fn process_events(&self) -> Vec<PathBuf> {
        let mut invalidate = Vec::new();

        // Drain all pending events
        while let Ok(event) = self.event_rx.try_recv() {
            match event {
                WatchEvent::Modified(path) | WatchEvent::Deleted(path) => {
                    info!("File changed: {}", path.display());
                    invalidate.push(path);
                }
                WatchEvent::Created(path) => {
                    debug!("File created: {}", path.display());
                    // Created files might be new includes
                    invalidate.push(path);
                }
                WatchEvent::Error(e) => {
                    warn!("Watcher error: {}", e);
                }
            }
        }

        invalidate
    }

    /// Block waiting for an event with timeout
    pub fn recv_timeout(&self, timeout: Duration) -> Option<WatchEvent> {
        self.event_rx.recv_timeout(timeout).ok()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_watcher_creation() {
        let watcher = FileWatcher::new();
        assert!(watcher.is_ok());
    }

    #[test]
    fn test_watched_count() {
        let watcher = FileWatcher::new().unwrap();
        assert_eq!(watcher.watched_count(), 0);
    }
}
