<?php

declare(strict_types=1);

namespace App\Module\Technique\Domain\Repository;

use App\Module\Technique\Domain\Entity\Provider;
use Doctrine\ORM\QueryBuilder;

interface ProviderRepositoryInterface
{
    public function findById(int $id): ?Provider;

    public function save(Provider $provider): void;

    /**
     * Restreint un QueryBuilder de liste aux prestataires rattaches au site donne
     * (relation DIRECTE provider.sites). Sert le cloisonnement par site pilote par
     * l'utilisateur (RG-3.17, § 2.13) : le ProviderProvider resout le site courant
     * (CurrentSiteProvider) puis appelle cette methode quand l'user n'a pas
     * `sites.bypass_scope`. Decouple ainsi la DECISION (Provider, qui connait
     * l'user) du DQL (repository, qui ne connait que l'id de site).
     *
     * Sous-requete IN (et non JOIN sur la M2M) pour ne pas perturber le
     * DISTINCT / ORDER BY / pagination du QueryBuilder de selection — meme parti
     * pris que les filtres ?categoryCode / ?siteId. Applique AVANT la pagination
     * (le COUNT du Paginator reflete alors le perimetre de l'user).
     */
    public function applySiteScope(QueryBuilder $qb, int $siteId): void;

    /**
     * Construit un QueryBuilder de liste pour le repertoire prestataires.
     * - Exclut toujours les prestataires soft-deletes (deleted_at IS NOT NULL, RG-3.16).
     * - Archivage (RG-3.16) :
     *     - $archivedOnly = true  -> uniquement les archives (is_archived = true) ;
     *     - sinon $includeArchived = true -> actifs + archives (echappatoire) ;
     *     - sinon (defaut) -> uniquement les actifs (is_archived = false).
     *   $archivedOnly a la priorite sur $includeArchived.
     * - Tri par defaut : companyName ASC (RG-3.16).
     * - $search : recherche fuzzy insensible a la casse sur companyName + les
     *   contacts lies (firstName / lastName / email) via sous-requete.
     *   Metacaracteres LIKE echappes. Ignore si null/vide.
     * - $categoryCodes : restreint aux prestataires possedant au moins une
     *   categorie dont le code est dans la liste (OR). Liste vide = pas de filtre.
     * - $siteIds : restreint aux prestataires rattaches a l'un des sites donnes
     *   (OR — RG-3.03, relation DIRECTE provider.sites). Liste vide = pas de filtre.
     *
     * Filtrage centralise ICI (et non dans le provider/controller) pour que la
     * liste paginee et l'export partagent strictement la meme logique de selection
     * (miroir M2).
     *
     * Contrat = SELECTION uniquement (filtres + tri). Aucun fetch-join to-many :
     * l'hydratation des collections affichees est deleguee a
     * {@see self::hydrateListCollections()} pour ne pas imposer le cout d'un
     * produit cartesien aux chemins non pagines (§ 2.12, cf. M1/ERP-100, M2).
     *
     * NB : le cloisonnement par site pilote par l'utilisateur (RG-3.17, § 2.13) est
     * applique en AMONT par le ProviderProvider (ERP-134), pas par ce QueryBuilder
     * (qui ne connait pas l'user courant).
     *
     * @param list<string> $categoryCodes
     * @param list<int>    $siteIds
     */
    public function createListQueryBuilder(
        bool $includeArchived = false,
        ?string $search = null,
        array $categoryCodes = [],
        array $siteIds = [],
        bool $archivedOnly = false,
    ): QueryBuilder;

    /**
     * Hydrate en lot les collections affichees par le repertoire (categories puis
     * sites — relation DIRECTE provider.sites, RG-3.03) sur un jeu de prestataires
     * DEJA charges, via l'identity map Doctrine (memes instances). A appeler apres
     * une selection bornee (page courante ou jeu d'export) pour eviter le N+1 a la
     * serialisation, sans imposer de fetch-join au QueryBuilder de selection
     * (anti N+1, § 2.12).
     *
     * Charge les categories et les sites en DEUX requetes distinctes (et non un
     * double fetch-join) pour ne pas multiplier categories x sites en un seul
     * produit cartesien.
     *
     * @param list<Provider> $providers
     */
    public function hydrateListCollections(array $providers): void;

    /**
     * Hydrate en lot la collection `contacts` sur un jeu de prestataires DEJA
     * charges (memes instances via l'identity map). Reservee aux chemins qui ont
     * besoin du contact principal (export) : la LISTE paginee n'embarque pas les
     * contacts (§ 2.12), d'ou une methode dediee plutot qu'une passe supplementaire
     * dans {@see self::hydrateListCollections()}.
     *
     * @param list<Provider> $providers
     */
    public function hydrateContacts(array $providers): void;
}
