<?php

namespace WordpressModels\ORM\EntityListener;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use WordpressModels\DependencyInjection\Initializer\Attributes\ForceInitialize;
use WordpressModels\ORM\Entity\AbstractMeta;

/**
 * Doctrine entity listener for the PostMeta entity.
 *
 * Passes changes to Wordpress built-in functions. All handler methods cancel the entity from the UnitOfWork.
 *
 * Also refreshes entities when
 *
 */
#[AsDoctrineListener(event: 'onFlush'), AsDoctrineListener(event: 'postFlush')]
#[ForceInitialize]
class MetaListener implements EventSubscriber
{

    /**
     * @var AbstractMeta[]
     */
    private array $metaToRefresh = [];

    /**
     * @var class-string[]
     */
    private array $metaClassesToClear;

    public function __construct(private EntityManagerInterface $entityManager)
    {
        // register meta add, update, delete hooks
        $cmd = array_filter($this->entityManager->getMetadataFactory()->getAllMetadata(), fn(ClassMetadata $meta) => is_subclass_of($meta->getName(), AbstractMeta::class));
        $this->metaClassesToClear = array_map(fn(ClassMetadata $meta) => $meta->getName(), $cmd);
        $types = array_map(fn(string $meta) => $meta::getType(), $this->metaClassesToClear);

        foreach ($types as $type) {
            add_action("added_{$type}_meta", [$this, 'onMetaSave'], 10, 1);
            add_action("updated_{$type}_meta", [$this, 'onMetaSave'], 10, 1);
            add_action("deleted_{$type}_meta", [$this, 'onMetaSave'], 10, 1);
        }
    }

    /**
     * Insert or update the post in the database.
     *
     * @param AbstractMeta $meta
     * @param PreFlushEventArgs $args
     * @return void
     */
    public function preFlush(AbstractMeta $meta, PreFlushEventArgs $args)
    {
        // temporary store the post to refresh it after the flush
        $this->metaToRefresh[$meta->getId()] = $meta;
        if (!in_array($meta::class, $this->metaClassesToClear)) {
            $this->metaClassesToClear[] = $meta::class;
        }

        $entityManager = $args->getObjectManager();
        $tableName = $entityManager->getClassMetadata($meta::class)->getTableName();
    }

    /**
     * Clear all scheduled insertions and updates for the Post entity.
     *
     * @param OnFlushEventArgs $args
     * @return void
     */
    public function onFlush(OnFlushEventArgs $args)
    {
        // cancel the entity from the UnitOfWork and the scheduled insertions and updates
        foreach ($this->metaClassesToClear as $metaClass) {
            $args->getObjectManager()->getUnitOfWork()->clear($metaClass);
        }
    }

    /**
     * Ensure all previously tracked entities are refreshed after the flush.
     *
     * @param PostFlushEventArgs $args
     * @return void
     */
    public function postFlush(PostFlushEventArgs $args)
    {
        $objectManager = $args->getObjectManager();
        $unitOfWork = $objectManager->getUnitOfWork();
        foreach ($this->metaToRefresh as $post) {
            $unitOfWork->assignPostInsertId($post, $post->getId());
            $unitOfWork->refresh($post);
        }
        $this->metaToRefresh = [];
    }

    /**
     * Sync post state for entities that are managed by Doctrine.
     *
     * @param int $metaId
     * @param int $objectId
     * @param string $metaKey
     * @param mixed $metaValue
     * @param EntityManagerInterface $entityManager
     * @return void
     */
    public function onMetaSave(int $metaId)
    {
        // todo: we might need to handle the case where the post type is changed
//        $postClass = $entityManager->getClassMetadata(Post::class)->discriminatorMap[$post->post_type] ?? null;
        if (!($meta = $this->metaToRefresh[$metaId] ?? null)) {
            return;
        }

        // refresh the entity if it is managed by Doctrine
        $entity = $this->entityManager->getUnitOfWork()->tryGetById($metaId, $meta::class);
        if ($entity) {
            $this->entityManager->refresh($entity);
        }
    }

    public function getSubscribedEvents()
    {
        return [
            Events::onFlush,
            Events::postFlush,
        ];
    }
}