<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Fixtures\Doctrine;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\ManagerRegistry;

/**
 * Builds the bundle's own repositories the way DoctrineBundle does.
 *
 * Doctrine's default factory constructs every repository with
 * `($em, $metadata)`, which is wrong for the `ServiceEntityRepository`
 * subclasses this bundle uses — they take a ManagerRegistry and name their
 * entity themselves. Without this, `$em->getRepository(User::class)` blows up
 * in tests even though it works perfectly in the application.
 */
final class OrmRepositoryFactory implements RepositoryFactory
{
    /** @var array<string, EntityRepository> */
    private array $repositories = [];

    public function __construct(private readonly ManagerRegistry $registry)
    {
    }

    public function getRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository
    {
        return $this->repositories[$entityName] ??= $this->create($entityManager, $entityName);
    }

    private function create(EntityManagerInterface $entityManager, string $entityName): EntityRepository
    {
        $metadata = $entityManager->getClassMetadata($entityName);
        $repositoryClass = $metadata->customRepositoryClassName
            ?? $entityManager->getConfiguration()->getDefaultRepositoryClassName();

        if (is_a($repositoryClass, ServiceEntityRepository::class, true)) {
            // A handful of repositories take collaborators beyond the registry
            // (EditionRepository wants the decisions facade). There is no
            // container here to resolve those from, so those fall back to a
            // plain EntityRepository: find()/findBy() still work, the
            // repository's own methods are simply not available in tests.
            if (1 === new \ReflectionClass($repositoryClass)->getConstructor()?->getNumberOfRequiredParameters()) {
                return new $repositoryClass($this->registry);
            }

            return new EntityRepository($entityManager, $metadata);
        }

        \assert(is_a($repositoryClass, EntityRepository::class, true));

        return new $repositoryClass($entityManager, $metadata);
    }
}
