//! Event type enum for Claude Code hooks and API requests.

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;

/// Event types for Claude Code hooks and API requests.
///
/// Known event types are represented as enum variants for type safety.
/// Unknown event types (from future Claude Code versions) are captured
/// as `Custom(String)` to ensure forward compatibility.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum EventType {
    /// Session begins
    SessionStart,
    /// Session ends
    SessionEnd,
    /// Before tool execution
    PreToolUse,
    /// After tool execution
    PostToolUse,
    /// User permission requested
    PermissionRequest,
    /// Before context compaction
    PreCompact,
    /// Execution stopped
    Stop,
    /// Subagent started
    SubagentStart,
    /// Subagent stopped
    SubagentStop,
    /// System notification
    Notification,
    /// User submitted prompt
    UserPromptSubmit,
    /// API request (from OpenTelemetry)
    ApiRequest,
    /// Unknown/future event type (forward compatibility)
    Custom(String),
}

impl EventType {
    /// Returns the canonical string representation of the event type.
    pub fn as_str(&self) -> &str {
        match self {
            EventType::SessionStart => "SessionStart",
            EventType::SessionEnd => "SessionEnd",
            EventType::PreToolUse => "PreToolUse",
            EventType::PostToolUse => "PostToolUse",
            EventType::PermissionRequest => "PermissionRequest",
            EventType::PreCompact => "PreCompact",
            EventType::Stop => "Stop",
            EventType::SubagentStart => "SubagentStart",
            EventType::SubagentStop => "SubagentStop",
            EventType::Notification => "Notification",
            EventType::UserPromptSubmit => "UserPromptSubmit",
            EventType::ApiRequest => "ApiRequest",
            EventType::Custom(s) => s.as_str(),
        }
    }

    /// Returns true if this is a known (non-custom) event type.
    pub fn is_known(&self) -> bool {
        !matches!(self, EventType::Custom(_))
    }
}

impl fmt::Display for EventType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl FromStr for EventType {
    type Err = std::convert::Infallible;

    /// Parse an event type string (case-insensitive for known types).
    /// Unknown strings become `Custom(String)`.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Trim whitespace and use case-insensitive matching for known types
        let trimmed = s.trim();
        Ok(match trimmed.to_ascii_lowercase().as_str() {
            "sessionstart" => EventType::SessionStart,
            "sessionend" => EventType::SessionEnd,
            "pretooluse" => EventType::PreToolUse,
            "posttooluse" => EventType::PostToolUse,
            "permissionrequest" => EventType::PermissionRequest,
            "precompact" => EventType::PreCompact,
            "stop" => EventType::Stop,
            "subagentstart" => EventType::SubagentStart,
            "subagentstop" => EventType::SubagentStop,
            "notification" => EventType::Notification,
            "userpromptsubmit" => EventType::UserPromptSubmit,
            "apirequest" => EventType::ApiRequest,
            _ => EventType::Custom(trimmed.to_string()),
        })
    }
}

impl Serialize for EventType {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(self.as_str())
    }
}

impl<'de> Deserialize<'de> for EventType {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        // FromStr is infallible, so this always succeeds
        Ok(s.parse().unwrap())
    }
}

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

    /// Helper to parse EventType - infallible since unknown strings become Custom
    fn parse_event_type(s: &str) -> EventType {
        // EventType::from_str is infallible (Err type is Infallible)
        s.parse::<EventType>()
            .unwrap_or_else(|e: std::convert::Infallible| match e {})
    }

    #[test]
    fn test_event_type_parse_known_types() {
        assert_eq!(parse_event_type("SessionStart"), EventType::SessionStart);
        assert_eq!(parse_event_type("SessionEnd"), EventType::SessionEnd);
        assert_eq!(parse_event_type("PreToolUse"), EventType::PreToolUse);
        assert_eq!(parse_event_type("PostToolUse"), EventType::PostToolUse);
        assert_eq!(
            parse_event_type("PermissionRequest"),
            EventType::PermissionRequest
        );
        assert_eq!(parse_event_type("PreCompact"), EventType::PreCompact);
        assert_eq!(parse_event_type("Stop"), EventType::Stop);
        assert_eq!(parse_event_type("SubagentStart"), EventType::SubagentStart);
        assert_eq!(parse_event_type("SubagentStop"), EventType::SubagentStop);
        assert_eq!(parse_event_type("Notification"), EventType::Notification);
        assert_eq!(
            parse_event_type("UserPromptSubmit"),
            EventType::UserPromptSubmit
        );
    }

    #[test]
    fn test_event_type_parse_case_insensitive() {
        assert_eq!(parse_event_type("sessionstart"), EventType::SessionStart);
        assert_eq!(parse_event_type("SESSIONSTART"), EventType::SessionStart);
        assert_eq!(parse_event_type("SessionSTART"), EventType::SessionStart);
        assert_eq!(parse_event_type("pretooluse"), EventType::PreToolUse);
        assert_eq!(
            parse_event_type("USERPROMPTSUBMIT"),
            EventType::UserPromptSubmit
        );
    }

    #[test]
    fn test_event_type_parse_unknown_becomes_custom() {
        let unknown = parse_event_type("FutureEventType");
        assert_eq!(unknown, EventType::Custom("FutureEventType".to_string()));

        let custom = parse_event_type("MyCustomEvent");
        assert_eq!(custom, EventType::Custom("MyCustomEvent".to_string()));
    }

    #[test]
    fn test_event_type_parse_trims_whitespace() {
        // Leading/trailing whitespace should be trimmed for known types
        assert_eq!(parse_event_type(" SessionStart "), EventType::SessionStart);
        assert_eq!(parse_event_type("\tPreToolUse\n"), EventType::PreToolUse);
        // Custom types also get trimmed
        assert_eq!(
            parse_event_type("  CustomEvent  "),
            EventType::Custom("CustomEvent".to_string())
        );
    }

    #[test]
    fn test_event_type_display() {
        assert_eq!(EventType::SessionStart.to_string(), "SessionStart");
        assert_eq!(EventType::PreToolUse.to_string(), "PreToolUse");
        assert_eq!(
            EventType::Custom("MyEvent".to_string()).to_string(),
            "MyEvent"
        );
    }

    #[test]
    fn test_event_type_as_str() {
        assert_eq!(EventType::SessionStart.as_str(), "SessionStart");
        assert_eq!(EventType::Custom("Test".to_string()).as_str(), "Test");
    }

    #[test]
    fn test_event_type_is_known() {
        assert!(EventType::SessionStart.is_known());
        assert!(EventType::PreToolUse.is_known());
        assert!(!EventType::Custom("Unknown".to_string()).is_known());
    }

    #[test]
    fn test_event_type_serialize() -> Result<(), String> {
        let session_start =
            serde_json::to_string(&EventType::SessionStart).map_err(|e| e.to_string())?;
        assert_eq!(session_start, r#""SessionStart""#);

        let custom = serde_json::to_string(&EventType::Custom("MyEvent".to_string()))
            .map_err(|e| e.to_string())?;
        assert_eq!(custom, r#""MyEvent""#);
        Ok(())
    }

    #[test]
    fn test_event_type_deserialize() -> Result<(), String> {
        let session_start: EventType =
            serde_json::from_str(r#""SessionStart""#).map_err(|e| e.to_string())?;
        assert_eq!(session_start, EventType::SessionStart);

        let unknown: EventType =
            serde_json::from_str(r#""FutureEvent""#).map_err(|e| e.to_string())?;
        assert_eq!(unknown, EventType::Custom("FutureEvent".to_string()));
        Ok(())
    }

    #[test]
    fn test_event_type_roundtrip() -> Result<(), String> {
        let original = EventType::PreToolUse;
        let json = serde_json::to_string(&original).map_err(|e| e.to_string())?;
        let parsed: EventType = serde_json::from_str(&json).map_err(|e| e.to_string())?;
        assert_eq!(original, parsed);

        let custom = EventType::Custom("SpecialEvent".to_string());
        let json = serde_json::to_string(&custom).map_err(|e| e.to_string())?;
        let parsed: EventType = serde_json::from_str(&json).map_err(|e| e.to_string())?;
        assert_eq!(custom, parsed);
        Ok(())
    }
}
