<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Backend;

use JTL\Alert\Alert;

/**
 * What one backend invocation produced — design `backend-action-invoker.md` §1.7.
 *
 * Three things are worth knowing before reading a field:
 *
 * 1. **A 3xx is success, not a failure.** `PaymentMethodsController` returns a `RedirectResponse`
 *    on its normal save path, and `GenericModelController::modelPRG()` redirects by design. A naive
 *    `status === 200` check would report every post/redirect/get screen as broken. {@see $redirect}
 *    carries the `Location` header when there is one.
 * 2. **`ok` is derived from the alert list**, via `alertTypeExists(Alert::TYPE_ERROR)` — the same
 *    signal core's own `IOController::saveShippingSurcharge()` uses to decide it failed. It is NOT
 *    derived from the status code. The one exception is an `adminIo` call that RETURNS a
 *    `JTL\IO\IOError` (`includes/src/IO/IOError.php:13`): several IO functions report failure by
 *    return value and never alert at all, so
 *    {@see BackendActionInvoker::invokeAdminIo()} forces `ok: false` and appends the error to
 *    {@see $alerts} as a synthetic `ioError` entry — the shape below is unchanged, and `$data`
 *    additionally carries the serialised `{error: {message, code, errors, additional}}`.
 * 3. **`ok === true` with an empty `$alerts` proves nothing.** Several controllers neither alert nor
 *    redirect on success, so "no error was reported" is all this can honestly mean. That is exactly
 *    why a catalog entry declares a `verify:` read-back: the effect is proven by reading it back,
 *    not by trusting this flag. Do not let a caller treat `ok` as confirmation of a write.
 */
final readonly class InvocationResult
{
    /**
     * @param list<AlertView>     $alerts
     * @param array<string,mixed> $data             decoded return value; `adminIo` only, `[]` for form posts
     * @param string|null         $renderedTemplate the template that WOULD have been rendered
     */
    public function __construct(
        public bool $ok,
        public array $alerts,
        public ?string $redirect,
        public int $status,
        public array $data,
        public ?string $renderedTemplate,
        public float $durationMs,
    ) {
    }

    /** @return list<AlertView> */
    public function errors(): array
    {
        return \array_values(\array_filter(
            $this->alerts,
            static fn (AlertView $a): bool => $a->type === Alert::TYPE_ERROR || $a->type === Alert::TYPE_DANGER,
        ));
    }

    /** @return array<string,mixed> */
    public function toArray(): array
    {
        return [
            'ok'               => $this->ok,
            'status'           => $this->status,
            'redirect'         => $this->redirect,
            'alerts'           => \array_map(
                static fn (AlertView $a): array => ['type' => $a->type, 'key' => $a->key, 'message' => $a->message],
                $this->alerts,
            ),
            'data'             => $this->data,
            'renderedTemplate' => $this->renderedTemplate,
            'durationMs'       => \round($this->durationMs, 1),
        ];
    }
}
