//! The `Tool` trait — contract for every invocable operation exposed to surfaces
//! (CLI, HTTP, MCP). Implementations are registered into a [`crate::registry::Registry`].

use crate::protocol::{Environment, ToolCategory, ToolError, UserContext};
use async_trait::async_trait;
use serde::Serialize;
use std::sync::Arc;

/// Per-invocation context passed to every `Tool::invoke`.
///
/// `user` is `None` for unauthenticated surfaces (local CLI, MCP stdio — the invoking
/// process already has OS-level privileges). HTTP auth middleware populates it
/// from the JWT subject claims when `--auth` is active.
#[derive(Debug, Clone)]
pub struct Context {
    pub user: Option<UserContext>,
    pub env: Arc<Environment>,
}

impl Context {
    pub fn new(env: Arc<Environment>) -> Self {
        Self { user: None, env }
    }

    pub fn with_user(env: Arc<Environment>, user: UserContext) -> Self {
        Self { user: Some(user), env }
    }
}

/// Read-only metadata cached by the Registry at registration time.
///
/// Surfaces consume `ToolMetadata` for `tools/list`-style endpoints without
/// paying the cost of re-serializing each tool's JSON Schema per request.
#[derive(Debug, Clone, Serialize)]
pub struct ToolMetadata {
    pub name: &'static str,
    pub description: &'static str,
    pub category: ToolCategory,
    pub schema: Arc<serde_json::Value>,
}

/// Contract implemented by every invocable operation.
///
/// ### Invariants expected of implementations
/// - `name()` is unique across the entire Registry — enforced at registration.
/// - `name()` matches the pattern `<domain>_<action>` (snake_case), see ADR-029.
/// - `schema()` returns a valid JSON Schema describing `params` accepted by `invoke`.
/// - `invoke` parses `params` itself; invalid shape → `ToolError::invalid_param`.
/// - Returned `serde_json::Value` conforms to the response contract documented in
///   `crate::protocol` (e.g. `Listing<T>` for list endpoints, flat record for item).
#[async_trait]
pub trait Tool: Send + Sync + 'static {
    /// Canonical name — e.g. `"workspace_list"`, `"server_create"`.
    fn name(&self) -> &'static str;

    /// One-line human description (surfaces MCP `description` field and HTTP help).
    fn description(&self) -> &'static str;

    /// JSON Schema for `params`. Called once by the Registry at registration.
    fn schema(&self) -> serde_json::Value;

    /// RBAC category — consumed by HTTP auth middleware.
    fn category(&self) -> ToolCategory;

    /// Execute the tool.
    ///
    /// `params` is the raw JSON body/args sent by the caller. Implementations parse it
    /// into their typed param struct. `ctx` carries optional user identity and the
    /// environment pointers (workspace root, orchestrator URL, cache dir).
    async fn invoke(
        &self,
        params: serde_json::Value,
        ctx: &Context,
    ) -> Result<serde_json::Value, ToolError>;
}

#[cfg(test)]
pub(crate) mod test_support {
    //! Mock tools used by registry tests.

    use super::*;
    use serde_json::json;

    pub struct EchoTool;

    #[async_trait]
    impl Tool for EchoTool {
        fn name(&self) -> &'static str {
            "echo"
        }
        fn description(&self) -> &'static str {
            "Return the input params unchanged"
        }
        fn schema(&self) -> serde_json::Value {
            json!({
                "type": "object",
                "additionalProperties": true
            })
        }
        fn category(&self) -> ToolCategory {
            ToolCategory::Read
        }
        async fn invoke(
            &self,
            params: serde_json::Value,
            _ctx: &Context,
        ) -> Result<serde_json::Value, ToolError> {
            Ok(params)
        }
    }

    pub struct FailingTool;

    #[async_trait]
    impl Tool for FailingTool {
        fn name(&self) -> &'static str {
            "failing"
        }
        fn description(&self) -> &'static str {
            "Always returns NotFound"
        }
        fn schema(&self) -> serde_json::Value {
            json!({"type": "object"})
        }
        fn category(&self) -> ToolCategory {
            ToolCategory::Mutation
        }
        async fn invoke(
            &self,
            _params: serde_json::Value,
            _ctx: &Context,
        ) -> Result<serde_json::Value, ToolError> {
            Err(ToolError::not_found("nothing here"))
        }
    }
}
