//! SSE Type Definitions for ACP Server
//!
//! This module contains all the data structures and types used for SSE notifications.
//! These types define the wire format for ACP protocol messages.

use serde::{Deserialize, Serialize};

use dirigent_protocol::ContentBlock;

// ============================================================================
// SessionUpdateParams
// ============================================================================

/// Params for session/update JSON-RPC notification (ACP spec compliant)
///
/// This matches the structure sent by Claude and expected by Zed.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct SessionUpdateParams {
    /// The session ID this update belongs to
    pub session_id: String,

    /// The update content
    pub update: SessionUpdateVariant,

    /// Optional event type override (default: "session/update")
    /// Reserved for future protocol extensions. Currently unused.
    #[serde(skip)]
    pub event_type_override: Option<String>,
}

impl SessionUpdateParams {
    /// Get the SSE event type for session updates
    ///
    /// According to ACP spec, session updates use "session/update" event type.
    pub fn event_type(&self) -> &str {
        self.event_type_override
            .as_deref()
            .unwrap_or("session/update")
    }

    /// Create a raw event with a custom event type
    ///
    /// This creates an event that will be serialized as raw JSON data
    /// (without the SessionUpdateParams wrapper) when sent over SSE.
    /// Used for `_rpc_response` deferred responses pushed via SSE after gateway transfer.
    pub fn raw_event(event_type: &str, data: serde_json::Value) -> Self {
        Self {
            session_id: String::new(), // Not used for raw events
            update: SessionUpdateVariant::Unknown { data },
            event_type_override: Some(event_type.to_string()),
        }
    }

    /// Check if this is a raw event (should serialize data directly)
    pub fn is_raw_event(&self) -> bool {
        self.event_type_override.is_some()
    }

    /// Get the data to serialize for SSE
    ///
    /// For raw events (with event_type_override), returns the raw JSON data.
    /// For normal events, returns None (caller should serialize the full struct).
    pub fn raw_data(&self) -> Option<&serde_json::Value> {
        if self.event_type_override.is_some() {
            if let SessionUpdateVariant::Unknown { data } = &self.update {
                return Some(data);
            }
        }
        None
    }

    /// Serialize to JSON string for SSE transmission
    ///
    /// For raw events, serializes just the raw data.
    /// For normal events, serializes the full SessionUpdateParams struct.
    pub fn to_sse_json(&self) -> String {
        if let Some(raw_data) = self.raw_data() {
            // Raw event: serialize just the data
            serde_json::to_string(raw_data).unwrap_or_else(|_| "{}".to_string())
        } else {
            // Normal event: serialize the full struct
            serde_json::to_string(self).unwrap_or_else(|_| "{}".to_string())
        }
    }
}

// ============================================================================
// ConfigOption Types
// ============================================================================

/// A single configuration option for session settings (outgoing ACP wire format)
///
/// Used in `config_option_update` notifications to send the complete set of
/// configuration options (modes, models, etc.) to the client.
///
/// Note: This is the **outgoing** wire format for ACP Server SSE.
/// For the **incoming** format (parsed from agents), see `dirigent_protocol::ConfigOption`.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ConfigOption {
    /// Unique identifier for this option (e.g., "mode", "model")
    pub id: String,
    /// Human-readable display name (e.g., "Session Mode", "Model")
    pub name: String,
    /// Optional description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    /// Semantic category for UX grouping (e.g., "mode", "model", "thought_level")
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<String>,
    /// Input type (e.g., "select", "toggle", "text")
    #[serde(rename = "type")]
    pub option_type: ConfigOptionType,
    /// Currently selected value
    pub current_value: String,
    /// Available options for "select" type
    #[serde(skip_serializing_if = "Option::is_none")]
    pub options: Option<Vec<ConfigOptionChoice>>,
}

/// Type of configuration option input
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ConfigOptionType {
    Select,
    Toggle,
    Text,
}

/// A single choice within a select-type config option
///
/// Matches ACP's `SessionConfigSelectOption` structure.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ConfigOptionChoice {
    /// Value identifier for this choice (sent back when selected)
    pub value: String,
    /// Human-readable display name
    pub name: String,
    /// Optional description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

// ============================================================================
// SessionUpdateVariant
// ============================================================================

/// Variants of session updates (ACP spec compliant)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
pub enum SessionUpdateVariant {
    /// Agent message chunk (streaming text)
    #[serde(rename = "agent_message_chunk")]
    AgentMessageChunk {
        /// The content block (text, image, etc.)
        content: ContentBlock,
    },

    /// Message generation complete
    #[serde(rename = "message_complete")]
    MessageComplete {
        /// The message ID that completed
        #[serde(rename = "messageId", skip_serializing_if = "Option::is_none")]
        message_id: Option<String>,
    },

    /// Session is idle and ready for input
    #[serde(rename = "session_idle")]
    SessionIdle {},

    /// Available slash commands update
    #[serde(rename = "available_commands_update")]
    AvailableCommandsUpdate {
        /// List of available slash commands
        #[serde(rename = "availableCommands")]
        available_commands: Vec<SlashCommand>,
    },

    /// Connector changed (session transferred to a different connector)
    #[serde(rename = "connector_changed")]
    ConnectorChanged {
        /// The new connector ID
        #[serde(rename = "newConnectorId")]
        new_connector_id: String,
        /// The new internal session ID in the target connector
        #[serde(rename = "newInternalSessionId")]
        new_internal_session_id: String,
        /// Whether a new session was created (true) or existing session loaded (false)
        #[serde(rename = "isNewSession")]
        is_new_session: bool,
    },

    /// Tool call started/initiated
    #[serde(rename = "tool_call")]
    ToolCall {
        /// The tool call ID
        #[serde(rename = "toolCallId")]
        tool_call_id: String,

        /// Optional title for the tool call
        #[serde(skip_serializing_if = "Option::is_none")]
        title: Option<String>,

        /// Optional kind/category (e.g., "search", "edit")
        #[serde(skip_serializing_if = "Option::is_none")]
        kind: Option<String>,

        /// Raw input parameters
        #[serde(rename = "rawInput", skip_serializing_if = "Option::is_none")]
        raw_input: Option<serde_json::Value>,

        /// Current status (pending, in_progress, completed, failed)
        #[serde(skip_serializing_if = "Option::is_none")]
        status: Option<String>,

        /// Content blocks (e.g., tool output)
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        content: Vec<ContentBlock>,

        /// Metadata (can include claudeCode.toolName, toolResponse, etc.)
        #[serde(skip_serializing_if = "Option::is_none")]
        _meta: Option<serde_json::Value>,
    },

    /// Tool call update (status change, output, etc.)
    #[serde(rename = "tool_call_update")]
    ToolCallUpdate {
        /// The tool call ID being updated
        #[serde(rename = "toolCallId")]
        tool_call_id: String,

        /// Updated status
        #[serde(skip_serializing_if = "Option::is_none")]
        status: Option<String>,

        /// Updated content blocks
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        content: Vec<ContentBlock>,

        /// Raw output from the tool
        #[serde(rename = "rawOutput", skip_serializing_if = "Option::is_none")]
        raw_output: Option<serde_json::Value>,

        /// Error message if tool call failed
        #[serde(skip_serializing_if = "Option::is_none")]
        error: Option<String>,

        /// Metadata (can include toolResponse from Claude)
        #[serde(skip_serializing_if = "Option::is_none")]
        _meta: Option<serde_json::Value>,
    },

    /// Agent request needing client response (e.g., permission prompt)
    ///
    /// This variant is used for bidirectional requests where the agent initiates
    /// a request (like `session/request_permission`) that requires a response from
    /// the client. The client should respond via the `/acp/agent_response` endpoint.
    #[serde(rename = "agent_request")]
    AgentRequest {
        /// The request ID from the agent (to correlate response)
        #[serde(rename = "requestId")]
        request_id: serde_json::Value,

        /// The method being requested (e.g., "session/request_permission")
        method: String,

        /// The request parameters
        params: serde_json::Value,
    },

    /// Session modes update (full state) - NOT SUPPORTED BY ZED
    /// Kept for future ACP protocol support
    #[serde(rename = "modes_update")]
    ModesUpdate {
        /// Session mode state (flattened)
        #[serde(flatten)]
        modes: dirigent_protocol::SessionModeState,
    },

    /// Session models update (full state) - NOT SUPPORTED BY ZED
    /// Kept for future ACP protocol support
    #[serde(rename = "models_update")]
    ModelsUpdate {
        /// Session model state (flattened)
        #[serde(flatten)]
        models: dirigent_protocol::SessionModelState,
    },

    /// Current mode update (standard ACP notification)
    /// Signals which mode is currently selected. Zed accepts this.
    #[serde(rename = "current_mode_update")]
    CurrentModeUpdate {
        /// The ID of the currently selected mode
        /// Note: Zed expects "currentModeId" not "modeId"
        #[serde(rename = "currentModeId")]
        mode_id: String,
    },

    /// Current model update - NOT SUPPORTED BY ZED
    /// Kept for completeness but Zed only accepts current_mode_update
    #[serde(rename = "current_model_update")]
    CurrentModelUpdate {
        /// The ID of the currently selected model
        #[serde(rename = "modelId")]
        model_id: String,
    },

    /// Config option update - send full configuration options to client
    ///
    /// This is used after session transfer to provide the target connector's
    /// actual modes/models instead of Gateway's placeholder values.
    /// See: https://agentclientprotocol.com/rfds/session-config-options
    #[serde(rename = "config_option_update")]
    ConfigOptionUpdate {
        /// List of configuration options to display
        #[serde(rename = "configOptions")]
        config_options: Vec<ConfigOption>,
    },

    /// Unknown update type (forward compatibility - pass through as raw JSON)
    #[serde(untagged)]
    Unknown {
        #[serde(flatten)]
        data: serde_json::Value,
    },
}

impl SessionUpdateVariant {
    /// Returns the variant name for logging (without data)
    pub fn variant_name(&self) -> &'static str {
        match self {
            SessionUpdateVariant::AgentMessageChunk { .. } => "AgentMessageChunk",
            SessionUpdateVariant::MessageComplete { .. } => "MessageComplete",
            SessionUpdateVariant::SessionIdle { .. } => "SessionIdle",
            SessionUpdateVariant::AvailableCommandsUpdate { .. } => "AvailableCommandsUpdate",
            SessionUpdateVariant::ConnectorChanged { .. } => "ConnectorChanged",
            SessionUpdateVariant::ToolCall { .. } => "ToolCall",
            SessionUpdateVariant::ToolCallUpdate { .. } => "ToolCallUpdate",
            SessionUpdateVariant::AgentRequest { .. } => "AgentRequest",
            SessionUpdateVariant::ModesUpdate { .. } => "ModesUpdate",
            SessionUpdateVariant::ModelsUpdate { .. } => "ModelsUpdate",
            SessionUpdateVariant::CurrentModeUpdate { .. } => "CurrentModeUpdate",
            SessionUpdateVariant::CurrentModelUpdate { .. } => "CurrentModelUpdate",
            SessionUpdateVariant::ConfigOptionUpdate { .. } => "ConfigOptionUpdate",
            SessionUpdateVariant::Unknown { .. } => "Unknown",
        }
    }
}

impl Default for SessionUpdateVariant {
    fn default() -> Self {
        SessionUpdateVariant::SessionIdle {}
    }
}

// ============================================================================
// SlashCommand
// ============================================================================

/// Slash command definition
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SlashCommand {
    pub name: String,
    pub description: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub input: Option<serde_json::Value>,
}

// ============================================================================
// AcpNotification (Legacy)
// ============================================================================

/// ACP Notification types for SSE streaming (DEPRECATED - keeping for compatibility)
///
/// These notifications are sent to clients over the SSE connection to inform
/// them about session events, message streaming, and errors.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(
    tag = "type",
    rename_all = "snake_case",
    rename_all_fields = "camelCase"
)]
pub enum AcpNotification {
    /// A chunk of message content has been received
    ///
    /// Sent during streaming to provide incremental content updates.
    /// The client should append this content to the message being built.
    MessageChunk {
        /// The session ID this chunk belongs to
        session_id: String,

        /// The message ID this chunk belongs to
        message_id: String,

        /// The content chunk (typically text)
        content: String,

        /// Optional content type (e.g., "text", "thought")
        #[serde(skip_serializing_if = "Option::is_none")]
        content_type: Option<String>,
    },

    /// A message has been completed
    ///
    /// Sent when the agent has finished generating a message.
    /// The client should finalize the message display.
    MessageComplete {
        /// The session ID
        session_id: String,

        /// The completed message ID
        message_id: String,
    },

    /// A session has become idle
    ///
    /// Sent when the session transitions to an idle state, meaning
    /// no active generation is occurring.
    SessionIdle {
        /// The session ID that became idle
        session_id: String,
    },

    /// An error occurred in a session
    ///
    /// Sent when an error occurs during processing.
    SessionError {
        /// The session ID where the error occurred
        session_id: String,

        /// Human-readable error message
        error: String,
    },

    /// A tool call has started or been updated
    ///
    /// Sent when a tool call is initiated or its status changes.
    ToolCallUpdate {
        /// The session ID
        session_id: String,

        /// The message ID containing this tool call
        message_id: String,

        /// The tool call ID
        tool_call_id: String,

        /// The tool name
        tool_name: String,

        /// Current status (pending, running, completed, error)
        status: String,

        /// Optional title for the tool call
        #[serde(skip_serializing_if = "Option::is_none")]
        title: Option<String>,

        /// Optional error message
        #[serde(skip_serializing_if = "Option::is_none")]
        error: Option<String>,
    },
}

impl AcpNotification {
    /// Get the SSE event type for this notification
    ///
    /// This is used as the `event:` field in the SSE stream.
    pub fn event_type(&self) -> &'static str {
        match self {
            AcpNotification::MessageChunk { .. } => "message/chunk",
            AcpNotification::MessageComplete { .. } => "message/complete",
            AcpNotification::SessionIdle { .. } => "session/idle",
            AcpNotification::SessionError { .. } => "session/error",
            AcpNotification::ToolCallUpdate { .. } => "tool/update",
        }
    }

    /// Convert to SSE format string
    ///
    /// Returns a string formatted for SSE transmission:
    /// ```text
    /// event: <event_type>
    /// data: <json_data>
    /// ```
    pub fn to_sse_string(&self) -> String {
        let data = serde_json::to_string(self).unwrap_or_else(|_| "{}".to_string());
        format!("event: {}\ndata: {}\n\n", self.event_type(), data)
    }
}

// ============================================================================
// ConfigOption Conversion Functions
// ============================================================================

/// Convert SessionModeState to a ConfigOption
///
/// Transforms the protocol's mode state into the ACP `config_option_update` format.
/// Uses "mode" ID to match the ACP protocol standard.
pub fn modes_to_config_option(modes: &dirigent_protocol::SessionModeState) -> ConfigOption {
    ConfigOption {
        id: "mode".to_string(),
        name: "Session Mode".to_string(),
        description: None,
        category: Some("mode".to_string()),
        option_type: ConfigOptionType::Select,
        current_value: modes.current_mode_id.clone(),
        options: Some(
            modes
                .available_modes
                .iter()
                .map(|m| ConfigOptionChoice {
                    value: m.id.clone(),
                    name: m.name.clone(),
                    description: m.description.clone(),
                })
                .collect(),
        ),
    }
}

/// Convert SessionModelState to a ConfigOption
///
/// Transforms the protocol's model state into the ACP `config_option_update` format.
/// Uses "model" ID to match the ACP protocol standard.
pub fn models_to_config_option(models: &dirigent_protocol::SessionModelState) -> ConfigOption {
    ConfigOption {
        id: "model".to_string(),
        name: "Model".to_string(),
        description: None,
        category: Some("model".to_string()),
        option_type: ConfigOptionType::Select,
        current_value: models.current_model_id.clone(),
        options: Some(
            models
                .available_models
                .iter()
                .map(|m| ConfigOptionChoice {
                    value: m.model_id.clone(),
                    name: m.name.clone(),
                    description: m.description.clone(),
                })
                .collect(),
        ),
    }
}
