<?php

declare(strict_types=1);

namespace Storage\Doctrine\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @template T of object
 *
 * @template-extends EntityRepository<T>
 */
abstract class BaseRepository extends EntityRepository implements ServiceEntityRepositoryInterface
{
    /** @phpstan-param ClassMetadata<T> $metadata */
    public function __construct(
        protected EntityManagerInterface $em,
        ?ClassMetadata $metadata = null,
        ?ManagerRegistry $registry = null,
    ) {
        if ($registry instanceof ManagerRegistry) {
            /** @var EntityManager $manager */
            $manager = $registry->getManagerForClass($this->getEntityClassName());

            parent::__construct($manager, $manager->getClassMetadata($this->getEntityClassName()));
        } elseif ($this->em instanceof EntityManager && $metadata instanceof ClassMetadata) {
            parent::__construct($this->em, $metadata);
        } else {
            throw new \RuntimeException('Failed to initialize repository.');
        }
    }

    /**
     * @return array<T>
     */
    #[\Override]
    public function findAll(bool $includeSoftDeletedRecords = false): array
    {
        $isSoftDeletedFilterEnabled = $this->em->getFilters()->isEnabled('soft_deletable');
        if ($includeSoftDeletedRecords) {
            $this->disableSoftDeletedFilter();
        }

        /** @var array<T> $results */
        $results = parent::findAll();

        $isSoftDeletedFilterEnabled ? $this->enableSoftDeletedFilter() : $this->disableSoftDeletedFilter();

        return $results;
    }

    /**
     * @param int|string $id
     *
     * @return ?T
     */
    public function findById($id, bool $includeSoftDeletedRecords = false)
    {
        $isSoftDeletedFilterEnabled = $this->em->getFilters()->isEnabled('soft_deletable');

        if ($includeSoftDeletedRecords) {
            $this->disableSoftDeletedFilter();
        }

        /** @var ?T $entity */
        $entity = $this->find($id);

        $isSoftDeletedFilterEnabled ? $this->enableSoftDeletedFilter() : $this->disableSoftDeletedFilter();

        return $entity;
    }

    /**
     * @param array<string, mixed> $criteria
     * @param array<string, mixed>|null $orderBy
     * @param mixed|null $limit
     * @param mixed|null $offset
     *
     * @return array<T>
     */
    #[\Override]
    public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null, bool $includeSoftDeletedRecords = false): array
    {
        $isSoftDeletedFilterEnabled = $this->em->getFilters()->isEnabled('soft_deletable');

        if ($includeSoftDeletedRecords) {
            $this->disableSoftDeletedFilter();
        }

        /** @var array<T> $entity */
        $entity = parent::findBy($criteria, $orderBy, $limit, $offset);

        $isSoftDeletedFilterEnabled ? $this->enableSoftDeletedFilter() : $this->disableSoftDeletedFilter();

        return $entity;
    }

    /**
     * @param array<string, mixed> $criteria
     * @param array<string, mixed>|null $orderBy
     *
     * @return T|null
     */
    #[\Override]
    public function findOneBy(array $criteria, ?array $orderBy = null, bool $includeSoftDeletedRecords = false): ?object
    {
        $isSoftDeletedFilterEnabled = $this->em->getFilters()->isEnabled('soft_deletable');

        if ($includeSoftDeletedRecords) {
            $this->disableSoftDeletedFilter();
        }

        /** @var T|null $entity */
        $entity = parent::findOneBy($criteria, $orderBy);

        $isSoftDeletedFilterEnabled ? $this->enableSoftDeletedFilter() : $this->disableSoftDeletedFilter();

        return $entity;
    }

    /**
     * @param T $entity
     *
     * @throws ORMException
     * @throws OptimisticLockException
     */
    public function save($entity, bool $flush = true): void
    {
        $this->em->persist($entity);
        if ($flush) {
            $this->em->flush();
        }
    }

    /**
     * @throws ORMException
     * @throws OptimisticLockException
     */
    public function flush(): void
    {
        $this->em->flush();
    }

    /**
     * @param T $entity
     *
     * @throws ORMException
     * @throws OptimisticLockException
     */
    public function remove($entity, bool $flush = true): void
    {
        $this->em->remove($entity);
        if ($flush) {
            $this->em->flush();
        }
    }

    public function enableSoftDeletedFilter(): void
    {
        if (!$this->em->getFilters()->isEnabled('soft_deletable')) {
            $this->em->getFilters()->enable('soft_deletable');
        }
    }

    public function disableSoftDeletedFilter(): void
    {
        if ($this->em->getFilters()->isEnabled('soft_deletable')) {
            $this->em->getFilters()->disable('soft_deletable');
        }
    }

    /**
     * @return class-string<T>
     */
    abstract protected function getEntityClassName(): string;
}
