//! Domain error types for reactive operations.

use std::path::PathBuf;
use thiserror::Error;

/// Error type for reactive operations.
#[derive(Debug, Error)]
pub enum ReactiveError {
    /// Subscription not found.
    #[error("Subscription not found: {0}")]
    SubscriptionNotFound(String),

    /// Subscription error.
    #[error("Subscription error: {0}")]
    Subscription(String),

    /// Handler execution failed.
    #[error("Handler execution failed: {0}")]
    HandlerFailed(String),

    /// Handler not found.
    #[error("Handler not found: {0}")]
    HandlerNotFound(String),

    /// Event dispatch error.
    #[error("Dispatch error: {0}")]
    Dispatch(String),

    /// File watch failed.
    #[error("File watch failed for {path}: {reason}")]
    FileWatchFailed {
        /// Path to file.
        path: PathBuf,
        /// Failure reason.
        reason: String,
    },

    /// Watcher error.
    #[error("Watcher error: {0}")]
    Watcher(String),

    /// Query execution failed.
    #[error("Query execution failed: {0}")]
    QueryFailed(String),

    /// Stream error.
    #[error("Stream error: {0}")]
    StreamError(String),

    /// Invalid subscription configuration.
    #[error("Invalid subscription configuration: {0}")]
    InvalidConfiguration(String),

    /// Configuration error.
    #[error("Configuration error: {0}")]
    Config(String),

    /// Maximum subscriptions exceeded.
    #[error("Maximum subscriptions exceeded: {0}")]
    MaxSubscriptions(usize),

    /// Maximum retries exceeded.
    #[error("Maximum retries ({0}) exceeded for handler")]
    MaxRetriesExceeded(u32),

    /// Handler timeout.
    #[error("Handler timeout after {0}ms")]
    Timeout(u64),

    /// Channel closed.
    #[error("Channel error: {0}")]
    Channel(String),
}

impl ReactiveError {
    /// Creates a new subscription not found error.
    #[must_use]
    pub fn subscription_not_found(id: impl Into<String>) -> Self {
        Self::SubscriptionNotFound(id.into())
    }

    /// Creates a new subscription error.
    #[must_use]
    pub fn subscription(msg: impl Into<String>) -> Self {
        Self::Subscription(msg.into())
    }

    /// Creates a new handler failed error.
    #[must_use]
    pub fn handler_failed(reason: impl Into<String>) -> Self {
        Self::HandlerFailed(reason.into())
    }

    /// Creates a new handler not found error.
    #[must_use]
    pub fn handler_not_found(id: impl Into<String>) -> Self {
        Self::HandlerNotFound(id.into())
    }

    /// Creates a new dispatch error.
    #[must_use]
    pub fn dispatch(msg: impl Into<String>) -> Self {
        Self::Dispatch(msg.into())
    }

    /// Creates a new file watch failed error.
    #[must_use]
    pub fn file_watch_failed(path: impl Into<PathBuf>, reason: impl Into<String>) -> Self {
        Self::FileWatchFailed {
            path: path.into(),
            reason: reason.into(),
        }
    }

    /// Creates a new watcher error.
    #[must_use]
    pub fn watcher(msg: impl Into<String>) -> Self {
        Self::Watcher(msg.into())
    }

    /// Creates a new query failed error.
    #[must_use]
    pub fn query_failed(reason: impl Into<String>) -> Self {
        Self::QueryFailed(reason.into())
    }

    /// Creates a new invalid configuration error.
    #[must_use]
    pub fn invalid_config(reason: impl Into<String>) -> Self {
        Self::InvalidConfiguration(reason.into())
    }

    /// Creates a new config error.
    #[must_use]
    pub fn config(reason: impl Into<String>) -> Self {
        Self::Config(reason.into())
    }

    /// Checks if error is retryable.
    #[must_use]
    pub const fn is_retryable(&self) -> bool {
        matches!(
            self,
            Self::HandlerFailed(_) | Self::Timeout(_) | Self::StreamError(_)
        )
    }
}

/// Result type alias for reactive operations.
pub type Result<T> = std::result::Result<T, ReactiveError>;

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

    #[test]
    fn test_subscription_not_found() {
        let err = ReactiveError::subscription_not_found("sub-123");
        assert!(err.to_string().contains("sub-123"));
    }

    #[test]
    fn test_file_watch_failed() {
        let err = ReactiveError::file_watch_failed("/path/to/file", "permission denied");
        assert!(err.to_string().contains("/path/to/file"));
        assert!(err.to_string().contains("permission denied"));
    }

    #[test]
    fn test_is_retryable() {
        assert!(ReactiveError::handler_failed("test").is_retryable());
        assert!(ReactiveError::Timeout(100).is_retryable());
        assert!(!ReactiveError::Channel("test".to_string()).is_retryable());
    }

    #[test]
    fn test_handler_not_found() {
        let err = ReactiveError::handler_not_found("handler-1");
        assert!(err.to_string().contains("handler-1"));
    }

    #[test]
    fn test_error_constructors_cover_remaining_variants() {
        let subscription = ReactiveError::subscription("invalid path");
        let dispatch = ReactiveError::dispatch("send failed");
        let watcher = ReactiveError::watcher("backend unavailable");
        let query = ReactiveError::query_failed("unsupported field");
        let invalid = ReactiveError::invalid_config("missing handler");
        let config = ReactiveError::config("bad debounce");

        assert!(subscription.to_string().contains("invalid path"));
        assert!(dispatch.to_string().contains("send failed"));
        assert!(watcher.to_string().contains("backend unavailable"));
        assert!(query.to_string().contains("unsupported field"));
        assert!(invalid.to_string().contains("missing handler"));
        assert!(config.to_string().contains("bad debounce"));
    }

    #[test]
    fn test_retryable_variants_are_limited() {
        assert!(ReactiveError::StreamError("stream".to_string()).is_retryable());
        assert!(!ReactiveError::Watcher("watch".to_string()).is_retryable());
        assert!(!ReactiveError::MaxSubscriptions(1).is_retryable());
        assert!(!ReactiveError::MaxRetriesExceeded(3).is_retryable());
    }
}
