//! File watcher traits for monitoring file system changes.

use crate::domain::error::Result;
use std::path::{Path, PathBuf};

/// Kind of file system event.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FileEventKind {
    /// File or directory was created.
    Created,
    /// File or directory was modified.
    Modified,
    /// File or directory was deleted.
    Deleted,
    /// File or directory was renamed.
    Renamed,
    /// Access permission changed.
    PermissionChanged,
    /// Metadata changed.
    MetadataChanged,
}

impl FileEventKind {
    /// Returns true if this is a content change event.
    #[must_use]
    pub const fn is_content_change(&self) -> bool {
        matches!(
            self,
            Self::Created | Self::Modified | Self::Deleted | Self::Renamed
        )
    }

    /// Returns true if this is a creation event.
    #[must_use]
    pub const fn is_create(&self) -> bool {
        matches!(self, Self::Created)
    }

    /// Returns true if this is a deletion event.
    #[must_use]
    pub const fn is_delete(&self) -> bool {
        matches!(self, Self::Deleted)
    }
}

/// A file system event.
#[derive(Debug, Clone)]
pub struct FileEvent {
    /// Kind of event.
    pub kind: FileEventKind,
    /// Path that triggered the event.
    pub path: PathBuf,
    /// New path for rename events.
    pub new_path: Option<PathBuf>,
}

impl FileEvent {
    /// Creates a new file event.
    #[must_use]
    pub fn new(kind: FileEventKind, path: impl Into<PathBuf>) -> Self {
        Self {
            kind,
            path: path.into(),
            new_path: None,
        }
    }

    /// Creates a created event.
    #[must_use]
    pub fn created(path: impl Into<PathBuf>) -> Self {
        Self::new(FileEventKind::Created, path)
    }

    /// Creates a modified event.
    #[must_use]
    pub fn modified(path: impl Into<PathBuf>) -> Self {
        Self::new(FileEventKind::Modified, path)
    }

    /// Creates a deleted event.
    #[must_use]
    pub fn deleted(path: impl Into<PathBuf>) -> Self {
        Self::new(FileEventKind::Deleted, path)
    }

    /// Creates a renamed event.
    #[must_use]
    pub fn renamed(from: impl Into<PathBuf>, to: impl Into<PathBuf>) -> Self {
        Self {
            kind: FileEventKind::Renamed,
            path: from.into(),
            new_path: Some(to.into()),
        }
    }

    /// Returns the new path for rename events.
    #[must_use]
    pub fn new_path(&self) -> Option<&Path> {
        self.new_path.as_deref()
    }
}

/// Configuration for file watching.
#[derive(Debug, Clone)]
pub struct WatchConfig {
    /// Watch directories recursively.
    pub recursive: bool,
    /// Debounce duration in milliseconds.
    pub debounce_ms: u64,
    /// Follow symbolic links.
    pub follow_links: bool,
    /// Compare file contents for modification detection.
    pub compare_contents: bool,
    /// File patterns to include.
    pub include_patterns: Vec<String>,
    /// File patterns to exclude.
    pub exclude_patterns: Vec<String>,
}

impl WatchConfig {
    /// Creates a new watch configuration.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets recursive watching.
    #[must_use]
    pub const fn recursive(mut self, recursive: bool) -> Self {
        self.recursive = recursive;
        self
    }

    /// Sets debounce duration.
    #[must_use]
    pub const fn debounce_ms(mut self, ms: u64) -> Self {
        self.debounce_ms = ms;
        self
    }

    /// Sets whether to follow symbolic links.
    #[must_use]
    pub const fn follow_links(mut self, follow: bool) -> Self {
        self.follow_links = follow;
        self
    }

    /// Adds an include pattern.
    #[must_use]
    pub fn include(mut self, pattern: impl Into<String>) -> Self {
        self.include_patterns.push(pattern.into());
        self
    }

    /// Adds an exclude pattern.
    #[must_use]
    pub fn exclude(mut self, pattern: impl Into<String>) -> Self {
        self.exclude_patterns.push(pattern.into());
        self
    }
}

impl Default for WatchConfig {
    fn default() -> Self {
        Self {
            recursive: true,
            debounce_ms: 100,
            follow_links: false,
            compare_contents: false,
            include_patterns: vec!["*".to_string()],
            exclude_patterns: vec![".*".to_string()],
        }
    }
}

/// Trait for watching file system changes.
///
/// Implement this trait to provide file watching functionality.
///
/// # Example
///
/// ```rust
/// use cfgmatic_reactive::prelude::{FileEvent, FileWatcher, Result};
/// use std::path::{Path, PathBuf};
///
/// struct MyWatcher;
///
/// impl FileWatcher for MyWatcher {
///     fn watch(&mut self, path: &Path) -> Result<()> {
///         // Start watching the path
///         Ok(())
///     }
///
///     fn unwatch(&mut self, _path: &Path) -> Result<()> {
///         Ok(())
///     }
///
///     fn poll(&mut self) -> Result<Option<FileEvent>> {
///         // Poll for events
///         Ok(None)
///     }
///
///     fn watched_paths(&self) -> Vec<PathBuf> {
///         Vec::new()
///     }
///
///     fn stop(&mut self) {}
/// }
///
/// let mut watcher = MyWatcher;
/// watcher.watch(Path::new("/tmp/config.toml")).unwrap();
/// ```
pub trait FileWatcher: Send + Sync {
    /// Start watching a path.
    ///
    /// # Errors
    ///
    /// Returns an error if watching cannot be started.
    fn watch(&mut self, path: &Path) -> Result<()>;

    /// Stop watching a path.
    ///
    /// # Errors
    ///
    /// Returns an error if watching cannot be stopped.
    fn unwatch(&mut self, path: &Path) -> Result<()>;

    /// Poll for the next event.
    ///
    /// # Errors
    ///
    /// Returns an error if polling fails.
    fn poll(&mut self) -> Result<Option<FileEvent>>;

    /// Returns the list of watched paths.
    fn watched_paths(&self) -> Vec<PathBuf>;

    /// Stops all watching.
    fn stop(&mut self);
}

/// Async trait for watching file system changes.
#[cfg(feature = "async")]
#[async_trait::async_trait]
pub trait AsyncFileWatcher: Send + Sync {
    /// Start watching a path.
    ///
    /// # Errors
    ///
    /// Returns an error if watching cannot be started.
    async fn watch(&mut self, path: &Path) -> Result<()>;

    /// Stop watching a path.
    ///
    /// # Errors
    ///
    /// Returns an error if watching cannot be stopped.
    async fn unwatch(&mut self, path: &Path) -> Result<()>;

    /// Receive the next event.
    ///
    /// # Errors
    ///
    /// Returns an error if receiving fails.
    async fn next(&mut self) -> Result<Option<FileEvent>>;

    /// Returns the list of watched paths.
    fn watched_paths(&self) -> Vec<PathBuf>;

    /// Stops all watching.
    async fn stop(&mut self);
}

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

    #[test]
    fn test_file_event_kind() {
        assert!(FileEventKind::Created.is_create());
        assert!(FileEventKind::Deleted.is_delete());
        assert!(FileEventKind::Modified.is_content_change());
        assert!(!FileEventKind::PermissionChanged.is_content_change());
    }

    #[test]
    fn test_file_event_constructors() {
        let created = FileEvent::created("/path/to/file");
        assert_eq!(created.kind, FileEventKind::Created);
        assert!(created.new_path.is_none());

        let renamed = FileEvent::renamed("/old", "/new");
        assert_eq!(renamed.kind, FileEventKind::Renamed);
        assert!(renamed.new_path.is_some());
    }

    #[test]
    fn test_watch_config_builder() {
        let config = WatchConfig::new()
            .recursive(false)
            .debounce_ms(200)
            .follow_links(true)
            .include("*.json")
            .exclude("node_modules");

        assert!(!config.recursive);
        assert_eq!(config.debounce_ms, 200);
        assert!(config.follow_links);
        assert!(config.include_patterns.contains(&"*.json".to_string()));
        assert!(
            config
                .exclude_patterns
                .contains(&"node_modules".to_string())
        );
    }
}
