<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Tools;

use Closure;
use Plugin\tnt_mcp\src\Mcp\Action\ActionValidator;
use Plugin\tnt_mcp\src\Mcp\Contracts\Tool;
use Plugin\tnt_mcp\src\Mcp\Mode\EndpointClass;
use RuntimeException;

/**
 * READ tool: check an `invoke_admin_action` payload without performing it (design round 4 §3).
 *
 * The pre-flight `dryRun` cannot be. A dry run invokes the real controller inside a rolled-back
 * transaction, which is stronger — and is refused for every entry whose side effects reach past the
 * database (D22), i.e. all of payment, because saving a payment method flushes the whole cache.
 * This tool touches nothing at all, so it works everywhere, and it catches the two failure classes
 * {@see \Plugin\tnt_mcp\src\Mcp\Action\ArgumentBinder} deliberately does not: an id that is not in
 * the enum's table, and a broken co-requirement.
 *
 * Reversible: it reads the catalog and SELECTs enum tables. Validating changes nothing.
 */
final class ValidateAdminAction implements Tool
{
    /**
     * @param Closure(): ActionValidator $validatorFactory must not parse the catalog or touch the DB
     *                                   until run() — `ScopeCatalog` builds every tool just to read
     *                                   its scope()
     */
    public function __construct(private readonly Closure $validatorFactory)
    {
    }

    public function name(): string
    {
        return 'validate_admin_action';
    }

    public function description(): string
    {
        // Hard limit: 140 characters. Tool descriptions are prompt, paid on every request.
        return 'Check an admin-action payload without performing it: shape, enum ids, '
            . 'co-required fields. Writes nothing. Use before invoke_admin_action.';
    }

    /** @return array<string,mixed> */
    public function inputSchema(): array
    {
        return [
            'type'       => 'object',
            'properties' => [
                'id'   => [
                    'type'        => 'string',
                    'description' => 'Action id as returned by list_admin_actions, '
                        . 'e.g. "shipping.method.save".',
                ],
                'args' => [
                    'type'        => 'object',
                    'description' => 'The exact arguments you would send to invoke_admin_action.',
                ],
            ],
            'required'             => ['id'],
            'additionalProperties' => false,
        ];
    }

    public function isWrite(): bool
    {
        return false;
    }

    public function scope(): string
    {
        return 'actions:read';
    }

    public function endpointClass(): EndpointClass
    {
        return EndpointClass::Reversible;
    }

    /**
     * @param array<string,mixed> $args
     * @return array<string,mixed>
     */
    public function run(array $args): array
    {
        $id = $args['id'] ?? null;
        if (!\is_string($id) || \trim($id) === '') {
            throw new RuntimeException('validate_admin_action: "id" is required');
        }
        $payload = $args['args'] ?? [];
        if (!\is_array($payload)) {
            throw new RuntimeException('validate_admin_action: "args" must be an object');
        }

        /** @var array<string,mixed> $payload */
        return ($this->validatorFactory)()->validate(\trim($id), $payload);
    }
}
