//! Configuration change types.
//!
//! This module defines types for representing configuration changes,
//! used in diff operations and change notifications.

use serde_json::Value;

/// A single configuration change.
///
/// Represents a change to a specific path in the configuration,
/// including the type of change and the old/new values.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Change {
    /// JSON Pointer path to the changed field.
    ///
    /// Uses JSON Pointer (RFC 6901) syntax:
    /// - `/server/port` - nested field
    /// - `/database/0/host` - array element
    /// - `/` - root configuration
    pub path: String,

    /// The type of change.
    pub change_type: ChangeType,

    /// Old value (if applicable).
    pub old: Option<Value>,

    /// New value (if applicable).
    pub new: Option<Value>,
}

impl Change {
    /// Create a new change.
    #[must_use]
    pub const fn new(path: String, change_type: ChangeType) -> Self {
        Self {
            path,
            change_type,
            old: None,
            new: None,
        }
    }

    /// Create a "no change" event.
    #[must_use]
    pub const fn no_change(path: String) -> Self {
        Self::new(path, ChangeType::NoChange)
    }

    /// Create a "create" change.
    #[must_use]
    pub const fn create(path: String, value: Value) -> Self {
        Self {
            path,
            change_type: ChangeType::Create,
            old: None,
            new: Some(value),
        }
    }

    /// Create an "update" change.
    #[must_use]
    pub const fn update(path: String, old: Value, new: Value) -> Self {
        Self {
            path,
            change_type: ChangeType::Update,
            old: Some(old),
            new: Some(new),
        }
    }

    /// Create a "delete" change.
    #[must_use]
    pub const fn delete(path: String, old: Value) -> Self {
        Self {
            path,
            change_type: ChangeType::Delete,
            old: Some(old),
            new: None,
        }
    }

    /// Check if this change represents any actual modification.
    #[must_use]
    pub const fn has_change(&self) -> bool {
        !matches!(self.change_type, ChangeType::NoChange)
    }

    /// Get the path as a string slice.
    #[must_use]
    pub fn path(&self) -> &str {
        &self.path
    }
}

/// The type of configuration change.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChangeType {
    /// No change detected.
    NoChange,

    /// A new field was created.
    Create,

    /// An existing field was updated.
    Update,

    /// A field was deleted.
    Delete,
}

impl ChangeType {
    /// Check if this change type modifies the configuration.
    #[must_use]
    pub const fn is_modifying(&self) -> bool {
        matches!(self, Self::Create | Self::Update | Self::Delete)
    }

    /// Check if this is a no-change type.
    #[must_use]
    pub const fn is_no_change(&self) -> bool {
        matches!(self, Self::NoChange)
    }
}

/// Summary of configuration changes.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ChangeSummary {
    /// Number of creates.
    pub creates: usize,

    /// Number of updates.
    pub updates: usize,

    /// Number of deletes.
    pub deletes: usize,

    /// Total number of changes.
    pub total: usize,
}

impl ChangeSummary {
    /// Create a new summary from a list of changes.
    #[must_use]
    pub fn from_changes(changes: &[Change]) -> Self {
        let mut summary = Self::default();

        for change in changes {
            match change.change_type {
                ChangeType::Create => summary.creates += 1,
                ChangeType::Update => summary.updates += 1,
                ChangeType::Delete => summary.deletes += 1,
                ChangeType::NoChange => {}
            }
        }

        summary.total = changes.len();
        summary
    }

    /// Check if there are any changes.
    #[must_use]
    pub const fn has_changes(&self) -> bool {
        self.creates > 0 || self.updates > 0 || self.deletes > 0
    }

    /// Get the total number of modifying changes (excluding `NoChange`).
    #[must_use]
    pub const fn modifying_count(&self) -> usize {
        self.creates + self.updates + self.deletes
    }
}

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

    #[test]
    fn test_change_creation() {
        let change = Change::create("/test".to_string(), Value::String("value".to_string()));
        assert_eq!(change.path, "/test");
        assert_eq!(change.change_type, ChangeType::Create);
        assert!(change.new.is_some());
        assert!(change.old.is_none());
    }

    #[test]
    fn test_update_change() {
        let change = Change::update(
            "/port".to_string(),
            Value::Number(8080.into()),
            Value::Number(3000.into()),
        );
        assert_eq!(change.change_type, ChangeType::Update);
        assert_eq!(change.old, Some(Value::Number(8080.into())));
        assert_eq!(change.new, Some(Value::Number(3000.into())));
    }

    #[test]
    fn test_has_change() {
        assert!(Change::create("/test".to_string(), Value::Null).has_change());
        assert!(Change::update("/test".to_string(), Value::Null, Value::Null).has_change());
        assert!(Change::delete("/test".to_string(), Value::Null).has_change());
        assert!(!Change::no_change("/test".to_string()).has_change());
    }

    #[test]
    fn test_change_type_properties() {
        assert!(ChangeType::Create.is_modifying());
        assert!(ChangeType::Update.is_modifying());
        assert!(ChangeType::Delete.is_modifying());
        assert!(!ChangeType::NoChange.is_modifying());
        assert!(ChangeType::NoChange.is_no_change());
    }

    #[test]
    fn test_change_summary() {
        let changes = vec![
            Change::create("/a".to_string(), Value::Null),
            Change::update("/b".to_string(), Value::Null, Value::Null),
            Change::delete("/c".to_string(), Value::Null),
            Change::no_change("/d".to_string()),
        ];

        let summary = ChangeSummary::from_changes(&changes);
        assert_eq!(summary.creates, 1);
        assert_eq!(summary.updates, 1);
        assert_eq!(summary.deletes, 1);
        assert_eq!(summary.total, 4);
        assert_eq!(summary.modifying_count(), 3);
        assert!(summary.has_changes());
    }

    #[test]
    fn test_empty_summary() {
        let summary = ChangeSummary::default();
        assert!(!summary.has_changes());
        assert_eq!(summary.modifying_count(), 0);
    }
}
