<?php

declare(strict_types=1);

namespace App\Module\Catalog\Application\Service;

use App\Module\Catalog\Domain\Repository\CategoryRepositoryInterface;
use Symfony\Component\String\Slugger\AsciiSlugger;

/**
 * Genere le code technique stable d'une Category a partir de son nom (ERP-78).
 *
 * Regle (decision produit 02/06) : `code` est obligatoire et auto-genere — un
 * slug MAJUSCULE du nom, sans accent, separateurs non alphanumeriques reduits a
 * `_`, borne a 50 caracteres (longueur colonne). Exemples :
 *  - « Distributeur »      -> DISTRIBUTEUR
 *  - « Agro-alimentaire »  -> AGRO_ALIMENTAIRE
 *  - « Transport/Logistique » -> TRANSPORT_LOGISTIQUE
 *
 * Le code est FIGE a la creation (jamais recalcule sur renommage) afin de rester
 * une cle deterministe stable entre environnements (RG-1.03 / RG-1.29 cote M1).
 *
 * Unicite : l'index partiel `uq_category_code` (WHERE deleted_at IS NULL) impose
 * l'unicite parmi les categories actives. Deux noms distincts peuvent produire
 * le meme slug (« Agro alimentaire » / « Agro-alimentaire ») : on suffixe alors
 * le code par `_2`, `_3`... jusqu'a obtenir un code libre.
 */
final class CategoryCodeGenerator
{
    /** Longueur maximale de la colonne `category.code`. */
    private const int MAX_LENGTH = 50;

    private readonly AsciiSlugger $slugger;

    public function __construct(
        private readonly CategoryRepositoryInterface $categoryRepository,
    ) {
        $this->slugger = new AsciiSlugger();
    }

    /**
     * Slug brut (sans garantie d'unicite) — utile pour les seeds deterministes.
     */
    public function slugify(string $name): string
    {
        $slug = $this->slugger->slug($name, '_')->upper()->toString();

        // Borne a la longueur colonne, puis retire un eventuel `_` terminal
        // introduit par la troncature.
        $slug = substr($slug, 0, self::MAX_LENGTH);
        $slug = trim($slug, '_');

        // Garde-fou : un nom uniquement compose de caracteres non alphanumeriques
        // (theorique, le nom est NotBlank + Length>=2) donnerait un slug vide.
        return '' === $slug ? 'CATEGORY' : $slug;
    }

    /**
     * Code unique parmi les categories actives : slug du nom, suffixe `_N` en
     * cas de collision. `$excludeId` ignore la categorie courante (PATCH).
     */
    public function generateUnique(string $name, ?int $excludeId = null): string
    {
        $base      = $this->slugify($name);
        $candidate = $base;
        $suffix    = 2;

        while ($this->categoryRepository->existsActiveByCode($candidate, $excludeId)) {
            $suffixStr = '_'.$suffix;
            // Retronque la base pour que `base + suffixe` tienne dans 50 caracteres.
            $candidate = substr($base, 0, self::MAX_LENGTH - strlen($suffixStr)).$suffixStr;
            ++$suffix;
        }

        return $candidate;
    }
}
