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

namespace WordpressModels\ORM;

use Doctrine\Common\EventManager;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Exception\MissingMappingDriverImplementation;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Doctrine\Persistence\Mapping\MappingException;
use WordpressModels\ORM\Entity\Post;

/**
 * Factory + Singleton provider class for Doctrine EntityManager and Configurations.
 */
class EntityManagerFactory
{

    private static ?EntityManagerInterface $entityManager = null;

    /**
     * Create an EntityManager using the WpdbDriver.
     *
     * @throws Exception
     * @throws MissingMappingDriverImplementation
     * @throws MappingException
     * @throws \ReflectionException
     */
    public static function create(?Configuration $config = null): EntityManagerInterface
    {
        if (self::$entityManager?->isOpen()) {
            return self::$entityManager;
        }

        $config ??= self::createConfiguration();
        $eventManager = new EventManager();

        self::$entityManager = new EntityManager(
            DriverManager::getConnection(['driverClass' => WpdbDriver::class], $config),
            $config,
            $eventManager
        );

        // register the table prefix injector
        $eventManager->addEventSubscriber(new TablePrefixEventSubscriber());

        // register wordpress hook bridge for doctrine ORM events
        $eventManager->addEventSubscriber(new DoctrineEventHookBridge());

        // register post type discriminators
        $eventManager->addEventSubscriber(new PostTypeClassMetadataListener());

        // add woocommerce post types if installed
        if (isset($GLOBALS['wc_container'])) {
            WooCommerceEntities::instance();
        }

        // add timestamp listener
        $eventManager->addEventSubscriber(new TimestampListener());

        // preload post classes
        $classMetadataFactory = self::$entityManager->getMetadataFactory();
        $classes = apply_filters('doctrine_post_type_discriminator_map', $classMetadataFactory->getMetadataFor(Post::class)->discriminatorMap);
        foreach ($classes as $class) {
            $classMetadataFactory->getMetadataFor($class);
        }

        return self::$entityManager;
    }

    /**
     * Create a Doctrine EntityManager Configuration.
     *
     * Sets the proxy cache directory at WP_CONTENT_DIR/cache/{optional_version_dir/}proxy
     *
     * Initially, looks for entities in the ./Entity subdirectory of the current directory. Entity locations
     * can be registered using the `doctrine_entity_directories` hook.
     *
     * @param string $version Recommended
     * @todo Make production-ready doctrine config
     */
    public static function createConfiguration(string $version = ''): Configuration
    {
        $config = new Configuration();
        $config->setNamingStrategy(new UnderscoreNamingStrategy());

        // set the proxy dir per version
        $proxyDir = $version ? "$version/proxy" : 'proxy';
        $config->setProxyDir(WP_CONTENT_DIR . "/cache/$proxyDir");
        $config->setProxyNamespace('DatabaseProxy');

        // always autogenerate classes
        $config->setAutoGenerateProxyClasses(true);

        // use the Model directory for entity metadata
        $paths = apply_filters('doctrine_entity_directories', [
            __DIR__ . '/Entity',
        ]);

        $driverChain = new MappingDriverChain();
        $driverChain->setDefaultDriver(new AttributeDriver($paths));

        $config->setMetadataDriverImpl($driverChain);

        return $config;
    }

}
