<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Action;

/**
 * The ruler the guidance layer measures itself with.
 *
 * `read-logs-undo-guidance.md` §5.1 states its budgets in tokens, and there is no tokeniser in this
 * plugin — pulling one in for a size check would be a dependency bought for a rounding error. The
 * assumption, named here rather than buried at a call site: **one token ≈ four bytes of the encoded
 * JSON response**. That is the standard English-text ratio for BPE tokenisers and it errs on the
 * safe side for this payload, whose bulk is prose. It is deliberately measured on the *encoded*
 * response, not on the source strings, because the braces, keys and quotes an LLM pays for are part
 * of what it reads.
 *
 * Not exact, and not meant to be: it decides whether a response is comfortably inside a budget or
 * needs a truncation stage, and both answers survive a 20% error.
 */
final class TokenBudget
{
    private const CHARS_PER_TOKEN = 4;

    /**
     * Approximate size of $payload as the MCP client will see it.
     *
     * @param array<mixed> $payload
     */
    public static function estimate(array $payload): int
    {
        $json = \json_encode($payload, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES);

        return (int)\ceil(\strlen($json === false ? '' : $json) / self::CHARS_PER_TOKEN);
    }

    /**
     * @param array<mixed> $payload
     */
    public static function fits(array $payload, int $budget): bool
    {
        return self::estimate($payload) <= $budget;
    }
}
