/**
 * Les fonctions PURES de `<FrDateInput>` — conversion, masque de saisie et
 * placement du curseur.
 *
 * ⚠️ Elles vivent dans leur propre module pour UNE raison : le harnais Vitest du
 * dépôt tourne en environnement `node` et ne collecte que `*.test.ts`. Laissées
 * dans le `.tsx`, elles n'étaient testables qu'en montant React, `react-day-picker`
 * et les primitives Radix — c'est-à-dire jamais.
 */

export function isoToFr(iso: string): string {
    if (!iso) return '';
    const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
    if (!m) return iso;
    return `${m[3]}/${m[2]}/${m[1]}`;
}

export function frToIso(fr: string): string | null {
    const nu = fr.trim();
    // Les huit chiffres nus (`12032026`) sont acceptés en plus de `jj/mm/aaaa` :
    // le masque de saisie pose les barres obliques, mais un collage depuis un
    // tableur ou un export n'en porte pas.
    const m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec(nu) ?? /^(\d{2})(\d{2})(\d{4})$/.exec(nu);
    if (!m) return null;

    const jour = Number(m[1]);
    const mois = Number(m[2]);
    const annee = Number(m[3]);

    if (jour < 1 || jour > 31 || mois < 1 || mois > 12) return null;

    // ⚠️ LE CALENDRIER EST VÉRIFIÉ, PAS SEULEMENT LES BORNES. « jour ≤ 31 »
    // laissait passer 31/02 et 31/04 : la valeur partait au serveur en
    // `2026-02-31`, que PHP lit comme le 3 mars — la règle `date` de Laravel
    // l'accepte. Une date décalée d'office est pire qu'une saisie refusée, sur
    // un écran où la date de paiement décide du mois d'un certificat fiscal.
    // Un jour hors du mois déborde sur le suivant : si les parties reconstruites
    // ne sont plus celles saisies, la date n'existe pas.
    const reconstruite = new Date(annee, mois - 1, jour);
    if (reconstruite.getFullYear() !== annee || reconstruite.getMonth() !== mois - 1 || reconstruite.getDate() !== jour) {
        return null;
    }

    return `${m[3]}-${String(mois).padStart(2, '0')}-${String(jour).padStart(2, '0')}`;
}

/**
 * `12032026` → `12/03/2026`, au fil de la frappe : on ne tape que les chiffres,
 * les barres obliques se posent seules.
 *
 * ⚠️ Elles ne se posent PAS pendant une suppression. Sans ce cas, effacer la
 * barre de `12/` la reposerait au caractère suivant et le curseur ne pourrait
 * jamais revenir dans le jour — le champ deviendrait impossible à corriger par
 * la gauche.
 *
 * Le masque ne juge RIEN : `34/56/7890` se compose sans broncher. C'est
 * `frToIso()` qui refuse, au moment de valider — un masque qui refuse pendant
 * la frappe empêche d'écrire les états intermédiaires d'une date légitime.
 */
export function masquerDateFr(saisie: string, enSuppression = false): string {
    const chiffres = saisie.replace(/\D/g, '').slice(0, 8);
    if (chiffres === '') return '';

    let masque = chiffres.slice(0, 2);

    if (chiffres.length > 2) masque += '/' + chiffres.slice(2, 4);
    else if (chiffres.length === 2 && !enSuppression) masque += '/';

    if (chiffres.length > 4) masque += '/' + chiffres.slice(4, 8);
    else if (chiffres.length === 4 && !enSuppression) masque += '/';

    return masque;
}

/**
 * Où poser le curseur dans le masque pour qu'il reste APRÈS le n-ième chiffre.
 *
 * Le masque insère des caractères que l'utilisateur n'a pas tapés ; sans ce
 * recalage, React réécrit `value` et le curseur saute en fin de champ à chaque
 * frappe — corriger le mois d'une date déjà saisie devient impossible.
 */
export function positionApresChiffres(masque: string, chiffres: number, sauterLeSeparateur: boolean): number {
    let vus = 0;
    let i = 0;

    if (chiffres > 0) {
        for (; i < masque.length; i++) {
            if (masque[i] >= '0' && masque[i] <= '9') {
                vus++;
                if (vus === chiffres) {
                    i++;
                    break;
                }
            }
        }
    }

    // En frappe (pas en suppression), on franchit la barre que le masque vient
    // de poser : le chiffre suivant doit atterrir dans le champ suivant.
    if (sauterLeSeparateur) {
        while (i < masque.length && masque[i] === '/') i++;
    }

    return i;
}

/**
 * Calendar day → ISO using its LOCAL parts. `toISOString()` would convert to
 * UTC first, so east-of-Greenwich users (Tunisia is UTC+1) would get the
 * PREVIOUS day for every pick.
 */
export function toLocalIso(date: Date): string {
    const m = String(date.getMonth() + 1).padStart(2, '0');
    const d = String(date.getDate()).padStart(2, '0');
    return `${date.getFullYear()}-${m}-${d}`;
}

/**
 * Aujourd'hui en ISO local. À utiliser partout plutôt que
 * `new Date().toISOString().slice(0, 10)` : `toISOString()` bascule en UTC, donc
 * en Tunisie (UTC+1) toute saisie faite entre minuit et 1 h du matin serait
 * datée de la VEILLE.
 */
export function todayIso(): string {
    return toLocalIso(new Date());
}
