<?php

declare(strict_types=1);

namespace App\Administration\Application\Service\Cache;

use App\Administration\Application\Dto\PaginatedResult;

use function json_encode;

use const JSON_THROW_ON_ERROR;

use function ksort;
use function md5;
use function sprintf;

use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

/**
 * Service cache-aside pour les requêtes paginées.
 *
 * Chaque entrée est taguée par type d'entité + tenant,
 * permettant une invalidation ciblée lors de mutations.
 */
final readonly class PaginatedQueryCache
{
    public function __construct(
        private TagAwareCacheInterface $paginatedQueriesCache,
    ) {
    }

    /**
     * @template T
     *
     * @param array<string, mixed>           $params Filtres + page + limit
     * @param callable(): PaginatedResult<T> $loader Fonction qui exécute la requête SQL
     *
     * @return PaginatedResult<T>
     */
    public function getOrLoad(
        string $entityType,
        string $tenantId,
        array $params,
        callable $loader,
    ): PaginatedResult {
        $key = $this->buildKey($entityType, $tenantId, $params);
        $tag = sprintf('query_%s_%s', $entityType, $tenantId);

        /* @var PaginatedResult<T> */
        return $this->paginatedQueriesCache->get(
            $key,
            static function (ItemInterface $item) use ($tag, $loader): PaginatedResult {
                $item->tag([$tag]);

                return $loader();
            },
        );
    }

    public function invalidate(string $entityType, string $tenantId): void
    {
        $this->paginatedQueriesCache->invalidateTags(
            [sprintf('query_%s_%s', $entityType, $tenantId)],
        );
    }

    /**
     * @param array<string, mixed> $params
     */
    private function buildKey(string $entityType, string $tenantId, array $params): string
    {
        ksort($params);

        return sprintf('query_%s_%s_%s', $entityType, $tenantId, md5(json_encode($params, JSON_THROW_ON_ERROR)));
    }
}
