/**
 * Contrat d'écran de l'envoi d'un bénéficiaire vers TEJ.
 *
 * ────────────────────────────────────────────────────────────────────────────
 * CE QUE CET ÉCRAN DÉCLENCHE
 * ────────────────────────────────────────────────────────────────────────────
 * `POST /fournisseurs/{beneficiaire}/tej` — la PREMIÈRE écriture réelle du
 * manager vers un service fiscal de l'État. Jamais un job, jamais un cron,
 * jamais un observer : uniquement le clic d'un humain sur cet écran, après une
 * confirmation qui lui MONTRE ce qui part.
 *
 * ────────────────────────────────────────────────────────────────────────────
 * TROIS FAITS DE LA CAPTURE DU 2026-08-06 QUI DICTENT CETTE UI
 * ────────────────────────────────────────────────────────────────────────────
 * 1. TEJ NE REFUSE JAMAIS UN DOUBLON. Un ré-envoi répond 200 « Création
 *    effectuée avec succès » et ne crée rien : c'est un UPSERT silencieux, clé
 *    = `typeIdentifier + identifier`. Aucun 409, aucun 400. L'écran ne peut
 *    donc RIEN apprendre d'un code d'erreur : c'est à lui d'afficher l'état
 *    connu et d'exiger un geste explicite avant un second envoi.
 * 2. Le même POST sert à MODIFIER — il n'existe ni PUT ni PATCH côté TEJ. Un
 *    renvoi n'est pas un doublon inoffensif : c'est une mise à jour de la
 *    fiche fiscale déjà déposée.
 * 3. L'identifiant part SANS séparateur (`1184751K`, jamais `0000000/S`). La
 *    normalisation appartient au SERVEUR (`TejBeneficiaireService::
 *    normaliserIdentifiant()`). L'écran ne la refait pas — il AFFICHE la forme
 *    normalisée que le serveur lui donne, pour que l'utilisateur confirme la
 *    chaîne exacte qui voyagera, pas celle qu'il a saisie.
 *
 * ────────────────────────────────────────────────────────────────────────────
 * PROPS ATTENDUES DU SERVEUR (le PHP est écrit en parallèle)
 * ────────────────────────────────────────────────────────────────────────────
 * Sur la page `fournisseurs/index` :
 *
 *   'tej' => [
 *       'ecriture_active'     => bool,          // cf. TejEcritureProp
 *       'raison_indisponible' => string|null,
 *   ],
 *   'can' => [ ..., 'tej' => bool ],            // permission `fournisseur.tej`
 *
 * Sur chaque ligne de `suppliers[]` :
 *
 *   'tej' => [                                  // cf. FicheTej
 *       'external_id'     => int|null,
 *       'synced_at'       => string|null,       // ISO 8601
 *       'type_identifier' => int|null,          // référentiel TEJ, 1..5
 *       'identifiant'     => string|null,       // forme NORMALISÉE
 *       'envoyable'       => bool,
 *       'blocage'         => string|null,
 *   ],
 */

/**
 * Référentiel `typeIdentifier` de TEJ, relevé le 2026-08-06.
 * Seul le type 2 (CIN) exige une date de naissance à l'envoi.
 */
export type TypeIdentifierTej = 1 | 2 | 3 | 4 | 5;

/**
 * Le CIN, seul type dont TEJ exige la date de naissance (`birthDate` du corps).
 * Nommé plutôt qu'écrit `2` sur les trois écrans qui le testent : un nombre nu
 * n'apprend rien au lecteur, et le référentiel est documenté juste au-dessus.
 */
export const TYPE_IDENTIFIER_CIN: TypeIdentifierTej = 2;

/**
 * Le matricule fiscal — le SEUL type sur lequel le registre national remplit
 * (nom, activité, adresse, e-mail, téléphone). Nommé pour la même raison que le
 * précédent : `1` nu, dans une URL de consultation, n'apprend rien au lecteur.
 */
export const TYPE_IDENTIFIER_MATRICULE: TypeIdentifierTej = 1;

/**
 * La fiche SOUMISE sera-t-elle identifiée chez TEJ par son CIN — donc soumise à
 * `birthDate` ?
 *
 * Miroir EXACT de `BeneficiaireRequest::identifieeParUnCin()`, qui reproduit
 * lui-même l'ordre de repli de `TejBeneficiaireService::typeIdentifierPour()` :
 * le matricule fiscal désigne le tiers EN PRIORITÉ (typeIdentifier 1, aucune
 * date de naissance attendue), le CIN ne prend le relais qu'à défaut
 * (typeIdentifier 2, date exigée).
 *
 * ⚠️ Le calcul porte sur les IDENTIFIANTS, jamais sur `type_personne`. Une
 * personne physique qui porte un matricule fiscal part chez TEJ sous ce
 * matricule et sans date : lui réclamer la date bloquerait une création
 * légitime. Inversement, un formulaire qui masquerait le champ selon
 * `type_personne` finirait par cacher une donnée que le serveur exige — une
 * impasse, exactement celle que le dialogue d'envoi a déjà connue.
 *
 * `blank()` / `filled()` de Laravel ignorent les espaces : `.trim()` ici fait la
 * même chose, pour que les deux règles répondent sur les mêmes saisies.
 */
export function estIdentifieeParCin(matriculeFiscal: string | null | undefined, cin: string | null | undefined): boolean {
    return (matriculeFiscal ?? '').trim() === '' && (cin ?? '').trim() !== '';
}

/**
 * État TEJ d'une fiche, sérialisé par le serveur à partir des DEUX colonnes
 * déjà en base (`beneficiaires.tej_external_id`, `beneficiaires.tej_synced_at`).
 *
 * ⚠️ Lecture BASE uniquement : rendre cet écran ne doit déclencher aucun appel
 * réseau vers TEJ (ce serait un `verify` par ligne à chaque affichage).
 */
export interface FicheTej {
    /**
     * `beneficiaires.tej_external_id` — l'`id` de la RELATION rendu par TEJ
     * (ex. 2320024), à ne pas confondre avec `taxpayer.id` (le contribuable).
     * Non nul sans `synced_at` = la fiche a été rapprochée du registre sans que
     * le manager ait jamais posté.
     */
    external_id: number | null;

    /**
     * `beneficiaires.tej_synced_at` en ISO 8601. Non nul = C'EST NOUS qui avons
     * envoyé, à cette date.
     */
    synced_at: string | null;

    /** Référentiel TEJ. `null` = le serveur n'a pas su qualifier l'identité. */
    type_identifier: TypeIdentifierTej | null;

    /**
     * L'identifiant tel qu'il partira dans le corps, déjà NORMALISÉ par le
     * serveur (séparateurs et espaces retirés, majuscules). C'est cette chaîne
     * que la confirmation affiche — pas la valeur brute de la fiche.
     */
    identifiant: string | null;

    /**
     * Faux quand le serveur ne peut pas construire un corps valide : identité
     * fiscale absente, ou fiche CIN sans date de naissance (le type 2 l'exige,
     * et un POST avec `birthDate: null` serait accepté sans être correct).
     */
    envoyable: boolean;

    /**
     * Pourquoi ce n'est pas envoyable — phrase courte destinée à l'écran.
     * Ne doit porter NI identifiant, NI jeton : elle s'affiche telle quelle.
     */
    blocage: string | null;
    /** Code machine du blocage — voir TejEnvoiException::$codeRefus. */
    blocage_code: string | null;
}

/** Verrou serveur, prop `tej` de la page. */
export interface TejEcritureProp {
    /**
     * Reflet de l'unique interrupteur du module côté serveur
     * (`config('tej.enabled')`, et l'absence de marche à blanc). Faux = le
     * bouton est visiblement désactivé sur tout l'écran : le serveur refuserait
     * de toute façon, autant le dire avant le clic plutôt que par un toast
     * d'erreur après.
     */
    ecriture_active: boolean;

    /**
     * Phrase courte expliquant l'extinction, affichée en infobulle. Comme
     * `blocage` : aucun identifiant, aucun jeton, aucun nom de variable
     * d'environnement porteur de secret.
     */
    raison_indisponible: string | null;
}

/**
 * État lisible d'un coup d'œil, dérivé des deux colonnes.
 *
 * - `inconnu`       : le serveur n'a pas fourni le bloc `tej` (déploiement
 *                     partiel) — on désactive plutôt que d'envoyer à l'aveugle.
 * - `jamais_envoye` : ni envoi, ni rapprochement.
 * - `envoye`        : `synced_at` — nous l'avons envoyé, à cette date.
 * - `deja_connu`    : `external_id` sans `synced_at` — TEJ le connaît déjà,
 *                     mais pas parce que nous l'avons posté.
 */
export type EtatTej = 'inconnu' | 'jamais_envoye' | 'envoye' | 'deja_connu';

export function etatTej(fiche: FicheTej | null | undefined): EtatTej {
    if (!fiche) return 'inconnu';
    if (fiche.synced_at) return 'envoye';
    if (fiche.external_id !== null) return 'deja_connu';
    return 'jamais_envoye';
}

/**
 * Un envoi sur un état `envoye` ou `deja_connu` n'est pas une création : c'est
 * une MISE À JOUR de la fiche déposée (fait n° 2). L'écran exige alors une
 * confirmation supplémentaire et poste `forcer: true`, qui alimente le
 * `$forcerMiseAJour` du service.
 */
export function estMiseAJour(etat: EtatTej): boolean {
    return etat === 'envoye' || etat === 'deja_connu';
}

/** Libellé du référentiel `typeIdentifier`, via le catalogue de traductions. */
export function libelleTypeIdentifier(type: TypeIdentifierTej | null, t: (namespace: string, key: string, fallback?: string) => string): string {
    switch (type) {
        case 1:
            return t('fournisseurs', 'tej_type_1', 'Matricule fiscal');
        case 2:
            return t('fournisseurs', 'tej_type_2', 'CIN');
        case 3:
            return t('fournisseurs', 'tej_type_3', 'Passeport');
        case 4:
            return t('fournisseurs', 'tej_type_4', 'Carte de séjour');
        case 5:
            return t('fournisseurs', 'tej_type_5', 'Identifiant étranger');
        default:
            return t('fournisseurs', 'tej_type_inconnu', 'Type non déterminé');
    }
}

/**
 * Pourquoi le bouton est éteint, ou `null` s'il est cliquable.
 *
 * Ordre volontaire : le verrou SERVEUR d'abord. Une fiche incomplète alors que
 * l'écriture est coupée doit dire « écriture désactivée », pas « fiche
 * incomplète » — sinon l'utilisateur corrige une fiche pour rien.
 */
export function motifIndisponibilite(
    fiche: FicheTej | null | undefined,
    ecriture: TejEcritureProp,
    t: (namespace: string, key: string, fallback?: string) => string,
): string | null {
    if (!ecriture.ecriture_active) {
        return ecriture.raison_indisponible ?? t('fournisseurs', 'tej_ecriture_off', "L'envoi vers TEJ est désactivé côté serveur.");
    }
    if (!fiche) {
        return t('fournisseurs', 'tej_etat_absent', "L'état TEJ de cette fiche n'a pas été fourni par le serveur.");
    }
    if (!fiche.envoyable) {
        return fiche.blocage ?? t('fournisseurs', 'tej_non_envoyable', "Cette fiche ne peut pas être envoyée en l'état.");
    }
    return null;
}

/**
 * Code MACHINE du seul blocage que cet écran sait lever lui-même.
 *
 * Il vient de `TejEnvoiException::$codeRefus`, exposé par
 * `TejBeneficiaireService::etatFiche()` sous `blocage_code`. La version
 * précédente cherchait le fragment « date de naissance » DANS la phrase
 * française du blocage : ça marchait, mais ça se serait cassé en silence à la
 * première reformulation du message côté serveur. Un code machine ne se
 * reformule pas.
 */
const CODE_BLOCAGE_DATE = 'date_naissance_requise';

/**
 * Ce blocage est-il CELUI QUE L'ÉCRAN SAIT LEVER, c'est-à-dire la date de
 * naissance qu'un tiers CIN doit porter et que la base ne stocke pas encore ?
 *
 * Le type CIN reste vérifié en plus du code : c'est le seul type auquel TEJ
 * réclame une date, et le champ que le dialogue ouvre n'a de sens que là.
 */
export function dateNaissanceRequise(fiche: FicheTej | null | undefined): boolean {
    if (!fiche || fiche.envoyable) return false;
    if (fiche.type_identifier !== TYPE_IDENTIFIER_CIN) return false;

    return fiche.blocage_code === CODE_BLOCAGE_DATE;
}

/**
 * Motif qui éteint VRAIMENT le bouton, une fois écarté celui que le dialogue
 * recueille lui-même.
 *
 * `motifIndisponibilite` reste la vérité complète, celle qui énumère TOUT ce qui
 * bloque. Celle-ci répond à une autre question : « reste-t-il quelque chose que
 * l'utilisateur ne puisse pas régler ici ? ». Une fiche CIN sans date n'est plus
 * un cul-de-sac (« Indiquez-la au moment de l'envoi » sans champ pour le faire),
 * elle ouvre le dialogue qui la demande.
 *
 * Le verrou serveur et l'état TEJ non fourni priment et ne se lèvent pas au
 * clavier : ils sont rendus tels quels, avant même de regarder la fiche —
 * exactement l'ordre de `motifIndisponibilite`.
 */
export function motifIndisponibiliteHorsDate(
    fiche: FicheTej | null | undefined,
    ecriture: TejEcritureProp,
    t: (namespace: string, key: string, fallback?: string) => string,
): string | null {
    const motif = motifIndisponibilite(fiche, ecriture, t);

    if (motif === null) return null;
    if (!ecriture.ecriture_active || !fiche) return motif;

    return dateNaissanceRequise(fiche) ? null : motif;
}

/**
 * Date de naissance portée par la fiche → ISO `aaaa-mm-jj`, la forme qu'échange
 * `FrDateInput`.
 *
 * Tolère les trois écritures qu'un sérialiseur PHP peut rendre : `aaaa-mm-jj`,
 * un horodatage ISO complet (`aaaa-mm-jjThh:mm:ss…`, ce que produit une colonne
 * castée en date), et la forme TEJ `jj/mm/aaaa`. Tout le reste rend une chaîne
 * VIDE : un champ vide se remarque et se corrige, une date fausse pré-remplie se
 * confirme sans être relue — et elle partirait chez la DGI.
 */
export function isoDateNaissance(valeur: string | null | undefined): string {
    if (!valeur) return '';

    const iso = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]|$)/.exec(valeur);
    if (iso) return `${iso[1]}-${iso[2]}-${iso[3]}`;

    const fr = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(valeur);
    if (fr) return `${fr[3]}-${fr[2]}-${fr[1]}`;

    return '';
}

/* ────────────────────────────────────────────────────────────────────────────
 * CONSULTATION DU REGISTRE NATIONAL DES CONTRIBUABLES
 * ────────────────────────────────────────────────────────────────────────── */

/**
 * Profil rendu par `GET /fournisseurs/registre-tej/{type}/{identifiant}`.
 *
 * ⚠️ CE N'EST PAS UNE DÉDUPLICATION. Ce que le serveur interroge est le REGISTRE
 * NATIONAL DES CONTRIBUABLES, pas notre liste : tout matricule fiscal valide y
 * figure. C'est une source de REMPLISSAGE — le portail TEJ affiche exactement
 * ces valeurs, en lecture seule, sous un matricule. En conclure « ce tiers est
 * déjà déclaré » a coûté une journée le 2026-08-10 ; le seul signal de « nous
 * l'avons déjà envoyé » reste `FicheTej.synced_at` / `external_id`.
 *
 * Chaque champ peut être `null` : le serveur ne rend qu'une LISTE BLANCHE de
 * clés dont la provenance est documentée (`TejBeneficiaireService::
 * profilLisible()`), et une clé absente de la réponse de TEJ vaut `null`. Le
 * régime fiscal n'y est pas : aucune capture ne montre sous quel nom TEJ le
 * rendrait, et deviner une clé c'est inventer une API.
 */
export interface ProfilRegistreTej {
    raison_sociale: string | null;
    activite: string | null;
    adresse: string | null;
    email: string | null;
    telephone: string | null;
    resident: boolean | null;
}

/**
 * Réponse complète de la route de consultation.
 *
 * Trois issues, et l'écran les traite différemment :
 *  - `ok: false` .............. la question n'a pas pu être posée (module
 *    éteint, panne, plafond, forme refusée). `message` dit laquelle, et l'écran
 *    BASCULE EN SAISIE MANUELLE : ne pas savoir n'empêche pas de travailler ;
 *  - `ok: true, connu: false` . TEJ a répondu sans rien d'exploitable. Même
 *    bascule, autre phrase ;
 *  - `ok: true, connu: true` .. `profil` remplit l'écran, en lecture seule.
 */
export interface ReponseRegistreTej {
    ok: boolean;
    connu: boolean;
    /** Forme NORMALISÉE par le serveur — celle qui partira dans le corps. */
    identifiant: string | null;
    profil: ProfilRegistreTej | null;
    message: string | null;
}

/** Profil vide — défaut sûr quand la réponse n'en porte pas. */
export const PROFIL_REGISTRE_VIDE: ProfilRegistreTej = {
    raison_sociale: null,
    activite: null,
    adresse: null,
    email: null,
    telephone: null,
    resident: null,
};

/**
 * Met une saisie sous une forme qui peut VOYAGER dans un segment d'URL.
 *
 * ⚠️ Ce n'est PAS la normalisation TEJ, et la nuance n'est pas rhétorique. La
 * normalisation appartient au SERVEUR (`normaliserIdentifiant()` : extraction du
 * noyau, contrôle de forme `\d{7}[A-Z]`, refus de ce qui n'a pas la forme
 * observée), et l'écran ne la refait pas — il AFFICHE la forme normalisée que le
 * serveur lui renvoie.
 *
 * Ce que fait cette fonction est plus étroit : la route de consultation porte
 * l'identifiant dans son CHEMIN, et un `/` de séparateur (`1631526/V/A/M/000`)
 * y couperait le segment en morceaux. On retire donc ce qui ne peut pas tenir
 * dans un segment, et rien de plus. `1234567 SOCIETE ALPHA` devient
 * `1234567SOCIETEALPHA` — que le serveur REFUSERA, et c'est bien lui qui doit le
 * faire : la version qui extrayait un noyau côté client aurait envoyé
 * `1234567S`, un matricule parfaitement bien formé appartenant à quelqu'un
 * d'autre.
 */
export function identifiantTransportable(brut: string): string {
    return brut.toUpperCase().replace(/[^A-Z0-9]/g, '');
}

/**
 * Dernier jour sélectionnable dans le calendrier : LA VEILLE, pas aujourd'hui.
 *
 * `EnvoiTejRequest` comme `BeneficiaireRequest` valident `date_naissance` par
 * `before:today`, et c'est bien strict — mesuré sur le validateur : la date DU
 * JOUR est refusée. Borner à aujourd'hui offrirait donc un jour que le serveur
 * rejette, pour un aller-retour et un message d'erreur. Les deux écrans qui
 * saisissent cette date (la fiche et la confirmation d'envoi) partagent donc
 * cette borne.
 *
 * Bornes calculées sur les parties LOCALES de la date, jamais via
 * `toISOString()` : à l'est de Greenwich (la Tunisie est en UTC+1) l'UTC recule
 * d'un jour, et la borne sauterait une journée — le décalage déjà consigné dans
 * `FrDateInput`.
 */
export function derniereDateNaissanceIso(reference: Date = new Date()): string {
    const veille = new Date(reference.getFullYear(), reference.getMonth(), reference.getDate() - 1);
    const mois = String(veille.getMonth() + 1).padStart(2, '0');
    const jour = String(veille.getDate()).padStart(2, '0');

    return `${veille.getFullYear()}-${mois}-${jour}`;
}
