<?php

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Serenity\CoreBundle\Shared\Normalizer;

use Doctrine\ORM\EntityManagerInterface;
use Serenity\CoreBundle\Shared\Common\AutoIdentifierInterface;
use Serenity\CoreBundle\Shared\Common\IdentifierInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Uid\Uuid;

/**
 * Class DoctrineJsonEntityNormalizer.
 *
 * Normalizer et denormalizer personnalisé conçu pour sérialiser et désérialiser
 * les entités ou Value Objects implémentant {@see IdentifierInterface} lorsqu’ils
 * sont stockés dans des colonnes JSON (ou JSONB) de Doctrine.
 *
 * Ce normalizer simplifie le stockage et la reconstruction d’entités Doctrine
 * dans des structures JSON, en utilisant uniquement leur identifiant.
 *
 * ## Fonctionnement :
 *
 * - **Normalization :**
 *   Sérialise un objet implémentant {@see IdentifierInterface} en son identifiant
 *   (généralement un `int` ou un `string`).
 *
 * - **Denormalization :**
 *   Lors de la désérialisation, l'identifiant est utilisé pour retrouver
 *   l'entité correspondante via le `EntityManager`.
 *
 * ## Exemple :
 * ```php
 * class User implements IdentifierInterface {
 *     public function __construct(private int $id, private string $name) {}
 *     public function getId(): int { return $this->id; }
 * }
 *
 * // Normalisation
 * $normalizer->normalize($user); // retourne 42
 *
 * // Dé-normalisation
 * $normalizer->denormalize(42, User::class); // retourne l’entité User avec id = 42
 * ```
 *
 * ## Utilité :
 * - Réduit la taille du JSON stocké en base.
 * - Évite la duplication complète des entités dans les champs JSON.
 * - Facilite la reconstruction automatique via Doctrine.
 */
readonly class DoctrineJsonEntityNormalizer implements NormalizerInterface, DenormalizerInterface
{
    public function __construct(private EntityManagerInterface $entityManager)
    {
    }

    public function normalize(mixed $data, ?string $format = null, array $context = []): int|string
    {
        if (!$data instanceof IdentifierInterface && !$data instanceof AutoIdentifierInterface) {
            throw new InvalidArgumentException('The object does not implement IdentifierInterface.');
        }

        $id = $data->getId();

        if (!$id instanceof Uuid) {
            throw new InvalidArgumentException('Identifier must be an instance of Uuid.');
        }

        return $id->toRfc4122();
    }

    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
    {
        return $data instanceof IdentifierInterface || $data instanceof AutoIdentifierInterface;
    }

    /**
     * @throws NotNormalizableValueException
     */
    public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
    {
        if (!is_subclass_of($type, IdentifierInterface::class) && !is_subclass_of($type, AutoIdentifierInterface::class)) {
            throw new InvalidArgumentException('The data must belong to a IdentifierInterface.');
        }

        return $this->entityManager->getRepository($type)->find($data);
    }

    public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
    {
        if (!is_subclass_of($type, IdentifierInterface::class) && !is_subclass_of($type, AutoIdentifierInterface::class)) {
            return false;
        }

        return true;
    }

    public function getSupportedTypes(?string $format): array
    {
        return [
            IdentifierInterface::class => true,
            AutoIdentifierInterface::class => true,
        ];
    }
}
