use agent_client_protocol::schema::v1 as acp;
use anyhow::Result;
use gpui::{App, SharedString, Task};
use language_model::LanguageModelToolResultContent;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::rc::Rc;
use std::sync::Arc;

use crate::{AgentTool, SiblingThreadRequest, ThreadEnvironment, ToolCallEventStream, ToolInput};

/// Create a new agent thread that runs in parallel with this one.
///
/// Only use this after the user explicitly asks for or approves another thread.
/// The new thread appears in the agent sidebar just like a thread the user created
/// themselves, and runs independently — you will NOT receive its output and you
/// cannot interact with it afterwards. Use `spawn_agent` instead if you need the
/// results back.
///
/// A successful call returns only the title, agent ID, and model used; there is
/// currently no way to look up or control a sibling thread by session ID.
///
/// ### When to use
/// - The user explicitly asks you to create or start another thread.
/// - You may suggest a new thread for a separable task, but call this tool only
///   after the user explicitly agrees.
///
/// ### Prompt design
/// The new thread has no access to this conversation's history. Include in `prompt`
/// everything the new agent needs: goals, relevant file paths, constraints, and
/// context. Assume the new thread starts from a blank slate in the same project.
///
/// ### Agent and model selection
/// - If you don't know what agents or models are available, call `list_agents_and_models`.
/// - For bulk / lightweight work (e.g., spawning many parallel threads), prefer a
///   cheaper / faster model over the default.
/// - Leave `agent` and `model` unset to use the user's current defaults.
///
/// ### Worktree support
/// Set `use_new_worktree` to true to spawn the sibling inside a brand-new
/// workspace (a new tab) backed by linked git worktrees of each git
/// repository in the current project. This mirrors what the user gets when
/// they manually pick "Create worktree" from the worktree picker.
///
/// - The new workspace opens in its own tab; switch to it manually to see
///   the sibling's progress.
/// - The new worktrees start in detached HEAD state. Use `base_ref` to base
///   them off a specific branch, tag, or commit; omit it to base off `HEAD`.
///   The agent in the sibling thread can attach to a branch by running
///   `git switch -c <branch>` in its terminal if needed.
/// - `worktree_name` overrides the autogenerated directory name. Omit it to
///   let the editor pick a random non-colliding name.
/// - The project must contain at least one git repository, otherwise the
///   call fails.
///
/// Use this when the sibling needs to make changes that shouldn't touch the
/// user's current working tree (e.g., risky refactors, parallel experiments,
/// or work the user wants to review independently).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct CreateThreadToolInput {
    /// Short descriptive title for the new thread, shown in the sidebar
    /// (e.g., "Investigate flaky login test").
    pub title: String,

    /// The initial prompt to send to the new thread. Include all the context the
    /// new agent needs — files, goals, constraints — because it has no access to
    /// the current conversation's history.
    pub prompt: String,

    /// Optional agent ID to use. Omit to use the user's currently selected agent.
    /// Call `list_agents_and_models` if you need to see what's available.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub agent: Option<String>,

    /// Optional model override as `provider/model-id` (e.g.,
    /// `anthropic/claude-haiku-4-latest`). Only meaningful for Zed's native
    /// agent. Omit to use the user's configured default.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,

    /// If true, create the thread in a new git worktree rather than sharing
    /// the parent's worktree. The project must contain a git repository.
    #[serde(default)]
    pub use_new_worktree: bool,

    /// Optional name for the new worktree directory. When omitted, the
    /// editor generates a random non-colliding name (matching the
    /// manual "Create worktree" UI behavior). Only used when
    /// `use_new_worktree` is true.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub worktree_name: Option<String>,

    /// Git ref (branch, tag, or commit) to base the new worktree on. Only
    /// used when `use_new_worktree` is true. Defaults to `HEAD`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub base_ref: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CreateThreadToolOutput {
    Success {
        title: String,
        agent_id: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        model: Option<String>,
        /// A non-fatal heads-up about the created thread (e.g., the project's
        /// worktree layout was unusual and the new worktree may not match
        /// expectations). Present only when there's something to flag.
        #[serde(skip_serializing_if = "Option::is_none")]
        warning: Option<String>,
    },
    Error {
        error: String,
    },
}

impl From<CreateThreadToolOutput> for LanguageModelToolResultContent {
    fn from(output: CreateThreadToolOutput) -> Self {
        serde_json::to_string(&output)
            .unwrap_or_else(|e| format!("Failed to serialize create_thread output: {e}"))
            .into()
    }
}

pub struct CreateThreadTool {
    environment: Rc<dyn ThreadEnvironment>,
}

impl CreateThreadTool {
    pub fn new(environment: Rc<dyn ThreadEnvironment>) -> Self {
        Self { environment }
    }
}

impl AgentTool for CreateThreadTool {
    type Input = CreateThreadToolInput;
    type Output = CreateThreadToolOutput;

    const NAME: &'static str = "create_thread";

    fn kind() -> acp::ToolKind {
        acp::ToolKind::Other
    }

    fn initial_title(
        &self,
        input: Result<Self::Input, serde_json::Value>,
        _cx: &mut App,
    ) -> SharedString {
        match input {
            Ok(i) => format!("Create thread: {}", i.title).into(),
            Err(value) => value
                .get("title")
                .and_then(|v| v.as_str())
                .map(|s| format!("Create thread: {s}").into())
                .unwrap_or_else(|| "Create thread".into()),
        }
    }

    fn run(
        self: Arc<Self>,
        input: ToolInput<Self::Input>,
        _event_stream: ToolCallEventStream,
        cx: &mut App,
    ) -> Task<Result<Self::Output, Self::Output>> {
        cx.spawn(async move |cx| {
            let input = input
                .recv()
                .await
                .map_err(|e| CreateThreadToolOutput::Error {
                    error: format!("Failed to receive tool input: {e}"),
                })?;

            let title: SharedString = input.title.clone().into();
            let request = SiblingThreadRequest {
                title: title.clone(),
                prompt: input.prompt,
                agent_id: input.agent,
                model: input.model,
                use_new_worktree: input.use_new_worktree,
                worktree_name: input.worktree_name,
                base_ref: input.base_ref,
            };

            let task = self.environment.create_sibling_thread(request, cx);
            match task.await {
                Ok(info) => Ok(CreateThreadToolOutput::Success {
                    title: info.title.to_string(),
                    agent_id: info.agent_id,
                    model: info.model,
                    warning: info.warning,
                }),
                Err(error) => Err(CreateThreadToolOutput::Error {
                    error: error.to_string(),
                }),
            }
        })
    }
}
