<?php

declare(strict_types=1);

namespace App\Module\Core\Infrastructure\ApiPlatform\State\Processor;

use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Core\Domain\Entity\Role;
use App\Module\Core\Domain\Exception\SystemRoleDeletionException;
use Doctrine\ORM\EntityManagerInterface;
use LogicException;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * Processor applicatif pour l'entite Role.
 *
 * Choix d'implementation : une seule classe qui recoit en dependances les deux
 * processors Doctrine decores (Persist et Remove) et branche l'un ou l'autre
 * selon le type d'operation. Ce choix reste plus lisible que deux classes
 * jumelees et reflete la symetrie des gardes metier (immuabilite du `code`
 * cote ecriture, protection des roles systeme cote suppression).
 *
 * Gardes metier :
 *  - DELETE : delegue a Role::ensureDeletable() et traduit la
 *    SystemRoleDeletionException en AccessDeniedHttpException (403).
 *  - POST/PATCH : refuse toute modification du `code` (champ immuable apres
 *    creation), regle uniforme pour les roles systeme ET custom.
 *
 * @implements ProcessorInterface<Role, null|Role>
 */
final class RoleProcessor implements ProcessorInterface
{
    public function __construct(
        #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
        private readonly ProcessorInterface $persistProcessor,
        #[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')]
        private readonly ProcessorInterface $removeProcessor,
        private readonly EntityManagerInterface $entityManager,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof Role) {
            // Ce processor est wire exclusivement sur les operations Role.
            // Si on arrive ici avec autre chose, c'est une misconfiguration
            // qu'il faut faire remonter fort.
            throw new LogicException(sprintf(
                'RoleProcessor attend une instance de %s, %s recu.',
                Role::class,
                get_debug_type($data),
            ));
        }

        if ($operation instanceof DeleteOperationInterface) {
            try {
                $data->ensureDeletable();
            } catch (SystemRoleDeletionException $e) {
                // Traduction HTTP : le domaine reste pur, l'API renvoie 403.
                throw new AccessDeniedHttpException($e->getMessage(), $e);
            }

            return $this->removeProcessor->process($data, $operation, $uriVariables, $context);
        }

        // Ecriture (POST/PATCH) : verifier l'immuabilite du `code`.
        // L'UnitOfWork n'expose un etat d'origine que pour les entites deja
        // managees (PATCH). Pour un POST (entite nouvelle), `getOriginalEntityData`
        // retourne un tableau vide : aucune comparaison necessaire.
        $originalData = $this->entityManager->getUnitOfWork()->getOriginalEntityData($data);

        if (isset($originalData['code']) && $originalData['code'] !== $data->getCode()) {
            throw new BadRequestHttpException("Le code d'un role est immuable apres creation.");
        }

        return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
    }
}
