<?php

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

use PYS_PRO_GLOBAL\WP\MCP\Core\McpServer;
use PYS_PRO_GLOBAL\WP\MCP\Handlers\HandlerHelperTrait;
use PYS_PRO_GLOBAL\WP\MCP\Infrastructure\ErrorHandling\McpErrorFactory;
use PYS_PRO_GLOBAL\WP\McpSchema\Server\Prompts\DTO\GetPromptResult;
use PYS_PRO_GLOBAL\WP\McpSchema\Server\Prompts\DTO\ListPromptsResult;
use PYS_PRO_GLOBAL\WP\McpSchema\Server\Prompts\DTO\Prompt as PromptDto;
use PYS_PRO_GLOBAL\WP\McpSchema\Server\Prompts\DTO\PromptMessage;
/**
 * Handles prompts-related MCP methods.
 *
 * @since 0.5.0
 */
class PromptsHandler
{
    use HandlerHelperTrait;
    /**
     * Valid content types from ContentBlockFactory.
     *
     * @var list<string>
     */
    private static array $valid_content_types = array('text', 'image', 'audio', 'resource_link', 'resource');
    /**
     * Valid role values for PromptMessage.
     *
     * @var list<string>
     */
    private static array $valid_roles = array('user', 'assistant');
    /**
     * Default role for messages when not specified.
     *
     * @var string
     */
    private static string $default_role = 'user';
    /**
     * 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 prompts/list request.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\ListPromptsResult Response with prompts list DTO.
     */
    public function list_prompts(): ListPromptsResult
    {
        $prompts = array_values($this->mcp->get_prompts());
        /**
         * Filters the list of prompts before returning to the client.
         *
         * Use this filter to filter prompts by context, add dynamic prompts,
         * or reorder the prompts list.
         *
         * @since 0.5.0
         *
         * @param array<\WP\McpSchema\Server\Prompts\DTO\Prompt> $prompts Array of Prompt DTOs.
         * @param \WP\MCP\Core\McpServer                         $server  The MCP server instance.
         */
        $prompts = $this->validate_filtered_list(apply_filters('mcp_adapter_prompts_list', $prompts, $this->mcp), $prompts, 'mcp_adapter_prompts_list', $this->mcp->get_error_handler());
        return ListPromptsResult::fromArray(array('prompts' => $prompts));
    }
    /**
     * Handles the prompts/get request.
     *
     * @param array           $params Request parameters.
     * @param string|int|null $request_id Optional. The request ID for JSON-RPC. Default 0.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\GetPromptResult|\WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse Response with prompt execution results or error.
     */
    public function get_prompt(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, 'name');
        }
        $prompt_name = (string) $request_params['name'];
        $prompt_name = trim($prompt_name);
        if (isset($request_params['arguments']) && !is_array($request_params['arguments'])) {
            return McpErrorFactory::invalid_params($request_id, 'arguments must be an object');
        }
        $mcp_prompt = $this->mcp->get_mcp_prompt($prompt_name);
        if (!$mcp_prompt) {
            return McpErrorFactory::prompt_not_found($request_id, $prompt_name);
        }
        /** @var \WP\McpSchema\Server\Prompts\DTO\Prompt $prompt */
        $prompt = $mcp_prompt->get_protocol_dto();
        // Get the arguments for the prompt.
        $arguments = $request_params['arguments'] ?? array();
        try {
            $permission = $mcp_prompt->check_permission($arguments);
            if (\true !== $permission) {
                $error_message = 'Access denied for prompt: ' . $prompt_name;
                if (is_wp_error($permission)) {
                    $error_message = $permission->get_error_message();
                }
                return McpErrorFactory::permission_denied($request_id, $error_message);
            }
            /**
             * Filters prompt 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                              $arguments   The prompt arguments.
             * @param string                             $prompt_name The prompt name being retrieved.
             * @param \WP\MCP\Domain\Prompts\McpPrompt   $mcp_prompt  The MCP prompt instance.
             * @param \WP\MCP\Core\McpServer             $server      The MCP server instance.
             */
            $arguments = apply_filters('mcp_adapter_pre_prompt_get', $arguments, $prompt_name, $mcp_prompt, $this->mcp);
            // Allow pre-filter to short-circuit execution by returning WP_Error.
            if (is_wp_error($arguments)) {
                return McpErrorFactory::internal_error($request_id, $arguments->get_error_message());
            }
            $result = $mcp_prompt->execute($arguments);
            /**
             * Filters the prompt execution result before normalization.
             *
             * Use this filter for message transformation, context injection,
             * content enrichment, or audit logging.
             *
             * @since 0.5.0
             *
             * @param mixed|\WP_Error                    $result      The raw execution result (may be WP_Error).
             * @param array                              $arguments   The prompt arguments used.
             * @param string                             $prompt_name The prompt name.
             * @param \WP\MCP\Domain\Prompts\McpPrompt   $mcp_prompt  The MCP prompt instance.
             * @param \WP\MCP\Core\McpServer             $server      The MCP server instance.
             */
            $result = apply_filters('mcp_adapter_prompt_get_result', $result, $arguments, $prompt_name, $mcp_prompt, $this->mcp);
            if (is_wp_error($result)) {
                $this->mcp->get_error_handler()->log('Prompt execution returned WP_Error', array('prompt_name' => $prompt_name, 'error_code' => $result->get_error_code(), 'error_message' => $result->get_error_message()));
                return McpErrorFactory::internal_error($request_id, $result->get_error_message());
            }
            return $this->normalize_result_to_dto($result, $prompt, $prompt_name);
        } catch (\Throwable $e) {
            $this->mcp->get_error_handler()->log('Prompt execution failed', array('prompt_name' => $prompt_name, 'arguments' => $arguments, 'error' => $e->getMessage()));
            return McpErrorFactory::internal_error($request_id, 'Prompt execution failed');
        }
    }
    // =========================================================================
    // Result Normalization (Tiered Convenience Shortcuts)
    // =========================================================================
    /**
     * Normalize and convert prompt execution result to GetPromptResult DTO.
     *
     * Supports tiered return formats:
     * - Tier 1: Full MCP format with 'messages' array
     * - Tier 2: Simple 'text' shorthand
     * - Tier 3: Single message with 'role' and 'content'
     * - Tier 4: Multi-text with 'texts' array
     * - Tier 5: Fallback JSON encoding for arbitrary data
     *
     * @since 0.5.0
     *
     * @param array                                 $result      Raw result from prompt execution.
     * @param \WP\McpSchema\Server\Prompts\DTO\Prompt $prompt      The prompt DTO for description fallback.
     * @param string                                $prompt_name Prompt name for logging.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\GetPromptResult
     */
    private function normalize_result_to_dto(array $result, PromptDto $prompt, string $prompt_name): GetPromptResult
    {
        // Tier 1: Full MCP format with 'messages' array.
        if (isset($result['messages']) && is_array($result['messages'])) {
            return $this->normalize_tier1_messages($result, $prompt, $prompt_name);
        }
        // Tier 2: Simple 'text' shorthand.
        if (isset($result['text']) && is_string($result['text'])) {
            return $this->normalize_tier2_text($result, $prompt);
        }
        // Tier 3: Single message with 'role' key.
        if (isset($result['role']) && isset($result['content'])) {
            return $this->normalize_tier3_single_message($result, $prompt, $prompt_name);
        }
        // Tier 4: Multi-text with 'texts' array.
        if (isset($result['texts']) && is_array($result['texts'])) {
            return $this->normalize_tier4_texts($result, $prompt);
        }
        // Tier 5: Fallback - JSON encode arbitrary data.
        return $this->normalize_tier5_fallback($result, $prompt, $prompt_name);
    }
    /**
     * Tier 1: Full MCP-compliant format with 'messages' array.
     *
     * @since 0.5.0
     *
     * @param array                                 $result      Raw result with 'messages' key.
     * @param \WP\McpSchema\Server\Prompts\DTO\Prompt $prompt      The prompt DTO.
     * @param string                                $prompt_name Prompt name for logging.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\GetPromptResult
     */
    private function normalize_tier1_messages(array $result, PromptDto $prompt, string $prompt_name): GetPromptResult
    {
        $message_dtos = array();
        foreach ($result['messages'] as $index => $message) {
            if (!is_array($message)) {
                $this->mcp->get_error_handler()->log('Invalid message structure in prompt result, skipping', array('prompt_name' => $prompt_name, 'message_index' => $index, 'message_type' => gettype($message)), 'warning');
                continue;
            }
            $message_dtos[] = $this->validate_and_create_message($message, $prompt_name);
        }
        // Ensure we have at least one message.
        if (empty($message_dtos)) {
            $message_dtos[] = PromptMessage::fromArray(array('role' => self::$default_role, 'content' => array('type' => 'text', 'text' => '(No messages returned)')));
        }
        return GetPromptResult::fromArray(array('messages' => $message_dtos, 'description' => $result['description'] ?? $prompt->getDescription()));
    }
    /**
     * Tier 2: Simple 'text' shorthand.
     *
     * Creates a single user message with text content.
     *
     * @since 0.5.0
     *
     * @param array                                 $result Raw result with 'text' key.
     * @param \WP\McpSchema\Server\Prompts\DTO\Prompt $prompt The prompt DTO.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\GetPromptResult
     */
    private function normalize_tier2_text(array $result, PromptDto $prompt): GetPromptResult
    {
        $content = array('type' => 'text', 'text' => (string) $result['text']);
        // Support optional annotations on the text.
        if (isset($result['annotations']) && is_array($result['annotations'])) {
            $content['annotations'] = $result['annotations'];
        }
        $message_dto = PromptMessage::fromArray(array('role' => self::$default_role, 'content' => $content));
        return GetPromptResult::fromArray(array('messages' => array($message_dto), 'description' => $result['description'] ?? $prompt->getDescription()));
    }
    /**
     * Tier 3: Single message with 'role' and 'content'.
     *
     * @since 0.5.0
     *
     * @param array                                 $result      Raw result with 'role' and 'content' keys.
     * @param \WP\McpSchema\Server\Prompts\DTO\Prompt $prompt      The prompt DTO.
     * @param string                                $prompt_name Prompt name for logging.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\GetPromptResult
     */
    private function normalize_tier3_single_message(array $result, PromptDto $prompt, string $prompt_name): GetPromptResult
    {
        $message_dto = $this->validate_and_create_message($result, $prompt_name);
        return GetPromptResult::fromArray(array('messages' => array($message_dto), 'description' => $result['description'] ?? $prompt->getDescription()));
    }
    /**
     * Tier 4: Multi-text with 'texts' array.
     *
     * Creates multiple messages with the same role.
     *
     * @since 0.5.0
     *
     * @param array                                 $result Raw result with 'texts' key.
     * @param \WP\McpSchema\Server\Prompts\DTO\Prompt $prompt The prompt DTO.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\GetPromptResult
     */
    private function normalize_tier4_texts(array $result, PromptDto $prompt): GetPromptResult
    {
        $role = $this->validate_role($result['role'] ?? self::$default_role, '');
        $message_dtos = array();
        foreach ($result['texts'] as $text) {
            if (!is_string($text)) {
                continue;
            }
            $message_dtos[] = PromptMessage::fromArray(array('role' => $role, 'content' => array('type' => 'text', 'text' => $text)));
        }
        // Ensure we have at least one message.
        if (empty($message_dtos)) {
            $message_dtos[] = PromptMessage::fromArray(array('role' => $role, 'content' => array('type' => 'text', 'text' => '(No texts provided)')));
        }
        return GetPromptResult::fromArray(array('messages' => $message_dtos, 'description' => $result['description'] ?? $prompt->getDescription()));
    }
    /**
     * Tier 5: Fallback - JSON encode arbitrary data.
     *
     * Used when no other tier matches. Logs an observability event.
     *
     * @since 0.5.0
     *
     * @param array                                 $result      Raw result (arbitrary structure).
     * @param \WP\McpSchema\Server\Prompts\DTO\Prompt $prompt      The prompt DTO.
     * @param string                                $prompt_name Prompt name for logging.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\GetPromptResult
     */
    private function normalize_tier5_fallback(array $result, PromptDto $prompt, string $prompt_name): GetPromptResult
    {
        // Log observability event for fallback normalization.
        $this->mcp->get_observability_handler()->record_event('prompt_result_fallback_normalization', array('prompt_name' => $prompt_name, 'result_keys' => array_keys($result)));
        $json_content = wp_json_encode($result, \JSON_PRETTY_PRINT);
        if (\false === $json_content) {
            $json_content = '{}';
        }
        $message_dto = PromptMessage::fromArray(array('role' => self::$default_role, 'content' => array('type' => 'text', 'text' => $json_content)));
        return GetPromptResult::fromArray(array('messages' => array($message_dto), 'description' => $prompt->getDescription()));
    }
    // =========================================================================
    // Validation Helpers
    // =========================================================================
    /**
     * Validate message structure and create PromptMessage DTO.
     *
     * Validates role and content type, applying defaults where needed.
     *
     * @since 0.5.0
     *
     * @param array  $message     Raw message array.
     * @param string $prompt_name Prompt name for logging.
     *
     * @return \WP\McpSchema\Server\Prompts\DTO\PromptMessage
     */
    private function validate_and_create_message(array $message, string $prompt_name): PromptMessage
    {
        // Validate and normalize role.
        $role = $this->validate_role($message['role'] ?? self::$default_role, $prompt_name);
        // Validate and normalize content.
        $content = $message['content'] ?? array();
        if (!is_array($content)) {
            // If content is a string, wrap it as text.
            $content = array('type' => 'text', 'text' => is_string($content) ? $content : (string) $content);
        }
        $content = $this->validate_content_type($content, $prompt_name);
        return PromptMessage::fromArray(array('role' => $role, 'content' => $content));
    }
    /**
     * Validate content type against ContentBlockFactory registry.
     *
     * @since 0.5.0
     *
     * @param array  $content     Content array with 'type' key.
     * @param string $prompt_name Prompt name for logging.
     *
     * @return array Validated content array (may be modified if invalid type).
     */
    private function validate_content_type(array $content, string $prompt_name): array
    {
        $type = $content['type'] ?? null;
        // Check if type is missing.
        if (null === $type || '' === $type) {
            $this->mcp->get_error_handler()->log('Missing content type in prompt result, defaulting to text', array('prompt_name' => $prompt_name), 'warning');
            $text = isset($content['text']) ? (string) $content['text'] : wp_json_encode($content, \JSON_PRETTY_PRINT);
            return array('type' => 'text', 'text' => \false === $text ? '{}' : $text);
        }
        // Check if type is valid.
        if (!in_array($type, self::$valid_content_types, \true)) {
            $this->mcp->get_error_handler()->log('Invalid content type in prompt result, converting to text', array('prompt_name' => $prompt_name, 'invalid_type' => $type, 'valid_types' => self::$valid_content_types), 'warning');
            // Convert the entire content to a text representation.
            $json_content = wp_json_encode($content, \JSON_PRETTY_PRINT);
            if (\false === $json_content) {
                $json_content = '{}';
            }
            return array('type' => 'text', 'text' => $json_content);
        }
        // Type is valid, return content as-is (preserves annotations).
        return $content;
    }
    /**
     * Validate role value and apply default if invalid.
     *
     * @since 0.5.0
     *
     * @param string $role        Role value to validate.
     * @param string $prompt_name Prompt name for logging (empty to skip logging).
     *
     * @return string Valid role value.
     */
    private function validate_role(string $role, string $prompt_name): string
    {
        if (in_array($role, self::$valid_roles, \true)) {
            return $role;
        }
        if ('' !== $prompt_name) {
            $this->mcp->get_error_handler()->log('Invalid role in prompt message, defaulting to user', array('prompt_name' => $prompt_name, 'invalid_role' => $role, 'valid_roles' => self::$valid_roles), 'warning');
        }
        return self::$default_role;
    }
}
