<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Analytics;

use RuntimeException;

/**
 * `tbestellung.cStatus` is a bare number, and -1/1/2/3/4/5 in an MCP response is a number the model
 * has to guess at. Every analytics tool speaks names in both directions instead.
 *
 * The codes are the shop's own `BESTELLUNG_STATUS_*` constants (`includes/defines_inc.php:98-103`).
 * They are duplicated here rather than referenced because this class is unit-tested outside a booted
 * shop; {@see assertMatchesShop()} is the drift guard, and the integration suite calls it.
 */
final class OrderStatus
{
    public const CANCELLED         = 'cancelled';
    public const OPEN              = 'open';
    public const IN_PROGRESS       = 'in_progress';
    public const PAID              = 'paid';
    public const SHIPPED           = 'shipped';
    public const PARTIALLY_SHIPPED = 'partially_shipped';

    /** name => the shop's numeric cStatus */
    private const MAP = [
        self::CANCELLED         => -1,
        self::OPEN              => 1,
        self::IN_PROGRESS       => 2,
        self::PAID              => 3,
        self::SHIPPED           => 4,
        self::PARTIALLY_SHIPPED => 5,
    ];

    /** The shop constants these names stand for, checked by {@see assertMatchesShop()}. */
    private const CONSTANTS = [
        self::CANCELLED         => 'BESTELLUNG_STATUS_STORNO',
        self::OPEN              => 'BESTELLUNG_STATUS_OFFEN',
        self::IN_PROGRESS       => 'BESTELLUNG_STATUS_IN_BEARBEITUNG',
        self::PAID              => 'BESTELLUNG_STATUS_BEZAHLT',
        self::SHIPPED           => 'BESTELLUNG_STATUS_VERSANDT',
        self::PARTIALLY_SHIPPED => 'BESTELLUNG_STATUS_TEILVERSANDT',
    ];

    /** @return list<string> */
    public static function names(): array
    {
        return \array_keys(self::MAP);
    }

    /** An unknown code is reported as `status-<code>`, never dropped and never guessed. */
    public static function name(int|string|null $code): string
    {
        if ($code === null || $code === '') {
            return 'unknown';
        }
        $name = \array_search((int)$code, self::MAP, true);

        return \is_string($name) ? $name : 'status-' . (int)$code;
    }

    /**
     * Accepts one name or a list of them; `$raw` stays `mixed` because it arrives straight off the
     * wire and the type check below is the validation, not a formality.
     *
     * @return list<int> empty means "no status filter", not "no statuses"
     */
    public static function codesFromArg(string $tool, mixed $raw): array
    {
        if ($raw === null) {
            return [];
        }
        $values = \is_string($raw) ? [$raw] : $raw;
        if (!\is_array($values)) {
            throw new RuntimeException($tool . ': "status" must be a string or an array of strings.');
        }

        $codes = [];
        foreach ($values as $value) {
            if (!\is_string($value) || !isset(self::MAP[\mb_convert_case(\trim($value), \MB_CASE_LOWER)])) {
                throw new RuntimeException(\sprintf(
                    '%s: "status" must contain only: %s. Got: %s',
                    $tool,
                    \implode(', ', self::names()),
                    \is_string($value) ? '"' . \mb_substr($value, 0, 30) . '"' : \get_debug_type($value)
                ));
            }
            $codes[] = self::MAP[\mb_convert_case(\trim($value), \MB_CASE_LOWER)];
        }

        return \array_values(\array_unique($codes));
    }

    /**
     * Drift guard for D25's "declared evidence must still exist in the installed shop". Called from
     * the integration suite, where the shop's constants are actually defined.
     *
     * @return list<string> the mismatches; empty means the map still matches the shop
     */
    public static function assertMatchesShop(): array
    {
        $problems = [];
        foreach (self::CONSTANTS as $name => $constant) {
            if (!\defined($constant)) {
                $problems[] = $constant . ' is not defined by the shop.';
                continue;
            }
            if (\constant($constant) !== self::MAP[$name]) {
                $problems[] = \sprintf(
                    '%s is %s in the shop but %d here.',
                    $constant,
                    \var_export(\constant($constant), true),
                    self::MAP[$name]
                );
            }
        }

        return $problems;
    }
}
