<?php

declare(strict_types=1);

namespace App\Module\Catalog\Infrastructure\Doctrine;

use App\Module\Catalog\Domain\Entity\Category;
use App\Module\Catalog\Domain\Repository\CategoryRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @extends ServiceEntityRepository<Category>
 */
class DoctrineCategoryRepository extends ServiceEntityRepository implements CategoryRepositoryInterface
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Category::class);
    }

    public function findById(int $id): ?Category
    {
        return $this->find($id);
    }

    public function save(Category $category): void
    {
        $this->getEntityManager()->persist($category);
        $this->getEntityManager()->flush();
    }

    public function existsActiveByCode(string $code, ?int $excludeId = null): bool
    {
        $qb = $this->createQueryBuilder('c')
            ->select('1')
            ->andWhere('c.code = :code')
            ->andWhere('c.deletedAt IS NULL')
            ->setParameter('code', $code)
            ->setMaxResults(1)
        ;

        if (null !== $excludeId) {
            $qb->andWhere('c.id != :excludeId')->setParameter('excludeId', $excludeId);
        }

        return [] !== $qb->getQuery()->getResult();
    }

    public function createListQueryBuilder(
        bool $includeDeleted = false,
        ?string $typeCode = null,
        ?string $nameSearch = null,
        array $typeIds = [],
    ): QueryBuilder {
        // Eager-load de la collection categoryTypes (ManyToMany) : embarquee a la
        // serialisation -> on la fetch-joine pour eviter un N+1 par categorie. Le
        // provider enveloppe la requete dans un Paginator(fetchJoinCollection: true),
        // compatible avec ce fetch-join to-many.
        $qb = $this->createQueryBuilder('c')
            ->leftJoin('c.categoryTypes', 'cte')
            ->addSelect('cte')
            ->orderBy('c.name', 'ASC')
        ;

        if (!$includeDeleted) {
            $qb->andWhere('c.deletedAt IS NULL');
        }

        // Filtre `?typeCode=` : la categorie doit PORTER ce code de type (RG-2.10,
        // multi-select fournisseur). Sous-requete EXISTS correlee pour ne PAS
        // restreindre la collection eager-loadee `cte` (sinon les autres types de
        // la categorie disparaitraient du JSON) et eviter les lignes dupliquees.
        if (null !== $typeCode) {
            $sub = $this->getEntityManager()->createQueryBuilder()
                ->select('1')
                ->from(Category::class, 'c_tc')
                ->join('c_tc.categoryTypes', 'ct_tc')
                ->where('c_tc = c')
                ->andWhere('ct_tc.code = :typeCode')
            ;
            $qb->andWhere($qb->expr()->exists($sub->getDQL()))
                ->setParameter('typeCode', $typeCode)
            ;
        }

        // Filtre `?typeId[]=` (liste admin) : la categorie porte AU MOINS UN des
        // types coches (OR). Meme strategie EXISTS correlee que `typeCode`.
        if ([] !== $typeIds) {
            $sub = $this->getEntityManager()->createQueryBuilder()
                ->select('1')
                ->from(Category::class, 'c_ti')
                ->join('c_ti.categoryTypes', 'ct_ti')
                ->where('c_ti = c')
                ->andWhere('ct_ti.id IN (:typeIds)')
            ;
            $qb->andWhere($qb->expr()->exists($sub->getDQL()))
                ->setParameter('typeIds', $typeIds)
            ;
        }

        // Filtre `?name=` (liste admin) : recherche partielle case-insensitive.
        if (null !== $nameSearch && '' !== $nameSearch) {
            $qb->andWhere('LOWER(c.name) LIKE :nameSearch')
                ->setParameter('nameSearch', '%'.mb_strtolower($nameSearch).'%')
            ;
        }

        return $qb;
    }
}
