<?php

/**
 * RegisterAbilityAsMcpTool class for converting WordPress abilities to MCP tools.
 *
 * @package McpAdapter
 */
declare (strict_types=1);
namespace PYS_PRO_GLOBAL\WP\MCP\Domain\Tools;

use PYS_PRO_GLOBAL\WP\MCP\Domain\Utils\McpAnnotationMapper;
use PYS_PRO_GLOBAL\WP\MCP\Domain\Utils\McpNameSanitizer;
use PYS_PRO_GLOBAL\WP\MCP\Domain\Utils\McpValidator;
use PYS_PRO_GLOBAL\WP\MCP\Domain\Utils\SchemaTransformer;
use PYS_PRO_GLOBAL\WP\McpSchema\Server\Tools\DTO\Tool as ToolDto;
use WP_Error;
/**
 * RegisterAbilityAsMcpTool class.
 *
 * This class registers a WordPress ability as an MCP tool.
 *
 * @internal
 *
 * @package McpAdapter
 */
class RegisterAbilityAsMcpTool
{
    /**
     * The WordPress ability instance.
     *
     * @var \WP_Ability
     */
    private \WP_Ability $ability;
    /**
     * Constructor.
     *
     * @param \WP_Ability $ability The ability.
     */
    private function __construct(\WP_Ability $ability)
    {
        $this->ability = $ability;
    }
    /**
     * Build a clean Tool DTO and adapter metadata for internal wiring.
     *
     * This method returns a protocol-only Tool DTO and provides the adapter metadata
     * separately. This keeps the DTO stable across MCP spec changes and avoids coupling internal execution
     * wiring to protocol surfaces.
     *
     * @param \WP_Ability $ability The ability.
     *
     * @return array{tool: \WP\McpSchema\Server\Tools\DTO\Tool, adapter_meta: array<string, mixed>}|\WP_Error
     * @since 0.5.0
     *
     */
    public static function build(\WP_Ability $ability)
    {
        $tool = new self($ability);
        $data = $tool->build_tool_data();
        if (is_wp_error($data)) {
            return $data;
        }
        try {
            $tool_dto = ToolDto::fromArray($data['tool_data']);
        } catch (\Throwable $e) {
            return new WP_Error('mcp_tool_dto_creation_failed', sprintf(
                /* translators: %s: error message */
                __('Failed to create Tool DTO for ability %1$s: %2$s', 'mcp-adapter'),
                $ability->get_name(),
                $e->getMessage()
            ), array('exception' => $e));
        }
        // Optional deep validation if enabled.
        $mcp_validation_enabled = apply_filters('mcp_adapter_validation_enabled', \false);
        if ($mcp_validation_enabled) {
            $validation_result = McpToolValidator::validate_tool_dto($tool_dto);
            if (is_wp_error($validation_result)) {
                return $validation_result;
            }
        }
        return array('tool' => $tool_dto, 'adapter_meta' => $data['adapter_meta']);
    }
    /**
     * Build Tool DTO data and adapter metadata.
     *
     * @return array{tool_data: array<string, mixed>, adapter_meta: array<string, mixed>}|\WP_Error
     * @since 0.5.0
     *
     */
    private function build_tool_data()
    {
        // Resolve tool name first (can fail).
        $tool_name = $this->resolve_tool_name();
        if (is_wp_error($tool_name)) {
            return $tool_name;
        }
        // Transform input schema to MCP-compatible object format.
        $input_transform = SchemaTransformer::transform_to_object_schema($this->ability->get_input_schema());
        $tool_data = array('name' => $tool_name, 'description' => trim($this->ability->get_description()), 'inputSchema' => $input_transform['schema']);
        // Add optional title from ability label.
        $label = $this->ability->get_label();
        $label = trim($label);
        if (!empty($label)) {
            $tool_data['title'] = $label;
        }
        // Add optional output schema, transformed to object format if needed.
        $output_schema = $this->ability->get_output_schema();
        $output_transform = null;
        if (!empty($output_schema)) {
            $output_transform = SchemaTransformer::transform_to_object_schema($output_schema, 'result');
            $tool_data['outputSchema'] = $output_transform['schema'];
        }
        // Map annotations from ability meta to MCP format using unified mapper.
        $ability_meta = $this->ability->get_meta();
        if (!empty($ability_meta['annotations']) && is_array($ability_meta['annotations'])) {
            $mcp_annotations = McpAnnotationMapper::map($ability_meta['annotations'], 'tool');
            if (!empty($mcp_annotations)) {
                $tool_data['annotations'] = $mcp_annotations;
            }
        }
        // Set annotations.title from label if annotations exist but don't have a title.
        if (!empty($label) && isset($tool_data['annotations']) && !isset($tool_data['annotations']['title'])) {
            $tool_data['annotations']['title'] = $label;
        }
        // Store transformation metadata as internal metadata (stripped before responding to clients).
        // Only record keys when semantically meaningful to keep metadata minimal and accurate.
        $adapter_meta = array('ability' => $this->ability->get_name());
        // Only record input transformation metadata when a wrapper was actually applied.
        if (!empty($input_transform['was_transformed'])) {
            $adapter_meta['input_schema_transformed'] = \true;
            $adapter_meta['input_schema_wrapper'] = $input_transform['wrapper_property'];
        }
        // Only record output transformation metadata when outputSchema exists.
        // Record wrapper only when transformation actually occurred.
        if (null !== $output_transform && !empty($output_transform['was_transformed'])) {
            $adapter_meta['output_schema_transformed'] = \true;
            $adapter_meta['output_schema_wrapper'] = $output_transform['wrapper_property'];
        }
        // Map icons from ability.meta.mcp.icons if present.
        $mcp_meta = $ability_meta['mcp'] ?? array();
        if (!empty($mcp_meta['icons']) && is_array($mcp_meta['icons'])) {
            $icons_result = McpValidator::validate_icons_array($mcp_meta['icons']);
            if (!empty($icons_result['valid'])) {
                $tool_data['icons'] = $icons_result['valid'];
            }
        }
        // Build Tool `_meta`:
        // - Preserve user-provided `_meta` from ability.meta.mcp._meta.
        // - Adapter metadata is NEVER included in protocol DTO meta; it is returned separately in adapter_meta.
        $tool_meta = array();
        if (!empty($mcp_meta['_meta']) && is_array($mcp_meta['_meta'])) {
            $tool_meta = $mcp_meta['_meta'];
        }
        if (!empty($tool_meta)) {
            $tool_data['_meta'] = $tool_meta;
        }
        return array('tool_data' => $tool_data, 'adapter_meta' => $adapter_meta);
    }
    /**
     * Resolve the MCP tool name from ability.
     *
     * Sanitizes the ability name to MCP-valid format, applies filter, and validates result.
     *
     * @return string|\WP_Error Valid tool name or error.
     * @since 0.5.0
     *
     */
    private function resolve_tool_name()
    {
        // Sanitize ability name to MCP-valid format.
        $sanitized_name = McpNameSanitizer::sanitize_name($this->ability->get_name());
        if (is_wp_error($sanitized_name)) {
            return $sanitized_name;
        }
        /**
         * Filters the MCP tool name derived from an ability.
         *
         * @since 0.5.0
         *
         * @param string $name The sanitized tool name.
         * @param \WP_Ability $ability The source ability instance.
         */
        $filtered_name = apply_filters('mcp_adapter_tool_name', $sanitized_name, $this->ability);
        // Validate post-filter (in case filter broke it).
        if (!is_string($filtered_name) || !McpValidator::validate_name($filtered_name)) {
            return new WP_Error('mcp_tool_name_filter_invalid', sprintf(
                /* translators: %s: invalid tool name returned by filter */
                __('Filter returned invalid MCP tool name: %s', 'mcp-adapter'),
                is_string($filtered_name) ? $filtered_name : gettype($filtered_name)
            ));
        }
        return $filtered_name;
    }
}
