<?php

namespace AutoAdmin\Bundle\Content\Doctrine\EventListener;

use AutoAdmin\Bundle\Content\Model\CreatedAtInterface;
use AutoAdmin\Bundle\Content\Model\TranslationInterface;
use AutoAdmin\Bundle\Content\Model\UpdatedAtInterface;
use AutoAdmin\Bundle\Content\Model\UpdatedAtOptOutInterface;
use AutoAdmin\Bundle\Core\Doctrine\Traits\ListenerStateManagerTrait;
use DateTime;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Events;

class CreateUpdateSubscriber implements EventSubscriber
{
    use ListenerStateManagerTrait;

    public function getSubscribedEvents(): array
    {
        return [
            Events::preUpdate,
            Events::prePersist,
            Events::onFlush,
        ];
    }

    public function preUpdate(PreUpdateEventArgs $args): void
    {
        if (!$this->canRun()) {
            return;
        }

        $this->updatedAt($args);
    }

    public function updatedAt(PreUpdateEventArgs $args): void
    {
        $entity = $args->getObject();

        if ($entity instanceof UpdatedAtInterface) {
            $datetime = new DateTime();
            $entity->setUpdatedAt($datetime);
        }
    }

    public function prePersist(PrePersistEventArgs $args): void
    {
        if (!$this->canRun()) {
            return;
        }

        $this->createdAt($args);
    }

    public function createdAt(PrePersistEventArgs $args): void
    {
        $entity = $args->getObject();
        $datetime = new DateTime();

        if ($entity instanceof CreatedAtInterface && !$entity->getCreatedAt()) {
            $entity->setCreatedAt($datetime);
        }

        if (
            !$entity instanceof UpdatedAtOptOutInterface
            && $entity instanceof UpdatedAtInterface
            && !$entity->getUpdatedAt()
        ) {
            $entity->setUpdatedAt($datetime);
        }
    }

    public function onFlush(OnFlushEventArgs $args): void
    {
        if (!$this->canRun()) {
            return;
        }

        $em = $args->getObjectManager();
        $uow = $em->getUnitOfWork();

        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            // if the $entity implements UpdatedAtInterface and has a translatable "parent" class,
            // do update its "updatedAt" field, $entity will be properly handled in preUpdate method
            if (
                $entity instanceof UpdatedAtInterface
                && $entity instanceof TranslationInterface
                && ($translatable = $entity->getTranslatable())
                && $translatable instanceof UpdatedAtInterface
            ) {
                $translatableClass = get_class($translatable);
                $meta = $em->getClassMetadata($translatableClass);
                $translatable->setUpdatedAt(new DateTime());
                if ($uow->getEntityChangeSet($translatable)) {
                    // the entity has pending changes, we need to recompute/merge
                    $uow->recomputeSingleEntityChangeSet($meta, $translatable);
                } else {
                    // there are no changes, we compute from scratch
                    $uow->computeChangeSet($meta, $translatable);
                }
            }
        }
    }
}
