#![allow(dead_code)] // WIP scaffolding not yet wired
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentSummary {
    pub id: String,
    pub name: String,
    pub description: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub tags: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentState {
    pub id: String,
    pub name: String,
    pub description: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub llm_config: LlmConfig,
    pub memory: MemoryConfig,
    pub memory_blocks: Vec<MemoryBlock>,
    pub tools: Vec<String>,
    pub tags: Vec<String>,
    /// Public key (hex) of the instance that created this agent.
    /// `None` for agents created before this field existed.
    pub owner_seed_id: Option<String>,
    #[serde(rename = "_souveraine")]
    pub souveraine: SouveraineConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConfig {
    pub model: String,
    #[serde(default = "default_context_window")]
    pub context_window: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub temperature: Option<f32>,
    /// Maximum tool-calling rounds before forcing a text response.
    /// Configurable per-agent; 0 disables tools entirely.
    #[serde(default = "default_max_tool_rounds")]
    pub max_tool_rounds: u32,
    /// Milliseconds to wait between tool rounds to avoid rate-limit cascades.
    /// The follow-up LLM call after a tool executes can trigger rate limits
    /// if it arrives too quickly. Default 500ms.
    #[serde(default = "default_inter_round_delay")]
    pub inter_round_delay_ms: u64,
    /// Whether this model supports image inputs (vision).
    /// When false, images are stripped to text markers before sending.
    #[serde(default = "default_supports_images")]
    pub supports_images: bool,
    /// How many tool rounds between subconscious mid-turn checkpoints.
    /// 0 disables checkpointing entirely.
    #[serde(default = "default_checkpoint_interval")]
    pub checkpoint_interval: u32,
}

fn default_supports_images() -> bool { true }
fn default_checkpoint_interval() -> u32 { 10 }

fn default_context_window() -> u32 {
    128000
}

fn default_max_tool_rounds() -> u32 {
    10
}

fn default_inter_round_delay() -> u64 {
    500
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
    pub git_enabled: bool,
    pub auto_commit: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context_window: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryBlock {
    pub label: String,
    pub value: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SouveraineConfig {
    pub n1_enabled: bool,
    pub reflection_enabled: bool,
    pub archivist_enabled: bool,
    pub archivist_threshold: f32,
    pub sensorium_bandwidth: String,
    /// Per-agent model override for the subconscious (N+1) pass. When set,
    /// it wins over the global `[subconscious] model`. None falls back to
    /// the global setting, then the agent's own primary model.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub subconscious_model: Option<String>,
    /// Per-agent model override for the reflection (N+25) pass.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reflection_model: Option<String>,
    /// Per-agent model override for the archivist (N+100) synthesis pass.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub archivist_model: Option<String>,
    /// Per-agent inference provider name (e.g. "zai"). When set, the agent's
    /// requests route through the named provider instead of the global
    /// `[inference] provider`. None falls back to the global default.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provider: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreateAgentRequest {
    pub name: String,
    #[serde(default)]
    pub description: Option<String>,
    pub llm_config: LlmConfig,
    #[serde(default)]
    pub memory_blocks: Vec<MemoryBlock>,
    #[serde(default)]
    pub tools: Vec<String>,
    #[serde(default)]
    pub tags: Vec<String>,
}

#[derive(Debug, Deserialize)]
pub struct UpdateAgentRequest {
    #[serde(default)]
    pub name: Option<String>,
    #[serde(default)]
    pub description: Option<String>,
    #[serde(default)]
    pub llm_config: Option<LlmConfig>,
    #[serde(default)]
    pub memory_blocks: Option<Vec<MemoryBlock>>,
    #[serde(default)]
    pub tools: Option<Vec<String>>,
}

#[derive(Debug, Deserialize)]
pub struct AgentFilters {
    #[serde(default)]
    pub name: Option<String>,
    #[serde(default)]
    pub tags: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Conversation {
    pub id: String,
    pub agent_id: String,
    pub created_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Deserialize)]
pub struct CreateConversationRequest {
    pub agent_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
    pub role: String,
    pub content: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<ToolCall>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,
}

impl Message {
    /// Convert API Message to internal ConversationMessage
    pub fn to_conversation_message(&self) -> crate::core::session::ConversationMessage {
        use crate::core::session::{ConversationMessage, MessageRole, ContentBlock};

        let role = match self.role.as_str() {
            "system" => MessageRole::System,
            "user" => MessageRole::User,
            "assistant" => MessageRole::Assistant,
            "tool" => MessageRole::Tool,
            _ => MessageRole::User,
        };

        ConversationMessage {
            role,
            blocks: vec![ContentBlock::Text { text: self.content.clone() }],
            usage: None,
            timestamp: Some(chrono::Utc::now()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
    pub id: String,
    pub function: ToolFunction,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolFunction {
    pub name: String,
    pub arguments: String,
}

#[derive(Debug, Deserialize)]
pub struct SendMessageRequest {
    pub messages: Vec<Message>,
    #[serde(default)]
    pub stream: bool,
    /// Ambient context from the sending surface — what the environment
    /// senses at the moment of speaking: active window, open apps, cursor
    /// position, device sensors. Injected as a system note before the user
    /// message so the agent perceives the room she is being spoken to in.
    #[serde(default)]
    pub ambient: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct InterjectRequest {
    pub text: String,
}

/// The full wire mirror of [`crate::backend::BackendEvent`].
///
/// Every engine event crosses the SSE boundary — no silent skips. The
/// exhaustive `From` impls in both directions mean a new `BackendEvent`
/// variant is a compile error here, not an invisible hole in every
/// non-TUI surface. Tag values are the wire contract; never rename.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "message_type")]
pub enum StreamEvent {
    #[serde(rename = "assistant_message")]
    AssistantMessage { content: String },
    #[serde(rename = "reasoning_message")]
    ReasoningMessage { content: String },
    #[serde(rename = "tool_call_message")]
    ToolCallMessage {
        tool_call: ToolCall,
        #[serde(default)]
        round: u32,
    },
    #[serde(rename = "tool_return_message")]
    ToolReturnMessage { tool_return: ToolReturn },
    #[serde(rename = "souveraine_surfacing")]
    Surfacing { source: String, content: String, priority: String },
    #[serde(rename = "souveraine_reflection")]
    Reflection { content: String },
    #[serde(rename = "souveraine_archivist")]
    Archivist { synthesis: String, pressure: f32 },
    #[serde(rename = "compaction_warning")]
    CompactionWarning { pressure: f32, tier: u8 },
    #[serde(rename = "context_pressure")]
    ContextPressure { pressure: f32, tokens: usize },
    #[serde(rename = "inference_strain")]
    InferenceStrain { attempt: u32, status: u16, model: String },
    #[serde(rename = "schedule_active")]
    ScheduleActive { name: String },
    #[serde(rename = "schedule_complete")]
    ScheduleComplete { name: String, silent: bool },
    #[serde(rename = "subconscious_token")]
    SubconsciousToken { content: String },
    #[serde(rename = "subconscious_tool_call")]
    SubconsciousToolCall { name: String, arguments: String },
    #[serde(rename = "subconscious_tool_result")]
    SubconsciousToolResult { name: String, output: String, is_error: bool },
    #[serde(rename = "subconscious_halt")]
    SubconsciousHalt { reason: String, severity: String },
    #[serde(rename = "subconscious_pass")]
    SubconsciousPass { active: bool },
    #[serde(rename = "atmosphere")]
    Atmosphere { preset: String },
    #[serde(rename = "itinerary")]
    Itinerary { route: String },
    #[serde(rename = "outfit")]
    Outfit { name: String },
    #[serde(rename = "interstitial")]
    Interstitial { text: String, register: crate::backend::Register },
    #[serde(rename = "primary_complete")]
    PrimaryComplete,
    /// The turn failed in the substrate. Mirrors BackendEvent::Error so a
    /// dead turn is never a silently-ended stream.
    #[serde(rename = "error")]
    Error { message: String },
    #[serde(rename = "done")]
    Done,
    #[serde(rename = "ping")]
    Ping,
}

impl StreamEvent {
    pub fn message_type(&self) -> &'static str {
        match self {
            StreamEvent::AssistantMessage { .. } => "message",
            StreamEvent::ReasoningMessage { .. } => "reasoning",
            StreamEvent::ToolCallMessage { .. } => "tool_call",
            StreamEvent::ToolReturnMessage { .. } => "tool_return",
            StreamEvent::Surfacing { .. } => "souveraine_surfacing",
            StreamEvent::Reflection { .. } => "souveraine_reflection",
            StreamEvent::Archivist { .. } => "souveraine_archivist",
            StreamEvent::CompactionWarning { .. } => "compaction_warning",
            StreamEvent::ContextPressure { .. } => "context_pressure",
            StreamEvent::InferenceStrain { .. } => "inference_strain",
            StreamEvent::ScheduleActive { .. } => "schedule_active",
            StreamEvent::ScheduleComplete { .. } => "schedule_complete",
            StreamEvent::SubconsciousToken { .. } => "subconscious_token",
            StreamEvent::SubconsciousToolCall { .. } => "subconscious_tool_call",
            StreamEvent::SubconsciousToolResult { .. } => "subconscious_tool_result",
            StreamEvent::SubconsciousHalt { .. } => "subconscious_halt",
            StreamEvent::SubconsciousPass { .. } => "subconscious_pass",
            StreamEvent::Atmosphere { .. } => "atmosphere",
            StreamEvent::Itinerary { .. } => "itinerary",
            StreamEvent::Outfit { .. } => "outfit",
            StreamEvent::Interstitial { .. } => "interstitial",
            StreamEvent::PrimaryComplete => "primary_complete",
            StreamEvent::Error { .. } => "error",
            StreamEvent::Done => "done",
            StreamEvent::Ping => "ping",
        }
    }
}

impl From<crate::backend::BackendEvent> for StreamEvent {
    fn from(be: crate::backend::BackendEvent) -> Self {
        use crate::backend::BackendEvent as BE;
        match be {
            BE::Token(content) => Self::AssistantMessage { content },
            BE::Reasoning(content) => Self::ReasoningMessage { content },
            BE::Surfacing { source, content, priority } => Self::Surfacing { source, content, priority },
            BE::Reflection(content) => Self::Reflection { content },
            BE::Archivist { synthesis, pressure } => Self::Archivist { synthesis, pressure },
            BE::CompactionWarning { pressure, tier } => Self::CompactionWarning { pressure, tier },
            BE::ContextPressure(pressure, tokens) => Self::ContextPressure { pressure, tokens },
            BE::InferenceStrain { attempt, status, model } => Self::InferenceStrain { attempt, status, model },
            BE::ScheduleActive { name } => Self::ScheduleActive { name },
            BE::ScheduleComplete { name, silent } => Self::ScheduleComplete { name, silent },
            BE::ToolCall { id, name, arguments, round } => Self::ToolCallMessage {
                tool_call: ToolCall { id, function: ToolFunction { name, arguments } },
                round,
            },
            BE::ToolResult { id, name, output, is_error } => Self::ToolReturnMessage {
                tool_return: ToolReturn {
                    status: if is_error { "error".into() } else { "success".into() },
                    output,
                    id,
                    name,
                },
            },
            BE::SubconsciousToken(content) => Self::SubconsciousToken { content },
            BE::SubconsciousToolCall { name, arguments } => Self::SubconsciousToolCall { name, arguments },
            BE::SubconsciousToolResult { name, output, is_error } => {
                Self::SubconsciousToolResult { name, output, is_error }
            }
            BE::SubconsciousHalt { reason, severity } => Self::SubconsciousHalt { reason, severity },
            BE::SubconsciousPass(active) => Self::SubconsciousPass { active },
            BE::Atmosphere(preset) => Self::Atmosphere { preset },
            BE::Itinerary(route) => Self::Itinerary { route },
            BE::Outfit(name) => Self::Outfit { name },
            BE::Interstitial { text, register } => Self::Interstitial { text, register },
            BE::Keepalive => Self::Ping,
            BE::PrimaryComplete => Self::PrimaryComplete,
            BE::Error { message } => Self::Error { message },
            BE::Done => Self::Done,
        }
    }
}

impl From<StreamEvent> for crate::backend::BackendEvent {
    fn from(se: StreamEvent) -> Self {
        use crate::backend::BackendEvent as BE;
        match se {
            StreamEvent::AssistantMessage { content } => BE::Token(content),
            StreamEvent::ReasoningMessage { content } => BE::Reasoning(content),
            StreamEvent::Surfacing { source, content, priority } => BE::Surfacing { source, content, priority },
            StreamEvent::Reflection { content } => BE::Reflection(content),
            StreamEvent::Archivist { synthesis, pressure } => BE::Archivist { synthesis, pressure },
            StreamEvent::CompactionWarning { pressure, tier } => BE::CompactionWarning { pressure, tier },
            StreamEvent::ContextPressure { pressure, tokens } => BE::ContextPressure(pressure, tokens),
            StreamEvent::InferenceStrain { attempt, status, model } => BE::InferenceStrain { attempt, status, model },
            StreamEvent::ScheduleActive { name } => BE::ScheduleActive { name },
            StreamEvent::ScheduleComplete { name, silent } => BE::ScheduleComplete { name, silent },
            StreamEvent::ToolCallMessage { tool_call, round } => BE::ToolCall {
                id: tool_call.id,
                name: tool_call.function.name,
                arguments: tool_call.function.arguments,
                round,
            },
            StreamEvent::ToolReturnMessage { tool_return } => BE::ToolResult {
                id: tool_return.id,
                name: tool_return.name,
                is_error: tool_return.status == "error",
                output: tool_return.output,
            },
            StreamEvent::SubconsciousToken { content } => BE::SubconsciousToken(content),
            StreamEvent::SubconsciousToolCall { name, arguments } => BE::SubconsciousToolCall { name, arguments },
            StreamEvent::SubconsciousToolResult { name, output, is_error } => {
                BE::SubconsciousToolResult { name, output, is_error }
            }
            StreamEvent::SubconsciousHalt { reason, severity } => BE::SubconsciousHalt { reason, severity },
            StreamEvent::SubconsciousPass { active } => BE::SubconsciousPass(active),
            StreamEvent::Atmosphere { preset } => BE::Atmosphere(preset),
            StreamEvent::Itinerary { route } => BE::Itinerary(route),
            StreamEvent::Outfit { name } => BE::Outfit(name),
            StreamEvent::Interstitial { text, register } => BE::Interstitial { text, register },
            StreamEvent::Ping => BE::Keepalive,
            StreamEvent::PrimaryComplete => BE::PrimaryComplete,
            StreamEvent::Error { message } => BE::Error { message },
            StreamEvent::Done => BE::Done,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolReturn {
    pub status: String,
    pub output: String,
    /// Tool-call id this return answers. Empty on frames from pre-widening servers.
    #[serde(default)]
    pub id: String,
    /// Tool name. Empty on frames from pre-widening servers.
    #[serde(default)]
    pub name: String,
}

#[derive(Debug, Serialize)]
pub struct ErrorResponse {
    pub error: String,
    pub message: String,
}
