/**
 * Formatage de l'écran « Retenue à la source ».
 *
 * Le dinar tunisien se compte en MILLIMES : trois décimales partout, sans
 * exception. Ces helpers ne recalculent JAMAIS une retenue destinée à être
 * affichée dans le tableau — les montants viennent du serveur. Le seul calcul
 * autorisé ici est l'aperçu d'un dialogue de saisie (`apercuCertificat`), qui est
 * une aide à la saisie et non une source de vérité.
 */

/** Nombre de décimales métier (miroir de config('retenue.decimales')). */
export const DECIMALES = 3;

/** Montant en millimes : « 1 234,500 ». */
export const fmtMillimes = (n: number) => n.toLocaleString('fr-TN', { minimumFractionDigits: 3, maximumFractionDigits: 3 });

/** Montant avec la devise : « 1 234,500 TND ». */
export const fmtTnd = (n: number) => `${fmtMillimes(n)} TND`;

/** Taux : « 10,00 % ». */
export const fmtTaux = (n: number) => `${Number(n).toLocaleString('fr-TN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} %`;

/** ISO `YYYY-MM-DD` → `jj/mm/aaaa` (chaîne vide si absente ou invalide). */
export function fmtDateFr(iso: string | null | undefined): 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]}`;
}

/**
 * Abréviations du libellé de paiement — miroir de config('retenue.abrev_paiement').
 * Les clés restent alignées sur config('payment.mode').
 */
export const ABREV_PAIEMENT: Record<string, string> = {
    VIREMENT: 'VT',
    CHÈQUE: 'CHQ',
    CASH: 'ESP',
    TPE: 'TPE',
    PAYMEE: 'PAYMEE',
};

/** Modes de paiement — miroir de config('payment.mode'). */
export const MODES_PAIEMENT = ['CASH', 'CHÈQUE', 'VIREMENT', 'PAYMEE', 'TPE'] as const;

export interface PaiementSaisi {
    est_paye: boolean;
    date_paiement: string | null;
    mode_paiement: string | null;
    banque: string | null;
}

/**
 * Aperçu du libellé de paiement « VT UIB LE 12/02/2026 ».
 *
 * Miroir exact de RetenueLigne::libellePaiement() côté serveur : il sert
 * UNIQUEMENT à pré-visualiser la saisie dans le dialogue. La valeur affichée
 * dans le tableau reste `ligne.libelle_paiement`, produite par le serveur.
 */
export function libellePaiement(p: PaiementSaisi): string | null {
    if (!p.est_paye || !p.date_paiement) return null;

    const mode = p.mode_paiement ?? '';
    const abrev = ABREV_PAIEMENT[mode] ?? mode;

    return [abrev, p.banque, 'LE', fmtDateFr(p.date_paiement)]
        .filter((part): part is string => typeof part === 'string' && part.trim() !== '')
        .join(' ')
        .trim();
}

/**
 * TRONCATURE au millime — la règle de TEJ, et donc du serveur.
 *
 * ⚠️ Ce n'est PAS un arrondi, et la différence est visible à l'écran.
 * TEJ tronque : vérifié sur 256 certificats réels, dont 63 où les deux règles
 * divergent — 63 suivent la troncature, 0 l'arrondi. `RetenueLigneService`
 * tronque donc lui aussi. Un aperçu qui arrondirait annoncerait un millime de
 * plus que le montant réellement enregistré, puis déposé : l'utilisateur verrait
 * l'écran se corriger tout seul après enregistrement, sans comprendre pourquoi.
 *
 * L'epsilon protège du binaire : `1.19 * 1000` peut valoir `1189.9999999`, que
 * `Math.floor` ramènerait à 1,189 au lieu de 1,190.
 */
export const tronqueMillime = (n: number) => Math.floor(n * 1000 + 1e-9) / 1000;

export interface ApercuCertificat {
    ht: number;
    tva: number;
    ttc: number;
    retenue: number;
    net: number;
    sousSeuil: boolean;
}

/**
 * Aperçu complet d'un certificat, DANS L'ORDRE DU SERVEUR.
 *
 * Reproduit `RetenueLigneService::applyAmounts()` pas à pas, y compris son
 * arbitrage : si un HT et un taux de TVA sont renseignés, le TTC en DÉCOULE et
 * le montant saisi plus haut est ignoré ; sinon le montant saisi EST le TTC
 * (cas des loyers, sans TVA).
 *
 * Les deux identités que TEJ vérifie sont conservées par construction :
 * `TTC = HT + TVA` et `Net = TTC − RS` — cette dernière par SOUSTRACTION, jamais
 * par un second pourcentage.
 *
 * ⚠️ Aide à la saisie : la valeur qui fait foi reste celle du serveur.
 */
export function apercuCertificat(saisie: {
    montantTtc: number;
    montantHt: number;
    tauxTva: number;
    tauxRs: number;
    seuilMinTtc: number | null;
}): ApercuCertificat {
    const nombre = (valeur: number) => (Number.isFinite(valeur) ? valeur : 0);

    const htSaisi = nombre(saisie.montantHt);
    const tauxTva = nombre(saisie.tauxTva);

    const decomposable = tauxTva > 0 && htSaisi > 0;
    const ht = decomposable ? tronqueMillime(htSaisi) : tronqueMillime(nombre(saisie.montantTtc));
    const tva = decomposable ? tronqueMillime((ht * tauxTva) / 100) : 0;
    const ttc = decomposable ? tronqueMillime(ht + tva) : ht;

    const sousSeuil = saisie.seuilMinTtc !== null && ttc < saisie.seuilMinTtc;
    const retenue = sousSeuil ? 0 : tronqueMillime((ttc * nombre(saisie.tauxRs)) / 100);

    return { ht, tva, ttc, retenue, net: tronqueMillime(ttc - retenue), sousSeuil };
}

/** `YYYY-MM` → `{ annee, mois }` pour l'ouverture d'un mois. */
export function moisEnAnneeMois(mois: string): { annee: number; mois: number } {
    const m = /^(\d{4})-(\d{2})$/.exec(mois);
    if (!m) {
        const now = new Date();
        return { annee: now.getFullYear(), mois: now.getMonth() + 1 };
    }
    return { annee: Number(m[1]), mois: Number(m[2]) };
}
