<?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\Doctrine\EventListener;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Events;

/**
 * Class DetachEntityIsEmptyOnPreFlush.
 *
 * Écouteur Doctrine exécuté avant le flush (`preFlush`) qui détache automatiquement
 * les entités considérées comme « vides » du cycle de persistance Doctrine.
 *
 * Une entité est jugée vide si elle expose une méthode `isEmpty(): bool` et que
 * celle-ci retourne `true`.
 * Cela s’aligne sur le contrat défini par {@see \Serenity\CoreBundle\Shared\Common\EmptyInterface}.
 *
 * ---
 *
 * ## 🎯 Objectif
 * Éviter l’insertion ou la mise à jour en base de données d’entités
 * sans contenu métier pertinent (par exemple des Value Objects ou sous-objets vides).
 *
 * ---
 *
 * ## ⚙️ Fonctionnement
 * 1. Lors de l’événement Doctrine `preFlush`, l’écouteur :
 *    - parcourt toutes les entités marquées pour **insertion**,
 *    - puis toutes les entités marquées pour **mise à jour**.
 *
 * 2. Pour chaque entité rencontrée :
 *    - si la méthode `isEmpty()` existe et retourne `true`,
 *      alors l’entité est **détachée** du `EntityManager` (via `$em->detach($entity)`).
 *
 * 3. L’entité détachée :
 *    - ne sera **ni insérée**, **ni mise à jour**, **ni suivie** par Doctrine.
 *    - disparaîtra du `UnitOfWork` avant la synchronisation finale en base.
 *
 * ---
 *
 * ## 💡 Exemple d’utilisation
 *
 * ```php
 * use Serenity\CoreBundle\Domain\Common\EmptyInterface;
 *
 * #[ORM\Entity]
 * class Metadata implements EmptyInterface
 * {
 *     public function __construct(private ?string $value = null) {}
 *
 *     public function isEmpty(): bool
 *     {
 *         return $this->value === null || $this->value === '';
 *     }
 * }
 * ```
 *
 * Si une instance `Metadata` vide est attachée à une entité principale,
 * elle sera automatiquement **ignorée au flush** :
 *
 * ```php
 * $metadata = new Metadata(null);
 * $em->persist($metadata);
 * $em->flush(); // → Le listener détache l’entité, aucun INSERT n’est effectué
 * ```
 *
 * ---
 *
 * ## 🧩 Intégration Serenity
 * - Fonctionne en complément du {@see \Serenity\CoreBundle\Shared\Normalizer\DoctrineJsonEmptyNormalizer}.
 * - Permet une gestion propre des entités « vides » à la fois en **base de données**
 *   et en **sérialisation JSON**.
 *
 * ---
 *
 * ## 🧱 Détails techniques
 * - Événement Doctrine : `preFlush`
 * - Enregistrement automatique via `#[AsDoctrineListener(event: Events::preFlush)]`
 * - Compatible avec Doctrine ORM >= 2.12
 *
 * ---
 */
#[AsDoctrineListener(event: Events::preFlush)]
class DetachEntityIsEmptyOnPreFlush
{
    public function __invoke(PreFlushEventArgs $args): void
    {
        $em = $args->getObjectManager();
        $uow = $em->getUnitOfWork();

        // Parcours des entités à insérer
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            if (method_exists($entity, 'isEmpty') && $entity->isEmpty()) {
                $em->detach($entity);
            }
        }

        // Parcours des entités à mettre à jour
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            if (method_exists($entity, 'isEmpty') && $entity->isEmpty()) {
                $em->detach($entity);
            }
        }
    }
}
