//! Tool directive types for controlling how individual tools are handled.
//!
//! A [`ToolDirective`] associates a tool (identified by its `tool_id` string,
//! matching `ToolCall.tool_name` from `dirigent_protocol`) with a [`ToolHandler`]
//! that determines how invocations of that tool should be routed.
//!
//! Directives come in two flavors:
//! - **Passthrough** (`checked: false`) -- the tool call is forwarded to the
//!   agent without interception.
//! - **Checked** (`checked: true`) -- the tool call is intercepted and routed
//!   to the specified [`ToolHandler`].

use serde::{Deserialize, Serialize};

/// Determines how a tool invocation is routed when intercepted.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type")]
pub enum ToolHandler {
    /// Let the agent handle the tool call natively (default / passthrough).
    Agent,
    /// Route the tool call to the editor (e.g., file writes shown in the UI).
    Editor,
    /// Deny the tool call entirely.
    Deny,
    /// Hide the tool from the agent's tool list.
    Hide,
    /// Handle the tool call inside Dirigent itself.
    Dirigent,
    /// Route to a named plugin handler.
    Plugin { name: String },
}

impl Default for ToolHandler {
    fn default() -> Self {
        Self::Agent
    }
}

/// A directive that binds a tool identifier to a handler configuration.
///
/// # Examples
///
/// ```
/// use dirigent_core::tools::{ToolDirective, ToolHandler};
///
/// // Create a passthrough directive (agent handles the tool natively)
/// let passthrough = ToolDirective::passthrough("file_write");
/// assert!(!passthrough.checked);
///
/// // Create a checked directive that denies the tool
/// let denied = ToolDirective::checked("shell_exec", ToolHandler::Deny);
/// assert!(denied.checked);
/// ```
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolDirective {
    /// Tool identifier -- matches `ToolCall.tool_name` from dirigent_protocol.
    pub tool_id: String,
    /// Whether this tool should be intercepted (`true`) or passed through (`false`).
    pub checked: bool,
    /// The handler to use when `checked` is `true`.
    pub handler: ToolHandler,
}

impl ToolDirective {
    /// Create a passthrough directive that lets the agent handle the tool natively.
    pub fn passthrough(tool_id: impl Into<String>) -> Self {
        Self {
            tool_id: tool_id.into(),
            checked: false,
            handler: ToolHandler::Agent,
        }
    }

    /// Create a checked directive that intercepts the tool and routes it to the
    /// given handler.
    pub fn checked(tool_id: impl Into<String>, handler: ToolHandler) -> Self {
        Self {
            tool_id: tool_id.into(),
            checked: true,
            handler,
        }
    }
}

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

    #[test]
    fn passthrough_has_agent_handler() {
        let d = ToolDirective::passthrough("read_file");
        assert_eq!(d.tool_id, "read_file");
        assert!(!d.checked);
        assert_eq!(d.handler, ToolHandler::Agent);
    }

    #[test]
    fn checked_stores_handler() {
        let d = ToolDirective::checked("shell_exec", ToolHandler::Deny);
        assert_eq!(d.tool_id, "shell_exec");
        assert!(d.checked);
        assert_eq!(d.handler, ToolHandler::Deny);
    }

    #[test]
    fn default_handler_is_agent() {
        assert_eq!(ToolHandler::default(), ToolHandler::Agent);
    }

    #[test]
    fn serde_roundtrip_all_variants() {
        let variants = vec![
            ToolHandler::Agent,
            ToolHandler::Editor,
            ToolHandler::Deny,
            ToolHandler::Hide,
            ToolHandler::Dirigent,
            ToolHandler::Plugin { name: "my_plugin".to_string() },
        ];

        for handler in variants {
            let json = serde_json::to_string(&handler).expect("serialize");
            let deserialized: ToolHandler = serde_json::from_str(&json).expect("deserialize");
            assert_eq!(handler, deserialized);
        }
    }

    #[cfg(feature = "server")]
    #[test]
    fn serde_toml_roundtrip_all_variants() {
        // Regression test: adjacently-tagged enums with unit variants fail TOML serialization.
        // ToolHandler must use internally-tagged (#[serde(tag = "type")]) to work with TOML.
        let directives = vec![
            ToolDirective::passthrough("read_file"),
            ToolDirective::checked("shell_exec", ToolHandler::Deny),
            ToolDirective::checked("editor_write", ToolHandler::Editor),
            ToolDirective::checked("hidden_tool", ToolHandler::Hide),
            ToolDirective::checked("dirigent_tool", ToolHandler::Dirigent),
            ToolDirective::checked("plugin_tool", ToolHandler::Plugin { name: "my_plugin".to_string() }),
        ];

        // Wrap in a table so TOML can serialize (TOML needs a top-level table)
        #[derive(Serialize, Deserialize, PartialEq, Debug)]
        struct Wrapper {
            directives: Vec<ToolDirective>,
        }

        let wrapper = Wrapper { directives };
        let toml_str = toml::to_string_pretty(&wrapper).expect("TOML serialize");
        let roundtripped: Wrapper = toml::from_str(&toml_str).expect("TOML deserialize");
        assert_eq!(wrapper, roundtripped);
    }
}
