<?php

declare(strict_types=1);

namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;

use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\Persistence\Mapping\Driver\PHPDriver;
use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver;
use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterMappingsPass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use TypeError;

use function func_get_arg;
use function func_num_args;
use function gettype;
use function is_array;
use function is_bool;
use function sprintf;

/**
 * Class for Symfony bundles to configure mappings for model classes not in the
 * auto-mapped folder.
 */
final class DoctrineOrmMappingsPass extends RegisterMappingsPass
{
    /**
     * You should not directly instantiate this class but use one of the
     * factory methods.
     *
     * @param Definition|Reference $driver            Driver DI definition or reference.
     * @param string[]             $namespaces        List of namespaces handled by $driver.
     * @param string[]             $managerParameters Ordered list of container parameters that
     *                                                could hold the manager name.
     *                                                doctrine.default_entity_manager is appended
     *                                                automatically.
     * @param string|false         $enabledParameter  If specified, the compiler pass only executes
     *                                                if this parameter is defined in the service
     *                                                container.
     */
    public function __construct(Definition|Reference $driver, array $namespaces, array $managerParameters, string|false $enabledParameter = false)
    {
        $managerParameters[] = 'doctrine.default_entity_manager';

        parent::__construct(
            $driver,
            $namespaces,
            $managerParameters,
            'doctrine.orm.%s_metadata_driver',
            $enabledParameter,
        );
    }

    /**
     * @param string[]     $namespaces          Hashmap of directory path to namespace.
     * @param string[]     $managerParameters   List of parameters that could which object manager name
     *                                          your bundle uses. This compiler pass will automatically
     *                                          append the parameter name for the default entity manager
     *                                          to this list.
     * @param string|false $enabledParameter    Service container parameter that must be present to
     *                                          enable the mapping. Set to false to not do any check,
     *                                          optional.
     * @param bool         $enableXsdValidation
     *
     * @phpstan-ignore missingType.iterableValue
     */
    public static function createXmlMappingDriver(array $namespaces, array $managerParameters = [], string|false $enabledParameter = false, bool|array $enableXsdValidation = false): self
    {
        if (is_array($enableXsdValidation)) {
            Deprecation::trigger(
                'doctrine/doctrine-bundle',
                'https://github.com/doctrine/DoctrineBundle/pull/2190',
                'Providing a $aliasMap argument to %s is deprecated and has no effect.',
                __METHOD__,
            );
            $enableXsdValidation = false;

            if (func_num_args() === 5) {
                $enableXsdValidationArg = func_get_arg(4);
                if (! is_bool($enableXsdValidationArg)) {
                    throw new TypeError(sprintf('$enableXsdValidation is expected to be boolean, %s provided', gettype($enableXsdValidationArg)));
                }

                $enableXsdValidation = $enableXsdValidationArg;
            }
        }

        $locator = new Definition(SymfonyFileLocator::class, [$namespaces, '.orm.xml']);
        $driver  = new Definition(XmlDriver::class, [$locator, XmlDriver::DEFAULT_FILE_EXTENSION, $enableXsdValidation]);

        return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter);
    }

    /**
     * @param string[]     $namespaces        Hashmap of directory path to namespace
     * @param string[]     $managerParameters List of parameters that could which object manager name
     *                                        your bundle uses. This compiler pass will automatically
     *                                        append the parameter name for the default entity manager
     *                                        to this list.
     * @param string|false $enabledParameter  Service container parameter that must be present to
     *                                        enable the mapping. Set to false to not do any check,
     *                                        optional.
     */
    public static function createPhpMappingDriver(array $namespaces, array $managerParameters = [], string|false $enabledParameter = false): self
    {
        $locator = new Definition(SymfonyFileLocator::class, [$namespaces, '.php']);
        $driver  = new Definition(PHPDriver::class, [$locator]);

        return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter);
    }

    /**
     * @param string[]     $namespaces        List of namespaces that are handled with attribute mapping
     * @param string[]     $directories       List of directories to look for classes with attributes
     * @param string[]     $managerParameters List of parameters that could which object manager name
     *                                        your bundle uses. This compiler pass will automatically
     *                                        append the parameter name for the default entity manager
     *                                        to this list.
     * @param string|false $enabledParameter  Service container parameter that must be present to
     *                                        enable the mapping. Set to false to not do any check,
     *                                        optional.
     */
    public static function createAttributeMappingDriver(array $namespaces, array $directories, array $managerParameters = [], string|false $enabledParameter = false): self
    {
        $driver = new Definition(AttributeDriver::class, [$directories]);

        return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter);
    }

    /**
     * @param string[]     $namespaces        List of namespaces that are handled with static php mapping
     * @param string[]     $directories       List of directories to look for static php mapping files
     * @param string[]     $managerParameters List of parameters that could which object manager name
     *                                        your bundle uses. This compiler pass will automatically
     *                                        append the parameter name for the default entity manager
     *                                        to this list.
     * @param string|false $enabledParameter  Service container parameter that must be present to
     *                                        enable the mapping. Set to false to not do any check,
     *                                        optional.
     */
    public static function createStaticPhpMappingDriver(array $namespaces, array $directories, array $managerParameters = [], string|false $enabledParameter = false): self
    {
        $driver = new Definition(StaticPHPDriver::class, [$directories]);

        return new DoctrineOrmMappingsPass($driver, $namespaces, $managerParameters, $enabledParameter);
    }
}
