//! Event struct definition and core implementations.

use crate::model::event_type::EventType;
use crate::time::timestamp_millis;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

/// A hook event captured from Claude Code
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
    /// Database ID (None for new events)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<i64>,

    /// Machine identifier for multi-machine sync
    pub machine_id: String,

    /// Event timestamp
    #[serde(with = "timestamp_millis")]
    pub timestamp: DateTime<Utc>,

    /// Event type (SessionStart, PreToolUse, etc.)
    pub event_type: EventType,

    /// Session ID from Claude Code
    pub session_id: String,

    /// Source framework (claude, cursor, gemini, etc.)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub framework: Option<String>,

    /// Tool use ID for correlating PreToolUse/PostToolUse
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_use_id: Option<String>,

    /// Child agent ID from PostToolUse for Task tool (links parent → child sessions)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub spawned_agent_id: Option<String>,

    /// Tool name (Bash, Read, Write, Edit, Task, etc.)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_name: Option<String>,

    /// Subagent type for Task tool (Explore, Plan, claude-code-guide, etc.)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub subagent_type: Option<String>,

    /// Permission mode (default, plan, acceptEdits, bypassPermissions)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub permission_mode: Option<String>,

    /// Path to the session transcript JSONL file
    #[serde(skip_serializing_if = "Option::is_none")]
    pub transcript_path: Option<String>,

    /// Parent process ID
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pid: Option<i32>,

    /// Process start time in seconds (Unix timestamp).
    /// Captured at event ingestion time for session identity validation.
    ///
    /// Note: This field is a transport mechanism - it's captured during event
    /// ingestion but only persisted to the sessions table, not the events table.
    /// Used to populate `Session::process_start_time` for PID identity validation.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub process_start_time: Option<i64>,

    /// Working directory
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cwd: Option<String>,

    /// Git branch name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub git_branch: Option<String>,

    // API request fields (NULL for hook events, populated for ApiRequest events)
    /// Model used for API request (e.g., "claude-3-opus")
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,

    /// Input tokens used
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tokens_input: Option<i64>,

    /// Output tokens generated
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tokens_output: Option<i64>,

    /// Tokens read from cache
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tokens_cache_read: Option<i64>,

    /// Tokens written to cache
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tokens_cache_write: Option<i64>,

    /// Cost in USD
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cost_usd: Option<f64>,

    /// API call duration in milliseconds
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<i64>,

    /// JSON payload with all hook data
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payload: Option<String>,

    /// Extensible JSON metadata for future fields
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<String>,

    /// Data source: 'hook', 'otel', or 'transcript'
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source: Option<String>,

    /// Whether this is a sidechain request (subagent/auxiliary)
    /// Only available from transcript parsing - critical for context size tracking
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_sidechain: Option<bool>,
}

impl Event {
    /// Create a new event with current timestamp
    pub fn new(machine_id: String, event_type: EventType, session_id: String) -> Self {
        Self {
            id: None,
            machine_id,
            timestamp: Utc::now(),
            event_type,
            session_id,
            framework: None,
            tool_use_id: None,
            spawned_agent_id: None,
            tool_name: None,
            subagent_type: None,
            permission_mode: None,
            transcript_path: None,
            pid: None,
            process_start_time: None,
            cwd: None,
            git_branch: None,
            model: None,
            tokens_input: None,
            tokens_output: None,
            tokens_cache_read: None,
            tokens_cache_write: None,
            cost_usd: None,
            duration_ms: None,
            payload: None,
            metadata: None,
            source: None,
            is_sidechain: None,
        }
    }

    /// Check if this event is an API request (has token data)
    pub fn is_api_request(&self) -> bool {
        self.tokens_input.is_some() || self.tokens_output.is_some()
    }

    /// Total tokens (input + output) for API requests
    pub fn total_tokens(&self) -> i64 {
        self.tokens_input.unwrap_or(0) + self.tokens_output.unwrap_or(0)
    }

    /// Returns true if this is a transcript event from the main conversation chain.
    ///
    /// Main chain events are the primary conversation between user and agent.
    /// Sidechain events are subagent/auxiliary requests that run in parallel.
    ///
    /// This is used for filtering when updating session context - we only want
    /// to update context (issue/PR numbers, branch info) from main chain events.
    ///
    /// Returns `true` if:
    /// - The event source is "transcript" AND
    /// - The event is NOT marked as sidechain (`is_sidechain != Some(true)`)
    ///
    /// Note: Only transcript events have reliable sidechain info. Hook and OTEL
    /// events don't have this field, so this method returns `false` for them.
    pub fn is_main_chain(&self) -> bool {
        self.source.as_deref() == Some("transcript") && self.is_sidechain != Some(true)
    }

    /// Returns true if this is a transcript event from a sidechain (subagent).
    ///
    /// Sidechain events are subagent requests that run in parallel to the main
    /// conversation. They should not update session context.
    ///
    /// Note: Only transcript events have reliable sidechain info.
    pub fn is_sidechain_event(&self) -> bool {
        self.source.as_deref() == Some("transcript") && self.is_sidechain == Some(true)
    }
}
