<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Contracts;

/**
 * Opt-in: "here is the call that checks this call without performing it."
 *
 * It exists for {@see \Plugin\tnt_mcp\src\Mcp\Batch\BatchRunner}'s `dryRun`, which has to answer
 * "is this whole plan good?" for a list of tools it knows nothing about. Without a declaration from
 * the tool itself, the batch would need a hard-coded table of "save_snippet is checked by
 * validate_snippet" — a table living in tnt_mcp, describing tools that ship in another plugin, and
 * going stale the day either side is renamed. The seam between the two plugins is
 * {@see ToolProvider}, and it stays that way: a tool that can be checked says so.
 *
 * A **separate, narrow interface** rather than a change to {@see Tool}, for the same reason
 * {@see PerCallEndpointClass} is one: dozens of shipped tools implement `Tool`, and most of them are
 * reads that no one would ever dry-run.
 *
 * ## What the returned call must be
 *
 * A call that WRITES NOTHING. It is dispatched through the ordinary
 * {@see \Plugin\tnt_mcp\src\Mcp\Server::dispatch()}, so it is scope- and mode-checked and audited
 * like any other; a "check" that quietly performed the write would turn `dryRun: true` into the most
 * expensive lie on the surface. It may be the same tool with a `dryRun` flag added (the workflow
 * write tools) or a different, purely validating tool (`save_snippet` → `validate_snippet`).
 *
 * ## How its answer is read
 *
 * The check's result must say whether the thing PASSED in one of `ok`, `valid` or `accepted` — the
 * three the tools on this surface already use, and the three
 * {@see \Plugin\tnt_mcp\src\Mcp\Batch\BatchRunner} reads. A check that ran cleanly over invalid
 * input answers `ok: true, valid: false`, and the batch reports that entry as failing: "the
 * validator worked" is not "the plan is good", and conflating the two is how a batch of broken
 * snippets dry-runs green.
 *
 * `null` means "these arguments cannot be checked without doing the thing" — for example an admin
 * action whose catalogue entry declares `dryRunnable: false`. That is not a failure: the batch
 * reports the entry as unchecked and says so, which is honest, where inventing a check would not be.
 */
interface CheckableTool
{
    /**
     * @param array<string,mixed> $args the arguments the real call would be made with
     *
     * @return array{tool:string,arguments:array<string,mixed>}|null the write-free call that decides
     *         whether the real one would succeed, or null when no such call exists
     */
    public function checkCall(array $args): ?array;
}
