<?php
/*
 * Copyright (c) 2023. RTM Business
 */

namespace WordpressModels\ORM;

use DateTime;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreRemoveEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\Persistence\Mapping\ClassMetadata;
use WordpressModels\ORM\Attributes\Timestamp;

/**
 * Doctrine event subscriber to automatically set timestamps for Entities with the Timestampable Trait.
 *
 * Also facilitates soft-deletions.
 */
class TimestampListener implements EventSubscriber
{

    private array $softDeletions = [];

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

    public function prePersist(PrePersistEventArgs $eventArgs)
    {
        $this->updateTimestamps($eventArgs, Events::prePersist);
    }

    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        $this->updateTimestamps($eventArgs, Events::preUpdate);
    }

    public function preRemove(PreRemoveEventArgs $eventArgs)
    {
        if ($this->updateTimestamps($eventArgs, Events::preRemove)) {
            $this->softDeletions[] = spl_object_hash($eventArgs->getObject());
        }
    }

    /**
     * Cancel scheduled deletions when soft-deletions are registered.
     *
     * @param OnFlushEventArgs $eventArgs
     * @return void
     */
    public function onFlush(OnFlushEventArgs $eventArgs)
    {
        $objectManager = $eventArgs->getObjectManager();
        $unitOfWork = $objectManager->getUnitOfWork();
        foreach ($unitOfWork->getScheduledEntityDeletions() as $deletion) {
            $classMetadata = $objectManager
                ->getClassMetadata($deletion::class);
            $hash = spl_object_hash($deletion);

            if (in_array($hash, $this->softDeletions)) {
                // cancel the deletion
                $objectManager->persist($deletion);
                $unitOfWork->computeChangeSet($classMetadata, $deletion);
            }
        }
    }

    private function updateTimestamps(LifecycleEventArgs $eventArgs, string $event): bool
    {
        $entity = $eventArgs->getObject();
        $objectManager = $eventArgs->getObjectManager();
        $props = $this->getTimestampProperties($objectManager->getClassMetadata($entity::class), $event);

        $isTimestamped = false;
        foreach ($props as $prop) {
            $prop->setValue($entity, new DateTime());
            $isTimestamped = true;
        }

        return $isTimestamped;
    }

    /**
     * @param ClassMetadata $metadata
     * @param string $event
     * @return \ReflectionProperty[]
     */
    private function getTimestampProperties(ClassMetadata $metadata, string $event): array
    {
        return array_filter($metadata->getReflectionProperties(), function (?\ReflectionProperty $property) use ($event) {
            if (!$property || !($reflectionAttribute = current($property->getAttributes(Timestamp::class)))) {
                return false;
            }
            /** @var Timestamp $timestamp */
            $timestamp = $reflectionAttribute->newInstance();
            return $timestamp->onEvent === $event;
        });
    }

}
