<?php

declare(strict_types=1);

namespace App\Module\Commercial\Infrastructure\ApiPlatform\Serializer;

use ApiPlatform\Metadata\IriConverterInterface;
use App\Shared\Domain\Contract\CategoryInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

/**
 * Denormalise un IRI (`/api/categories/{id}`) vers la Category concrete quand la
 * propriete cible est type-hintee par le contrat CategoryInterface (ex:
 * Client::$categories, ClientAddress::$categories).
 *
 * Pourquoi ce denormalizer : API Platform deduit le type de l'element de
 * collection depuis le phpdoc `@var Collection<int, CategoryInterface>`, donc
 * l'INTERFACE. Or le serializer ne sait pas denormaliser un IRI vers une
 * interface (« Could not denormalize object of type CategoryInterface[] ») : il
 * lui faut une classe-ressource concrete. On resout donc l'IRI via l'IriConverter
 * (qui retourne la Category mappee a la route) sans importer Category — la regle
 * ABSOLUE n°1 reste respectee (dependance au seul contrat Shared + API Platform).
 *
 * En lecture (normalisation), aucun probleme : l'objet reel EST une Category,
 * resource a part entiere, serialisee en IRI par le normalizer standard.
 */
final class CategoryReferenceDenormalizer implements DenormalizerInterface
{
    public function __construct(
        private readonly IriConverterInterface $iriConverter,
    ) {}

    public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): ?CategoryInterface
    {
        if (!is_string($data) || '' === $data) {
            return null;
        }

        // getResourceFromIri leve une exception sur IRI invalide -> 400, ce qui
        // est le comportement attendu pour une reference cassee.
        $resource = $this->iriConverter->getResourceFromIri($data);

        // IRI syntaxiquement valide mais pointant sur une autre ressource (ex:
        // '/api/clients/5' la ou une categorie est attendue) : on refuse
        // explicitement plutot que de retourner null silencieusement, ce qui
        // perdrait la reference sans erreur. UnexpectedValueException -> 400.
        if (!$resource instanceof CategoryInterface) {
            throw new UnexpectedValueException(sprintf(
                'L\'IRI "%s" ne référence pas une catégorie.',
                $data,
            ));
        }

        return $resource;
    }

    public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
    {
        // Support base sur le seul type cible : l'ArrayDenormalizer (collection
        // `CategoryInterface[]`) interroge le support en passant le TABLEAU
        // complet comme $data avant de deleguer element par element. Tester
        // is_string($data) ici casserait donc la chaine pour les collections.
        return CategoryInterface::class === $type;
    }

    /**
     * @return array<class-string|string, bool>
     */
    public function getSupportedTypes(?string $format): array
    {
        return [CategoryInterface::class => true];
    }
}
