use agent_types::common::ids::AgentId;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};

use crate::state::SubagentTerminalSnapshot;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct SpawnSubagentRequest {
    pub session_id: String,
    pub parent_agent_id: AgentId,
    pub description: String,
    pub task_goal: String,
    pub task_context: String,
    pub output_schema: Option<serde_json::Value>,
    pub subagent_role_id: Option<String>,
    pub predefined_prompt: Option<String>,
    pub max_turns: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct SpawnSubagentResult {
    pub agent_id: AgentId,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct JoinSubagentRequest {
    pub session_id: String,
    pub waiter_agent_id: AgentId,
    pub target_agent_id: AgentId,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum JoinSubagentResult {
    Ready { terminal: SubagentTerminalSnapshot },
    Pending { join_id: String },
}

#[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SubagentControlError {
    #[error("subagent not found: {agent_id}")]
    AgentNotFound { agent_id: String },
    #[error("subagent session mismatch: expected '{expected}', got '{actual}'")]
    SessionMismatch { expected: String, actual: String },
    #[error("agent cannot join itself: {agent_id}")]
    SelfJoin { agent_id: String },
    #[error("waiter already waiting on another join: {agent_id}")]
    WaiterAlreadyWaiting { agent_id: String },
    #[error("target terminal snapshot missing: {agent_id}")]
    MissingTerminalSnapshot { agent_id: String },
    #[error("subagent state invalid: {message}")]
    InvalidState { message: String },
    #[error("subagent control unavailable: {message}")]
    Unavailable { message: String },
}

#[async_trait]
pub trait SubagentControl: Send + Sync {
    async fn spawn(
        &self,
        request: SpawnSubagentRequest,
    ) -> Result<SpawnSubagentResult, SubagentControlError>;

    async fn join(
        &self,
        request: JoinSubagentRequest,
    ) -> Result<JoinSubagentResult, SubagentControlError>;
}