<?php

namespace NoahVet\Reef\Infrastructure\Business;

use NoahVet\Reef\Iterator\Factory\ReefApiIteratorFactoryInterface;

/**
 * Énumérateur paginé générique pour les endpoints membres d'équipe du BSM.
 *
 * Gère la pagination et le découpage en chunks des filtres par ID d'entité automatiquement
 * via {@see ReefApiIteratorFactoryInterface}.
 *
 * @template TUser of object
 */
abstract class AbstractTeamMembersEnumerator
{
    /**
     * @param class-string             $endpointClass     Classe endpoint Jane, ex. GetAllVeterinaryEstablishmentUser::class
     * @param class-string<TUser>      $responseClass     Classe DTO de réponse Jane, ex. VeterinaryEstablishmentUserResponse::class
     * @param string                   $entityIdFilterKey Paramètre de filtre par ID d'entité, ex. 'veterinaryEstablishmentId'
     * @param \Closure(object): string $entityIdResolver  Extrait l'UUID depuis l'entité de base, ex. fn($e) => $e->getId()
     * @param \Closure(TUser): ?string $entityIdExtractor Extrait l'ID d'entité depuis une réponse membre d'équipe
     */
    public function __construct(
        protected readonly object $client,
        protected readonly ReefApiIteratorFactoryInterface $iteratorFactory,
        protected readonly string $endpointClass,
        protected readonly string $responseClass,
        protected readonly string $entityIdFilterKey,
        protected readonly \Closure $entityIdResolver,
        protected readonly \Closure $entityIdExtractor,
    ) {
    }

    /**
     * @param array<string, string|list<string>> $filters
     *
     * @return \Generator<TUser>
     */
    public function enumerateAll(array $filters = [], int $querySize = 50): \Generator
    {
        $iterator = $this->iteratorFactory->create(
            $this->client,
            $this->endpointClass,
            $this->responseClass,
            $filters,
            $querySize,
        );

        $iterator->rewind();

        foreach ($iterator as $item) {
            /* @phpstan-ignore notIdentical.alwaysTrue */
            if (null !== $item) {
                yield $item;
            }
        }
    }

    /**
     * Retourne les membres d'équipe groupés par ID d'entité, filtrés sur ceux actifs à $now.
     *
     * Les grands tableaux d'IDs sont automatiquement découpés en chunks par le décorateur d'itérateur.
     *
     * @param list<object> $entities
     *
     * @return array<string, list<TUser>>
     */
    public function getTeamMembers(array $entities, int $querySize = 50, ?\DateTimeImmutable $now = null): array
    {
        $nowStr = ($now ?? new \DateTimeImmutable())->format('c');
        $uuids = \array_map(fn (object $entity): string => ($this->entityIdResolver)($entity), $entities);

        $filters = \array_merge(
            [$this->entityIdFilterKey => $uuids],
            $this->buildDateFilters($nowStr),
        );

        $iterator = $this->iteratorFactory->create(
            $this->client,
            $this->endpointClass,
            $this->responseClass,
            $filters,
            $querySize,
            $this->entityIdFilterKey,
        );

        $result = [];

        $iterator->rewind();

        foreach ($iterator as $item) {
            /* @phpstan-ignore identical.alwaysFalse */
            if (null === $item) {
                continue;
            }

            $entityId = ($this->entityIdExtractor)($item);

            if (null !== $entityId) {
                $result[$entityId][] = $item;
            }
        }

        return $result;
    }

    /**
     * Filtres de date appliqués aux membres d'équipe pour ne retourner que les membres actifs.
     * Peut être surchargé pour désactiver le filtrage par date (ex. endpoint sans support date).
     *
     * @return array<string, list<string>>
     */
    protected function buildDateFilters(string $nowStr): array
    {
        return [
            'toEntryDate' => [$nowStr, 'null'],
            'fromExitDate' => [$nowStr, 'null'],
        ];
    }
}
