<?php

namespace Doctrine\Bundle\PHPCRBundle;

use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\InitializerPass;
use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\MigratorPass;
use Doctrine\Bundle\PHPCRBundle\OptionalCommand\Jackalope\InitDoctrineDbalCommand;
use Doctrine\Bundle\PHPCRBundle\OptionalCommand\ODM\DocumentConvertTranslationCommand;
use Doctrine\Bundle\PHPCRBundle\OptionalCommand\ODM\DocumentMigrateClassCommand;
use Doctrine\Bundle\PHPCRBundle\OptionalCommand\ODM\InfoDoctrineCommand;
use Doctrine\Bundle\PHPCRBundle\OptionalCommand\ODM\VerifyUniqueNodeTypesMappingCommand;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ODM\PHPCR\Version;
use Jackalope\Session;
use Jackalope\Tools\Console\Command\InitDoctrineDbalCommand as BaseInitDoctrineDbalCommand;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass;
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class DoctrinePHPCRBundle extends Bundle
{
    /**
     * Autoloader for proxies.
     */
    private ?\Closure $autoloader;

    public function build(ContainerBuilder $container): void
    {
        parent::build($container);

        $container->addCompilerPass(new MigratorPass());
        $container->addCompilerPass(new InitializerPass());
        if (class_exists(Version::class)) {
            $container->addCompilerPass(new RegisterEventListenersAndSubscribersPass('doctrine_phpcr.sessions', 'doctrine_phpcr.%s_session.event_manager', 'doctrine_phpcr'), PassConfig::TYPE_BEFORE_OPTIMIZATION);
        }
    }

    public function registerCommands(Application $application): void
    {
        parent::registerCommands($application);

        if (class_exists(Version::class)) {
            $application->add(new DocumentMigrateClassCommand());
            $application->add(new InfoDoctrineCommand());
            $application->add(new VerifyUniqueNodeTypesMappingCommand());
            $application->add(new DocumentConvertTranslationCommand());
        }

        if (class_exists(BaseInitDoctrineDbalCommand::class)) {
            $application->add(new InitDoctrineDbalCommand());
        }
    }

    public function boot(): void
    {
        // Register an autoloader for proxies to avoid issues when unserializing them when the ODM is used.
        if ($this->container->hasParameter('doctrine_phpcr.odm.proxy_namespace')) {
            $namespace = $this->container->getParameter('doctrine_phpcr.odm.proxy_namespace');
            $dir = $this->container->getParameter('doctrine_phpcr.odm.proxy_dir');
            // See https://github.com/symfony/symfony/pull/3419 for usage of references
            $container = &$this->container;

            $this->autoloader = function ($class) use ($namespace, $dir, &$container) {
                if (str_starts_with($class, $namespace)) {
                    $fileName = str_replace('\\', '', substr($class, \strlen($namespace) + 1));
                    $file = $dir.\DIRECTORY_SEPARATOR.$fileName.'.php';

                    if (!is_file($file) && $container->getParameter('doctrine_phpcr.odm.auto_generate_proxy_classes')) {
                        $originalClassName = ClassUtils::getRealClass($class);
                        $registry = $container->get('doctrine_phpcr');

                        // Tries to auto-generate the proxy file
                        foreach ($registry->getManagers() as $dm) {
                            if ($dm->getConfiguration()->getAutoGenerateProxyClasses()) {
                                $classes = $dm->getMetadataFactory()->getAllMetadata();

                                foreach ($classes as $classMetadata) {
                                    if ($classMetadata->name === $originalClassName) {
                                        $dm->getProxyFactory()->generateProxyClasses([$classMetadata]);
                                    }
                                }
                            }
                        }

                        clearstatcache($file);
                    }

                    if (is_file($file)) {
                        require $file;
                    }
                }
            };
            spl_autoload_register($this->autoloader);
        }
    }

    public function shutdown(): void
    {
        if (isset($this->autoloader)) {
            spl_autoload_unregister($this->autoloader);
            unset($this->autoloader);
        }

        $this->clearDocumentManagers();
        $this->closeConnections();
    }

    /**
     * Clear all document managers to clear references to entities for GC.
     */
    private function clearDocumentManagers(): void
    {
        if (!$this->container->hasParameter('doctrine_phpcr.odm.document_managers')) {
            return;
        }

        foreach ($this->container->getParameter('doctrine_phpcr.odm.document_managers') as $id) {
            $this->container->get($id)->clear();
        }
    }

    /**
     * Close all connections to avoid reaching too many connections in the process when booting again later (tests).
     */
    private function closeConnections(): void
    {
        if (!$this->container->hasParameter('doctrine_phpcr.sessions')) {
            return;
        }

        foreach ($this->container->getParameter('doctrine_phpcr.sessions') as $id) {
            $session = $this->container->get($id);
            if (!$session instanceof Session) {
                return;
            }

            $session->getTransport()->logout();
        }
    }
}
