//! Query types for reactive subscriptions.
//!
//! Queries define what parts of configuration to watch and
//! determine when changes should be notified.

/// A query that selects part of configuration and detects changes.
pub trait Query<C>: Send + Sync {
    /// The output type of this query.
    type Output: Clone + Send + 'static;

    /// Extract value from configuration.
    fn select(&self, config: &C) -> Self::Output;

    /// Check if this query's value changed between old and new config.
    fn changed(&self, old: &C, new: &C) -> bool
    where
        Self::Output: PartialEq,
    {
        self.select(old) != self.select(new)
    }

    /// Check if this query might be affected by a path change.
    /// Used for optimization - if path doesn't match, skip `changed()` check.
    fn matches_path(&self, _path: &str) -> bool {
        // Default: check all changes
        true
    }
}

/// Query that watches a specific field via lens function.
pub struct FieldQuery<C, T> {
    lens: Box<dyn Fn(&C) -> &T + Send + Sync>,
    _phantom: std::marker::PhantomData<C>,
}

impl<C, T> FieldQuery<C, T> {
    /// Create a new field query from a lens function.
    ///
    /// # Example
    ///
    /// ```ignore
    /// let query = FieldQuery::new(|c: &Config| &c.server.port);
    /// ```
    pub fn new<F>(lens: F) -> Self
    where
        F: Fn(&C) -> &T + Send + Sync + 'static,
    {
        Self {
            lens: Box::new(lens),
            _phantom: std::marker::PhantomData,
        }
    }
}

impl<C: Send + Sync, T: Clone + Send + 'static> Query<C> for FieldQuery<C, T> {
    type Output = T;

    fn select(&self, config: &C) -> T {
        (self.lens)(config).clone()
    }
}

/// Query that watches a JSON Pointer path.
pub struct PathQuery {
    path: String,
}

impl PathQuery {
    /// Create a new path query.
    ///
    /// Path uses JSON Pointer syntax (RFC 6901):
    /// - `/server/port` - nested field
    /// - `/server/*` - wildcard for server section
    /// - `/database/url` - specific field
    ///
    /// # Example
    ///
    /// ```ignore
    /// let query = PathQuery::new("/server/port");
    /// let query = PathQuery::new("/server/*");  // wildcard
    /// ```
    pub fn new(path: impl Into<String>) -> Self {
        Self { path: path.into() }
    }

    /// Get the path.
    pub fn path(&self) -> &str {
        &self.path
    }
}

impl<C: serde::Serialize + Send + Sync> Query<C> for PathQuery {
    type Output = Option<serde_json::Value>;

    fn select(&self, config: &C) -> Option<serde_json::Value> {
        let value = serde_json::to_value(config).ok()?;
        value.pointer(&self.path).cloned()
    }

    fn matches_path(&self, changed_path: &str) -> bool {
        if self.path.ends_with('*') {
            let prefix = self.path.trim_end_matches('*');
            changed_path.starts_with(prefix)
        } else {
            self.path == changed_path
        }
    }
}

/// Query that watches a predicate condition.
pub struct PredicateQuery<C> {
    predicate: Box<dyn Fn(&C) -> bool + Send + Sync>,
}

impl<C> PredicateQuery<C> {
    /// Create a new predicate query.
    ///
    /// # Example
    ///
    /// ```ignore
    /// let query = PredicateQuery::new(|c: &Config| c.server.port > 8000);
    /// ```
    pub fn new<F>(predicate: F) -> Self
    where
        F: Fn(&C) -> bool + Send + Sync + 'static,
    {
        Self {
            predicate: Box::new(predicate),
        }
    }
}

impl<C: Send + Sync + 'static> Query<C> for PredicateQuery<C> {
    type Output = bool;

    fn select(&self, config: &C) -> bool {
        (self.predicate)(config)
    }
}

// Future: OrQuery and QueryExt for combining queries
// pub struct OrQuery<C, T> { ... }
// pub trait QueryExt<C, T> { fn or(...) ... }

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

    #[derive(Clone, Debug, PartialEq, serde::Serialize)]
    struct TestConfig {
        port: u16,
        host: String,
    }

    #[test]
    fn test_field_query() {
        let query = FieldQuery::new(|c: &TestConfig| &c.port);

        let config = TestConfig {
            port: 8080,
            host: "localhost".to_string(),
        };

        assert_eq!(query.select(&config), 8080);
    }

    #[test]
    fn test_field_query_changed() {
        let query = FieldQuery::new(|c: &TestConfig| &c.port);

        let old = TestConfig {
            port: 8080,
            host: "localhost".to_string(),
        };
        let new = TestConfig {
            port: 3000,
            host: "localhost".to_string(),
        };

        assert!(query.changed(&old, &new));

        let same = TestConfig {
            port: 8080,
            host: "localhost".to_string(),
        };
        assert!(!query.changed(&old, &same));
    }

    #[test]
    fn test_predicate_query() {
        let query = PredicateQuery::new(|c: &TestConfig| c.port > 8000);

        let high = TestConfig {
            port: 8080,
            host: "localhost".to_string(),
        };
        let low = TestConfig {
            port: 80,
            host: "localhost".to_string(),
        };

        assert!(query.select(&high));
        assert!(!query.select(&low));
        assert!(query.changed(&high, &low));
    }

    #[test]
    fn test_path_query_matches() {
        let query = PathQuery::new("/server/*");

        assert!(Query::<TestConfig>::matches_path(&query, "/server/port"));
        assert!(Query::<TestConfig>::matches_path(&query, "/server/host"));
        assert!(!Query::<TestConfig>::matches_path(&query, "/database/url"));
    }
}
