//! Reactive configuration layer - subscriptions and change notifications.
//!
//! This module provides granular subscriptions to configuration changes
//! using queries (field, path, predicate).
//!
//! # Example
//!
//! ```ignore
//! use cfgmatic::reactive::{ConfigStream, FieldQuery};
//! use cfgmatic::ConfigManager;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let manager = ConfigManager::new("myapp").build().await?;
//!
//!     // Subscribe to specific field
//!     let mut stream = manager
//!         .subscribe(FieldQuery::new(|c: &Config| c.server.port))
//!         .await?;
//!
//!     while let Some(change) = stream.next().await {
//!         println!("Port: {} -> {}", change.old, change.new);
//!     }
//!
//!     Ok(())
//! }
//! ```

#![allow(clippy::missing_errors_doc, clippy::missing_docs_in_private_items)]

pub use query::{FieldQuery, PathQuery, PredicateQuery, Query};
pub use stream::{ChangeSource, ConfigChange, ConfigStream};

mod query;
mod stream;

use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};

static SUBSCRIPTION_COUNTER: AtomicU64 = AtomicU64::new(1);

/// Unique subscription identifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SubscriptionId(u64);

impl SubscriptionId {
    /// Generate a new unique subscription ID.
    pub fn new() -> Self {
        Self(SUBSCRIPTION_COUNTER.fetch_add(1, Ordering::SeqCst))
    }
}

impl Default for SubscriptionId {
    fn default() -> Self {
        Self::new()
    }
}

/// A registered subscription to configuration changes.
pub struct Subscription<C, T> {
    #[allow(dead_code)]
    pub(crate) id: SubscriptionId,
    #[allow(dead_code)]
    pub(crate) query: Box<dyn Query<C, Output = T>>,
    #[allow(dead_code)]
    pub(crate) sender: tokio::sync::mpsc::Sender<ConfigChange<T>>,
}

/// Configuration diff - represents a single change.
#[derive(Debug, Clone)]
pub enum ConfigDiff {
    /// Field value changed.
    FieldChanged {
        /// JSON Pointer path to the field.
        path: String,
        /// Old value.
        old: serde_json::Value,
        /// New value.
        new: serde_json::Value,
    },
    /// New field added.
    FieldAdded {
        /// Path to the new field.
        path: String,
        /// Field value.
        value: serde_json::Value,
    },
    /// Field removed.
    FieldRemoved {
        /// Path to the removed field.
        path: String,
        /// Old field value.
        old: serde_json::Value,
    },
    /// Root configuration replaced.
    RootChanged,
}

/// Watch event from file system.
#[derive(Debug, Clone)]
pub enum WatchEvent {
    /// File(s) changed.
    FileChanged(Vec<PathBuf>),
    /// File created.
    FileCreated(PathBuf),
    /// File removed.
    FileRemoved(PathBuf),
    /// Error occurred.
    Error(String),
}

/// Builder for reactive configuration manager.
#[derive(Debug, Default)]
pub struct ReactiveBuilder {
    file_watch: bool,
    env_watch: bool,
    debounce: Option<std::time::Duration>,
}

impl ReactiveBuilder {
    /// Create a new reactive builder.
    pub fn new() -> Self {
        Self::default()
    }

    /// Enable file watching.
    #[must_use]
    #[allow(clippy::missing_const_for_fn)]
    pub fn file_watch(mut self, enabled: bool) -> Self {
        self.file_watch = enabled;
        self
    }

    /// Enable environment variable watching.
    #[must_use]
    #[allow(clippy::missing_const_for_fn)]
    pub fn env_watch(mut self, enabled: bool) -> Self {
        self.env_watch = enabled;
        self
    }

    /// Set debounce duration for changes.
    #[must_use]
    #[allow(clippy::missing_const_for_fn)]
    pub fn debounce(mut self, duration: std::time::Duration) -> Self {
        self.debounce = Some(duration);
        self
    }
}

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

    #[test]
    fn test_subscription_id_unique() {
        let id1 = SubscriptionId::new();
        let id2 = SubscriptionId::new();
        assert_ne!(id1.0, id2.0);
    }
}
