<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\AdminBundle\Routing;

use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Uid\Uuid;

/**
 * Makes base58 the default UUID format when GENERATING admin URLs.
 *
 * Admin routes were migrated to base58 UUIDs under their canonical names
 * (the RFC4122 form survives on parallel `*_legacy` routes). But
 * Uuid::__toString() is RFC4122, so `path('admin_…', {uuid: entity.uuid})`
 * would otherwise default to RFC4122 and fail the base58 requirement —
 * forcing every single call site to remember `->toBase58()`. This decorator
 * normalises UUID-shaped values for the `uuid` / `uuid1` / `uuid2`
 * parameters of admin (non-legacy) routes to base58 at generation time, so a
 * plain `{uuid: entity.uuid}` just works and a forgotten `.toBase58` can no
 * longer 500. Every other route — `*_legacy`, cms, api, file_show — passes
 * through untouched, as does every non-generate router method.
 */
#[AsDecorator('router')]
final class Base58AdminRouter implements RouterInterface, RequestMatcherInterface, WarmableInterface
{
    /** @var list<string> */
    private const UUID_PARAMS = ['uuid', 'uuid1', 'uuid2'];

    public function __construct(private readonly RouterInterface $inner)
    {
    }

    public function generate(string $name, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
    {
        if (str_starts_with($name, 'admin_') && !str_ends_with($name, '_legacy')) {
            foreach (self::UUID_PARAMS as $key) {
                if (isset($parameters[$key])) {
                    $parameters[$key] = self::toBase58($parameters[$key]);
                }
            }
        }

        return $this->inner->generate($name, $parameters, $referenceType);
    }

    public function match(string $pathinfo): array
    {
        return $this->inner->match($pathinfo);
    }

    public function matchRequest(Request $request): array
    {
        if ($this->inner instanceof RequestMatcherInterface) {
            return $this->inner->matchRequest($request);
        }

        return $this->inner->match($request->getPathInfo());
    }

    public function getRouteCollection(): RouteCollection
    {
        return $this->inner->getRouteCollection();
    }

    public function setContext(RequestContext $context): void
    {
        $this->inner->setContext($context);
    }

    public function getContext(): RequestContext
    {
        return $this->inner->getContext();
    }

    public function warmUp(string $cacheDir, ?string $buildDir = null): array
    {
        return $this->inner instanceof WarmableInterface ? $this->inner->warmUp($cacheDir, $buildDir) : [];
    }

    private static function toBase58(mixed $value): mixed
    {
        if ($value instanceof Uuid) {
            return $value->toBase58();
        }

        // Only an RFC4122 string (36 chars) is rewritten; anything else —
        // an already-base58 string, an integer id (legacy consents), null —
        // is left exactly as given.
        if (\is_string($value) && 36 === \strlen($value)) {
            try {
                return Uuid::fromString($value)->toBase58();
            } catch (\InvalidArgumentException) {
                return $value;
            }
        }

        return $value;
    }
}
