<?php

declare(strict_types=1);

namespace Gls\GlsPoland\PrestaShop\Installer;

use Gls\GlsPoland\Configuration\Initializer\DoctrineListenersInitializer;
use Gls\GlsPoland\Configuration\Initializer\DoctrineMappingsInitializer;
use Gls\GlsPoland\Configuration\Initializer\DoctrineTypesInitializer;
use Gls\GlsPoland\Configuration\Initializer\EnvInitializer;
use Gls\GlsPoland\DependencyInjection\CompilerPass\ProvideFactoriesCompilerPass;
use Gls\GlsPoland\PrestaShop\MessageHandler\Carrier\Assembler\CarrierAssemblerInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Compiler\CheckDefinitionValidityPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;

final class InstallerFactory
{
    private $module;
    private $appContainer;
    private $installerContainer;

    public function __construct(\Module $module, ContainerInterface $container)
    {
        $this->module = $module;
        $this->appContainer = $container;
    }

    /**
     * @return InstallerInterface&UninstallerInterface
     */
    public function createInstaller(): InstallerInterface
    {
        return $this->getContainer()->get(InstallerInterface::class);
    }

    private function getContainer(): ContainerInterface
    {
        return $this->installerContainer ?? ($this->installerContainer = $this->buildContainer());
    }

    private function buildContainer(): ContainerInterface
    {
        $this->initConfiguration();

        $parameterBag = new EnvPlaceholderParameterBag($this->appContainer->getParameterBag()->all());
        $container = new ContainerBuilder($parameterBag);

        $fileLocator = new FileLocator($this->module->getLocalPath() . 'config/');
        $loader = new YamlFileLoader($container, $fileLocator);
        $loader->load('installer.yml');

        $passConfig = $container->getCompilerPassConfig();
        $optimizationPasses = $this->setUpOptimizationPasses($passConfig->getOptimizationPasses());
        $passConfig->setOptimizationPasses($optimizationPasses);

        $container
            ->registerForAutoconfiguration(InstallerInterface::class)
            ->addTag('gls_poland.installer');
        $container
            ->registerForAutoconfiguration(UninstallerInterface::class)
            ->addTag('gls_poland.uninstaller');
        $container
            ->registerForAutoconfiguration(DatabaseMigrationInterface::class)
            ->addTag('gls_poland.db_migration');
        $container
            ->registerForAutoconfiguration(CarrierAssemblerInterface::class)
            ->addTag('gls_poland.carrier_assembler');

        $container->compile(true);
        $container->set('app_container', $this->appContainer);

        return $container;
    }

    private function setUpOptimizationPasses(array $passes): array
    {
        $additionalPass = new ProvideFactoriesCompilerPass($this->appContainer, 'app_container');
        foreach ($passes as $i => $pass) {
            if (!$pass instanceof CheckDefinitionValidityPass) {
                continue;
            }

            array_splice($passes, $i, 1, [$pass, $additionalPass]);

            return $passes;
        }

        $passes[] = $additionalPass;

        return $passes;
    }

    private function initConfiguration(): void
    {
        foreach ($this->getConfigInitializers() as $initializer) {
            $initializer->init();
        }
    }

    private function getConfigInitializers(): \Generator
    {
        $entityManager = $this->appContainer->get('doctrine')->getManager();

        yield new EnvInitializer($this->module, null, $this->appContainer->getParameter('kernel.environment'));
        yield new DoctrineMappingsInitializer($entityManager, $this->module);
        yield new DoctrineListenersInitializer($entityManager, $this->appContainer->getParameter('database_prefix'), true);
        yield new DoctrineTypesInitializer();
    }
}
