<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Action;

use RuntimeException;

/**
 * Carries every violation found in one {@see ArgumentBinder::bind()}, not the first.
 *
 * Same discipline as {@see CatalogValidationException}, for a sharper reason: the caller here is an
 * LLM, and one error per attempt means five attempts to learn five field mistakes — five model
 * round trips, five lots of the user's tokens, and five chances to give up half way and invent a
 * different (wrong) call instead.
 */
final class ArgumentBindingException extends RuntimeException
{
    /**
     * @param list<ArgumentBindingError> $errors
     */
    private function __construct(
        public readonly string $actionId,
        private readonly array $errors,
        string $message,
    ) {
        parent::__construct($message);
    }

    /**
     * @param list<ArgumentBindingError> $errors
     */
    public static function fromErrors(string $actionId, array $errors): self
    {
        $lines = \array_map(
            static fn (ArgumentBindingError $error): string => $error->describe(),
            $errors,
        );

        return new self(
            $actionId,
            $errors,
            \sprintf(
                "Arguments for \"%s\" are invalid (%d error(s)):\n%s",
                $actionId,
                \count($errors),
                \implode("\n", $lines),
            ),
        );
    }

    /** @return list<ArgumentBindingError> */
    public function errors(): array
    {
        return $this->errors;
    }

    /** @return list<string> the offending argument names, in report order, deduplicated */
    public function fields(): array
    {
        $fields = [];
        foreach ($this->errors as $error) {
            if (!\in_array($error->field, $fields, true)) {
                $fields[] = $error->field;
            }
        }

        return $fields;
    }
}
