//! Pure event types — no side effects.
//!
//! `StoredEvent` is the wire format shared by the gateway's WS event-push
//! relay (`gateway_relay.rs`, `http/gateway/websocket.rs`) — data-transformation
//! only; all I/O lives in `io.rs`.

use serde::{Deserialize, Serialize};

/// A pipeline event pushed to the gateway over the WS event-push relay.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum StoredEvent {
    /// A work item transitioned between pipeline stages.
    StageTransition {
        /// Work item ID (e.g. `"42_story_my_feature"`).
        story_id: String,
        /// Human-readable story name (empty string when unset).
        story_name: String,
        /// The stage the item moved FROM (display name, e.g. `"Current"`).
        from_stage: String,
        /// The stage the item moved TO (directory key, e.g. `"3_qa"`).
        to_stage: String,
        /// Unix timestamp in milliseconds when this event was recorded.
        timestamp_ms: u64,
    },
    /// A merge operation failed for a story.
    MergeFailure {
        /// Work item ID (e.g. `"42_story_my_feature"`).
        story_id: String,
        /// Human-readable story name (empty string when unset).
        story_name: String,
        /// Human-readable description of the failure.
        reason: String,
        /// Unix timestamp in milliseconds when this event was recorded.
        timestamp_ms: u64,
    },
    /// A story was blocked (e.g. retry limit exceeded).
    StoryBlocked {
        /// Work item ID (e.g. `"42_story_my_feature"`).
        story_id: String,
        /// Human-readable story name (empty string when unset).
        story_name: String,
        /// Human-readable reason the story was blocked.
        reason: String,
        /// Unix timestamp in milliseconds when this event was recorded.
        timestamp_ms: u64,
    },
    /// Free disk space on a sled crossed a warn/critical threshold (story 1200).
    DiskSpaceWarning {
        /// Severity level: `"warn"` or `"critical"`.
        level: String,
        /// Free space in bytes at the time of the check.
        free_bytes: u64,
        /// Size of the `target/` directory in bytes.
        target_bytes: u64,
        /// Size of the `.huskies/worktrees/` directory in bytes.
        worktrees_bytes: u64,
        /// Identifier of the sled that observed the reading.
        host_id: String,
        /// Unix timestamp in milliseconds when this event was recorded.
        timestamp_ms: u64,
    },
    /// Free disk space recovered on a sled after a warn/critical warning (story 1200).
    DiskSpaceRecovered {
        /// Free space in bytes at the time of recovery.
        free_bytes: u64,
        /// Identifier of the sled that observed the recovery.
        host_id: String,
        /// Unix timestamp in milliseconds when this event was recorded.
        timestamp_ms: u64,
    },
}

impl StoredEvent {
    /// Returns the `timestamp_ms` field common to all event variants.
    pub fn timestamp_ms(&self) -> u64 {
        match self {
            StoredEvent::StageTransition { timestamp_ms, .. } => *timestamp_ms,
            StoredEvent::MergeFailure { timestamp_ms, .. } => *timestamp_ms,
            StoredEvent::StoryBlocked { timestamp_ms, .. } => *timestamp_ms,
            StoredEvent::DiskSpaceWarning { timestamp_ms, .. } => *timestamp_ms,
            StoredEvent::DiskSpaceRecovered { timestamp_ms, .. } => *timestamp_ms,
        }
    }
}

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

    #[test]
    fn timestamp_ms_accessor_for_all_variants() {
        let variants = [
            StoredEvent::StageTransition {
                story_id: "1".to_string(),
                story_name: String::new(),
                from_stage: "2_current".to_string(),
                to_stage: "3_qa".to_string(),
                timestamp_ms: 100,
            },
            StoredEvent::MergeFailure {
                story_id: "2".to_string(),
                story_name: String::new(),
                reason: "x".to_string(),
                timestamp_ms: 200,
            },
            StoredEvent::StoryBlocked {
                story_id: "3".to_string(),
                story_name: String::new(),
                reason: "y".to_string(),
                timestamp_ms: 300,
            },
        ];
        assert_eq!(variants[0].timestamp_ms(), 100);
        assert_eq!(variants[1].timestamp_ms(), 200);
        assert_eq!(variants[2].timestamp_ms(), 300);
    }
}
