//! Shared types for SSE event payloads between backend and frontend.
//!
//! These types ensure compile-time type safety for SSE event encoding (backend)
//! and decoding (frontend). Field mismatches cause compile errors instead of
//! runtime bugs.

use serde::Deserialize;
use serde::Serialize;

/// Subagent spawned event payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentSpawnedPayload {
    pub child_session_id: String,
    pub agent: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mode: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prompt_summary: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub project: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
}

/// Subagent update event payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentUpdatePayload {
    pub child_session_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,
    #[serde(rename = "needsAttention", default)]
    pub needs_attention: bool,
    #[serde(rename = "ivornActions", default)]
    pub ivorn_actions: Vec<serde_json::Value>,
}

/// Subagent permission request payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentPermissionPayload {
    pub child_session_id: String,
    #[serde(rename = "requestId")]
    pub request_id: String,
    #[serde(rename = "toolCall", skip_serializing_if = "Option::is_none")]
    pub tool_call: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub options: Option<Vec<PermissionOption>>,
}

/// Permission option in a permission request.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionOption {
    #[serde(rename = "optionId")]
    pub option_id: String,
    pub name: String,
    #[serde(default)]
    pub kind: String,
}

/// Subagent error payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentErrorPayload {
    pub child_session_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
    #[serde(default)]
    pub terminated: bool,
}

/// Subagent completed payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentCompletedPayload {
    pub child_session_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,
    #[serde(default)]
    pub terminated: bool,
}

/// Session error payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionErrorPayload {
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub detail: Option<String>,
    pub fatal: bool,
}

/// Shell complete payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellCompletePayload {
    #[serde(rename = "exitCode")]
    pub exit_code: Option<i32>,
    #[serde(rename = "elapsedMs")]
    pub elapsed_ms: u64,
}

/// Sleep start payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SleepStartPayload {
    #[serde(rename = "sleepId")]
    pub sleep_id: String,
    pub duration: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
}

/// Sleep tick payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SleepTickPayload {
    #[serde(rename = "sleepId")]
    pub sleep_id: String,
    pub remaining: u64,
    pub duration: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
}

/// Tool call payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallPayload {
    #[serde(rename = "toolCallId")]
    pub tool_call_id: String,
    pub title: String,
    pub purpose: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub args: Option<serde_json::Value>,
    #[serde(rename = "argsRaw", skip_serializing_if = "Option::is_none")]
    pub args_raw: Option<String>,
    pub kind: String,
}

/// Tool update payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolUpdatePayload {
    #[serde(rename = "toolCallId")]
    pub tool_call_id: String,
    pub status: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub purpose: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub args: Option<serde_json::Value>,
    #[serde(rename = "argsRaw", skip_serializing_if = "Option::is_none")]
    pub args_raw: Option<String>,
}

/// Tool diff payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDiffPayload {
    #[serde(rename = "toolCallId")]
    pub tool_call_id: String,
    pub path: String,
    #[serde(rename = "oldText", skip_serializing_if = "Option::is_none")]
    pub old_text: Option<String>,
    #[serde(rename = "newText")]
    pub new_text: String,
}

/// Tool content payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolContentPayload {
    #[serde(rename = "toolCallId")]
    pub tool_call_id: String,
    pub content: serde_json::Value,
}

/// Restart progress payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RestartProgressPayload {
    pub stage: String,
    pub message: String,
}

/// Ivorn action in subagent updates.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IvornAction {
    pub label: String,
    pub content: String,
}

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

    #[test]
    fn subagent_spawned_roundtrip() {
        let payload = SubagentSpawnedPayload {
            child_session_id: "child-1".into(),
            agent: "kiro-cli".into(),
            mode: Some("developer".into()),
            prompt_summary: Some("Test task".into()),
            project: Some("my-project".into()),
            name: None,
        };
        let json = serde_json::to_string(&payload).unwrap();
        let parsed: SubagentSpawnedPayload = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.child_session_id, "child-1");
        assert_eq!(parsed.project, Some("my-project".into()));
    }

    #[test]
    fn session_error_roundtrip() {
        let payload = SessionErrorPayload {
            message: "Test error".into(),
            detail: Some("Details here".into()),
            fatal: true,
        };
        let json = serde_json::to_string(&payload).unwrap();
        let parsed: SessionErrorPayload = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.message, "Test error");
        assert!(parsed.fatal);
    }

    #[test]
    fn tool_call_with_args() {
        let payload = ToolCallPayload {
            tool_call_id: "call-1".into(),
            title: "read_file".into(),
            purpose: "Check config".into(),
            args: Some(serde_json::json!({"path": "/tmp/test.txt"})),
            args_raw: None,
            kind: "read".into(),
        };
        let json = serde_json::to_string(&payload).unwrap();
        assert!(json.contains("toolCallId"));
        assert!(json.contains("call-1"));
    }

    #[test]
    fn tool_call_with_raw_args() {
        let payload = ToolCallPayload {
            tool_call_id: "call-2".into(),
            title: "execute".into(),
            purpose: "Run command".into(),
            args: None,
            args_raw: Some("invalid json".into()),
            kind: "execute".into(),
        };
        let json = serde_json::to_string(&payload).unwrap();
        assert!(json.contains("argsRaw"));
    }
}
