<?php

/**
 * Tools method handlers for MCP requests.
 *
 * @package McpAdapter
 */
declare (strict_types=1);
namespace PYS_PRO_GLOBAL\WP\MCP\Handlers\Tools;

use PYS_PRO_GLOBAL\WP\MCP\Core\McpServer;
use PYS_PRO_GLOBAL\WP\MCP\Domain\Utils\ContentBlockHelper;
use PYS_PRO_GLOBAL\WP\MCP\Handlers\HandlerHelperTrait;
use PYS_PRO_GLOBAL\WP\MCP\Infrastructure\ErrorHandling\McpErrorFactory;
use PYS_PRO_GLOBAL\WP\MCP\Infrastructure\Observability\FailureReason;
use PYS_PRO_GLOBAL\WP\McpSchema\Server\Tools\DTO\CallToolResult;
use PYS_PRO_GLOBAL\WP\McpSchema\Server\Tools\DTO\ListToolsResult;
/**
 * Handles tools-related MCP methods.
 */
class ToolsHandler
{
    use HandlerHelperTrait;
    /**
     * Default MIME type for image results when none is specified.
     *
     * @var string
     */
    private const DEFAULT_IMAGE_MIME_TYPE = 'image/png';
    /**
     * The WordPress MCP instance.
     *
     * @var \WP\MCP\Core\McpServer
     */
    private McpServer $mcp;
    /**
     * Constructor.
     *
     * @param \WP\MCP\Core\McpServer $mcp The WordPress MCP instance.
     */
    public function __construct(McpServer $mcp)
    {
        $this->mcp = $mcp;
    }
    /**
     * Handles the tools/list/all request.
     *
     * This is a custom extension to the MCP spec that includes availability status.
     * Returns a ListToolsResult DTO containing all registered tools.
     *
     * Note: The 'available' flag is a non-standard extension and is not currently implemented.
     *
     * @return \WP\McpSchema\Server\Tools\DTO\ListToolsResult Response with all tools.
     */
    public function list_all_tools(): ListToolsResult
    {
        // Return the standard tools list.
        return $this->list_tools();
    }
    /**
     * Handles the tools/list request.
     *
     * Returns a ListToolsResult DTO containing all registered tools.
     * Tool DTOs are protocol-only; internal adapter metadata is stored in McpTool instances and is never exposed
     * to MCP clients.
     *
     * @return \WP\McpSchema\Server\Tools\DTO\ListToolsResult Response with tools list.
     */
    public function list_tools(): ListToolsResult
    {
        $tools = array_values($this->mcp->get_tools());
        /**
         * Filters the list of tools before returning to the client.
         *
         * Use this filter to hide tools per user/role, add dynamic tools,
         * or reorder the tools list.
         *
         * @since 0.5.0
         *
         * @param array<\WP\McpSchema\Server\Tools\DTO\Tool> $tools  Array of Tool DTOs.
         * @param \WP\MCP\Core\McpServer                     $server The MCP server instance.
         */
        $tools = $this->validate_filtered_list(apply_filters('mcp_adapter_tools_list', $tools, $this->mcp), $tools, 'mcp_adapter_tools_list', $this->mcp->get_error_handler());
        return ListToolsResult::fromArray(array('tools' => $tools));
    }
    /**
     * Handles the tools/call request.
     *
     * Returns either a CallToolResult DTO (for success or tool execution errors)
     * or a JSONRPCErrorResponse DTO (for protocol errors like tool not found).
     *
     * The MCP spec distinguishes between:
     * 1. **Protocol errors** (tool not found, server error) → JSONRPCErrorResponse
     * 2. **Tool execution errors** (permission denied, runtime error) → CallToolResult with isError=true
     *
     * This distinction is critical for LLM self-correction - execution errors are
     * visible to the LLM, while protocol errors indicate infrastructure issues.
     *
     * @param array $params Request params.
     * @param string|int|null $request_id Optional. The request ID for JSON-RPC. Default 0.
     *
     * @return \WP\McpSchema\Server\Tools\DTO\CallToolResult|\WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse
     */
    public function call_tool(array $params, $request_id = 0)
    {
        // Extract parameters using helper method.
        $request_params = $this->extract_params($params);
        if (!isset($request_params['name'])) {
            return McpErrorFactory::missing_parameter($request_id, 'tool name');
        }
        if (isset($request_params['arguments']) && !is_array($request_params['arguments'])) {
            return McpErrorFactory::invalid_params($request_id, 'arguments must be an object');
        }
        try {
            $tool_name = trim((string) $request_params['name']);
            $args = $request_params['arguments'] ?? array();
            $mcp_tool = $this->mcp->get_mcp_tool($tool_name);
            if (!$mcp_tool) {
                $this->mcp->get_error_handler()->log('Tool not found', array('tool_name' => $tool_name), 'warning');
                return McpErrorFactory::tool_not_found($request_id, $tool_name);
            }
            $permission = $mcp_tool->check_permission($args);
            if (\true !== $permission) {
                $error_message = __('Permission denied', 'mcp-adapter');
                if (is_wp_error($permission)) {
                    $error_message = $permission->get_error_message();
                    $this->mcp->get_error_handler()->log('Tool permission check failed', array('tool_name' => $tool_name, 'error_code' => $permission->get_error_code(), 'error_message' => $permission->get_error_message(), 'error_data' => $permission->get_error_data(), 'failure_reason' => FailureReason::PERMISSION_CHECK_FAILED));
                }
                return $this->create_error_result($error_message);
            }
            /**
             * Filters tool arguments before execution, or short-circuits execution entirely.
             *
             * Return the (optionally modified) arguments array to proceed with execution,
             * or return a WP_Error to block execution and return an error to the client.
             *
             * @since 0.5.0
             *
             * @param array                        $args      The tool arguments.
             * @param string                       $tool_name The tool name being called.
             * @param \WP\MCP\Domain\Tools\McpTool $mcp_tool  The MCP tool instance.
             * @param \WP\MCP\Core\McpServer       $server    The MCP server instance.
             */
            $args = apply_filters('mcp_adapter_pre_tool_call', $args, $tool_name, $mcp_tool, $this->mcp);
            // Allow pre-filter to short-circuit execution by returning WP_Error.
            if (is_wp_error($args)) {
                return $this->create_error_result($args->get_error_message());
            }
            $result = $mcp_tool->execute($args);
            /**
             * Filters the tool execution result before response assembly.
             *
             * Use this filter for result transformation, PII redaction,
             * audit logging, or content enrichment.
             *
             * @since 0.5.0
             *
             * @param mixed|\WP_Error              $result    The raw execution result (may be WP_Error).
             * @param array                        $args      The tool arguments used.
             * @param string                       $tool_name The tool name that was called.
             * @param \WP\MCP\Domain\Tools\McpTool $mcp_tool  The MCP tool instance.
             * @param \WP\MCP\Core\McpServer       $server    The MCP server instance.
             */
            $result = apply_filters('mcp_adapter_tool_call_result', $result, $args, $tool_name, $mcp_tool, $this->mcp);
            if (is_wp_error($result)) {
                $this->mcp->get_error_handler()->log('Tool execution returned WP_Error', array('tool_name' => $tool_name, 'error_code' => $result->get_error_code(), 'error_message' => $result->get_error_message(), 'error_data' => $result->get_error_data()));
                return $this->create_error_result($result->get_error_message());
            }
            // Backward compatibility: treat `{ success: false, error: string }` as tool execution error.
            if (is_array($result) && array_key_exists('success', $result) && \false === $result['success'] && isset($result['error']) && is_string($result['error']) && '' !== trim($result['error'])) {
                return $this->create_error_result($result['error']);
            }
            // Successful tool execution - build CallToolResult DTO.
            // Handle embedded resource results (MCP ContentBlock type: "resource").
            // This allows tools to return text/blob resources using the MCP schema's EmbeddedResource content block.
            if (isset($result['type']) && 'resource' === $result['type']) {
                $resource_item = $result;
                if (isset($result['resource']) && is_array($result['resource'])) {
                    $resource_item = $result['resource'];
                }
                $uri = $resource_item['uri'] ?? null;
                $mime_type = $resource_item['mimeType'] ?? null;
                if (is_string($uri)) {
                    $uri = trim($uri);
                }
                // Only return an EmbeddedResource if we have a valid URI and some content.
                if (is_string($uri) && '' !== $uri) {
                    if (isset($resource_item['text']) && is_string($resource_item['text'])) {
                        return CallToolResult::fromArray(array('content' => array(ContentBlockHelper::embedded_text_resource($uri, $resource_item['text'], is_string($mime_type) ? $mime_type : null)), 'isError' => \false));
                    }
                    if (isset($resource_item['blob']) && is_string($resource_item['blob'])) {
                        return CallToolResult::fromArray(array('content' => array(ContentBlockHelper::embedded_blob_resource($uri, $resource_item['blob'], is_string($mime_type) ? $mime_type : null)), 'isError' => \false));
                    }
                }
            }
            // Handle image results.
            if (isset($result['type']) && 'image' === $result['type'] && isset($result['results'])) {
                $image_data = base64_encode($result['results']);
                // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
                $mime_type = $result['mimeType'] ?? self::DEFAULT_IMAGE_MIME_TYPE;
                return CallToolResult::fromArray(array('content' => array(ContentBlockHelper::image($image_data, $mime_type)), 'structuredContent' => null, 'isError' => \false));
            }
            // Standard result - JSON-encode for text content, include as structuredContent.
            $json_text = wp_json_encode($result);
            if (\false === $json_text) {
                $json_text = '{}';
            }
            return CallToolResult::fromArray(array('content' => array(ContentBlockHelper::text($json_text)), 'structuredContent' => $result, 'isError' => \false));
        } catch (\Throwable $exception) {
            $this->mcp->get_error_handler()->log('Error calling tool', array('tool' => $request_params['name'], 'exception' => $exception->getMessage()));
            return McpErrorFactory::internal_error($request_id, 'Failed to execute tool');
        }
    }
    /**
     * Create an error CallToolResult from a message string.
     *
     * @since 0.5.0
     *
     * @param string $message The error message.
     *
     * @return \WP\McpSchema\Server\Tools\DTO\CallToolResult
     */
    private function create_error_result(string $message): CallToolResult
    {
        return CallToolResult::fromArray(array('content' => array(ContentBlockHelper::text($message)), 'structuredContent' => null, 'isError' => \true));
    }
}
