<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Product\Repository;

use CpCreation\VitiCore\Product\Model\ProductCollection;
use CpCreation\VitiCore\Product\Model\ProductCollectionItem;
use CpCreation\VitiCore\Repository\BaseRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;

class ProductCollectionRepository extends BaseRepository
{
    /**
     * @throws \ReflectionException
     */
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, ProductCollection::class);
    }

    public function findOneByCode(string $code): ?ProductCollection
    {
        return $this->findOneBy(['code' => $code]);
    }

    public function save(object $object): void
    {
        if (!$object instanceof ProductCollection) {
            throw new \InvalidArgumentException(\sprintf('Une collection de produits était attendue, "%s" reçu.', get_debug_type($object)));
        }

        $object->normalizeItemPositions();
        $entityManager = $this->getEntityManager();

        $entityManager->wrapInTransaction(function (EntityManagerInterface $entityManager) use ($object): void {
            // Gedmo borne un déplacement avec le MAX(position) courant. Réparer d'abord
            // les doublons et les trous lui redonne une série dense sur laquelle travailler.
            $persistedPositions = $this->normalizePersistedPositions($object);
            $unitOfWork = $entityManager->getUnitOfWork();

            foreach ($object->getItems() as $item) {
                if (!$item instanceof ProductCollectionItem) {
                    continue;
                }

                $persistedPosition = $persistedPositions[$item->getId()->toString()] ?? null;

                if (null !== $persistedPosition) {
                    // La réparation SQL change la référence persistée sans écraser la
                    // position soumise, que Doctrine doit encore transmettre à Gedmo.
                    $unitOfWork->setOriginalEntityProperty(spl_object_id($item), 'position', $persistedPosition);
                }
            }

            $entityManager->persist($object);
        });
    }

    /**
     * @return array<string, int>
     */
    private function normalizePersistedPositions(ProductCollection $collection): array
    {
        $connection = $this->getEntityManager()->getConnection();
        $rows = $connection->fetchAllAssociative(<<<'SQL'
SELECT id, position
FROM product_collection_product
WHERE collection_id = ?
ORDER BY
    CASE WHEN position IS NULL THEN 1 ELSE 0 END ASC,
    position ASC,
    created_at ASC,
    id ASC
SQL, [$collection->getId()->toString()]);
        $positions = [];

        foreach ($rows as $position => $row) {
            $id = (string) $row['id'];
            $positions[$id] = $position;

            if ((int) $row['position'] === $position && null !== $row['position']) {
                continue;
            }

            $connection->executeStatement(
                'UPDATE product_collection_product SET position = ? WHERE id = ?',
                [$position, $id],
            );
        }

        return $positions;
    }
}
