<?php

declare(strict_types=1);

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

use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Catalog\Application\Service\CategoryCodeGenerator;
use App\Module\Catalog\Domain\Entity\Category;
use DateTimeImmutable;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\Exception\HttpException;

/**
 * Processor Category : applique les regles de gestion en ecriture.
 *
 * - POST / PATCH : trim du nom (RG-1.03) ; a la CREATION, generation du `code`
 *   technique stable (slug MAJUSCULE du nom, unique parmi les actifs — ERP-78)
 *   via CategoryCodeGenerator ; puis delegation au persist_processor Doctrine
 *   ORM. Le code est FIGE a la creation (jamais recalcule sur PATCH). Toute
 *   UniqueConstraintViolationException remontee par Postgres (collision sur
 *   l'index partiel uq_category_name_active — unicite GLOBALE du nom parmi les
 *   actifs) est traduite en HTTP 409 avec le message attendu par la spec (RG-1.07).
 * - DELETE : soft delete (RG-1.12). On NE delegue PAS au remove_processor ;
 *   on pose deletedAt = now() puis on delegue au persist_processor pour que
 *   le UPDATE Doctrine parte et que le TimestampableBlamableSubscriber mette
 *   a jour updatedAt / updatedBy (RG-1.16) en plus de l'AuditListener.
 *
 * @implements ProcessorInterface<Category, null|Category>
 */
final class CategoryProcessor implements ProcessorInterface
{
    public function __construct(
        #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
        private readonly ProcessorInterface $persistProcessor,
        private readonly CategoryCodeGenerator $codeGenerator,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof Category) {
            return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
        }

        // RG-1.12 : soft delete au lieu d'un remove physique. On bascule la DELETE
        // en UPDATE en posant deletedAt, puis on persiste via le persist_processor.
        if ($operation instanceof DeleteOperationInterface) {
            $data->setDeletedAt(new DateTimeImmutable());

            try {
                $this->persistProcessor->process($data, $operation, $uriVariables, $context);
            } catch (UniqueConstraintViolationException $e) {
                // Par construction, le soft delete ne peut pas violer l'index
                // partiel (il LIBERE le couple (name, type) au lieu de le creer).
                // On laisse remonter en 500 pour signaler une anomalie reelle.
                throw $e;
            }

            return null;
        }

        // POST / PATCH : trim du nom avant validation et persistance (RG-1.03).
        if (null !== $data->getName()) {
            $data->setName(trim($data->getName()));
        }

        // ERP-78 : le code est genere a la CREATION puis fige. On le (re)genere
        // uniquement s'il est absent (POST, ou entite seedee sans code) — un PATCH
        // sur une categorie existante conserve son code. Genere depuis le nom
        // (NotBlank, deja trimme), unique parmi les actifs.
        if (null === $data->getCode() && null !== $data->getName()) {
            $data->setCode($this->codeGenerator->generateUnique($data->getName(), $data->getId()));
        }

        try {
            return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
        } catch (UniqueConstraintViolationException $e) {
            // RG-1.07 : doublon de nom GLOBAL (LOWER(name)) parmi les non-soft-deleted
            // (uq_category_name_active). L'unicite n'est plus liee au type depuis le
            // passage en ManyToMany.
            throw new HttpException(
                409,
                sprintf('Une catégorie nommée "%s" existe déjà.', $data->getName() ?? ''),
                $e,
            );
        }
    }
}
