import { describe, expect, it } from 'vitest';
import {
    applyCorrections,
    applyTypography,
    detectTypography,
    findProtectedRanges,
    isAllCaps,
    isProtected,
    normalizeWord,
    restoreCapitalization,
    type TypographyRule,
} from './typography';

/** Raccourci de lecture : le texte corrigé, sans le détail des corrections. */
function corrige(texte: string): string {
    return applyTypography(texte).corrected;
}

/** Les règles déclenchées sur une seule passe de détection. */
function regles(texte: string): TypographyRule[] {
    return detectTypography(texte).map((correction) => correction.rule);
}

describe('espaces multiples', () => {
    it('ramène une suite d’espaces à une seule', () => {
        expect(corrige('Merci   beaucoup')).toBe('Merci beaucoup');
    });

    it('ne touche pas à une indentation de début de ligne', () => {
        expect(corrige('Bonjour.\n    Draps fournis.')).toBe('Bonjour.\n    Draps fournis.');
    });

    it('ne se déclenche pas sur une espace unique', () => {
        expect(regles('Merci beaucoup')).not.toContain('espaces-multiples');
    });
});

describe('espace manquante après la ponctuation', () => {
    it('ajoute l’espace après une virgule collée', () => {
        expect(corrige('Bonjour,comment allez-vous ?')).toBe('Bonjour, comment allez-vous ?');
    });

    it('ajoute l’espace après un point collé et enchaîne sur la majuscule', () => {
        expect(corrige('Bonjour.comment ça va')).toBe('Bonjour. Comment ça va');
    });

    it('sépare une abréviation collée au nom', () => {
        expect(corrige('M.Dupont arrive')).toBe('M. Dupont arrive');
    });

    it('ne coupe pas un nombre décimal', () => {
        expect(corrige('Le tarif est de 3,5 euros')).toBe('Le tarif est de 3,5 euros');
    });

    it('ne coupe pas un nom de domaine', () => {
        expect(corrige('Réservation faite sur booking.com hier')).toBe('Réservation faite sur booking.com hier');
    });

    it('ne coupe pas une URL', () => {
        expect(corrige('Voir https://thelandlord.tn/reservations pour le détail')).toBe('Voir https://thelandlord.tn/reservations pour le détail');
    });

    it('ne coupe pas une adresse e-mail', () => {
        expect(corrige('Écrivez à contact@thelandlord.tn merci')).toBe('Écrivez à contact@thelandlord.tn merci');
    });
});

describe('espace parasite avant la virgule et le point', () => {
    it('recolle la virgule au mot', () => {
        expect(corrige('Bonjour , comment ça va')).toBe('Bonjour, comment ça va');
    });

    it('recolle le point au mot', () => {
        expect(corrige('Bonne journée .')).toBe('Bonne journée.');
    });

    it('ne se déclenche pas quand la ponctuation est déjà collée', () => {
        expect(regles('Bonjour, comment ça va')).not.toContain('espace-parasite-avant-ponctuation');
    });
});

describe('espace avant la ponctuation double (typographie française)', () => {
    it('ajoute l’espace devant le point d’interrogation', () => {
        expect(corrige('Tout va bien?')).toBe('Tout va bien ?');
    });

    it('ajoute l’espace devant les deux-points', () => {
        expect(corrige('Code d’entrée: 4521')).toBe('Code d’entrée : 4521');
    });

    it('ne redouble pas l’espace quand elle est déjà là', () => {
        expect(corrige('Tout va bien ?')).toBe('Tout va bien ?');
    });

    it('ne découpe pas une ponctuation répétée', () => {
        expect(corrige('Oui !!! Super ???')).toBe('Oui !!! Super ???');
    });

    it('laisse les heures intactes', () => {
        expect(corrige('Arrivée à 14:30 comme prévu')).toBe('Arrivée à 14:30 comme prévu');
    });

    it('laisse une URL intacte, deux-points compris', () => {
        expect(corrige('Lien : https://thelandlord.tn')).toBe('Lien : https://thelandlord.tn');
    });

    it('laisse une entité HTML intacte, point-virgule compris', () => {
        expect(corrige('Chambre&nbsp;12 réservée')).toBe('Chambre&nbsp;12 réservée');
    });
});

describe('mot doublé', () => {
    it('supprime la seconde occurrence et garde la capitale de la première', () => {
        expect(corrige('Le le chat dort')).toBe('Le chat dort');
    });

    it('laisse les redoublements légitimes du français', () => {
        expect(corrige('Nous nous sommes installés')).toBe('Nous nous sommes installés');
        expect(corrige('C’est très très bien')).toBe('C’est très très bien');
    });

    it('ne fusionne pas deux phrases séparées par une ponctuation', () => {
        expect(corrige('Merci. Merci beaucoup')).toBe('Merci. Merci beaucoup');
    });

    it('ne fusionne pas deux lignes distinctes', () => {
        expect(corrige('Lit double\ndouble vitrage')).toBe('Lit double\ndouble vitrage');
    });

    it('laisse les nombres répétés', () => {
        expect(regles('chambres 2 2 et 3')).not.toContain('mot-double');
    });
});

describe('majuscule de début de phrase', () => {
    it('capitalise le premier mot du message', () => {
        expect(corrige('bonjour à vous')).toBe('Bonjour à vous');
    });

    it('capitalise après un point, un point d’exclamation ou d’interrogation', () => {
        expect(corrige('Bonjour. merci ! super ? oui')).toBe('Bonjour. Merci ! Super ? Oui');
    });

    it('ne capitalise pas après une abréviation', () => {
        expect(corrige('Etc. et surtout merci')).toBe('Etc. et surtout merci');
    });

    it('ne capitalise pas après un simple retour à la ligne (listes à puces)', () => {
        expect(corrige('Fourni.\n- draps\n- serviettes')).toBe('Fourni.\n- draps\n- serviettes');
    });

    it('ne capitalise pas une marque en casse mixte', () => {
        expect(corrige('iPhone laissé dans la chambre.')).toBe('iPhone laissé dans la chambre.');
    });

    it('ne touche pas à une URL placée en tête de message', () => {
        expect(corrige('https://thelandlord.tn est notre site')).toBe('https://thelandlord.tn est notre site');
    });
});

describe('accents oubliés', () => {
    it('corrige les mots de la liste fermée', () => {
        expect(corrige('Le sejour a deja commence tres bien')).toBe('Le séjour a déjà commence très bien');
    });

    it('reporte la capitale d’origine sur le mot accentué', () => {
        expect(corrige('Tres bien')).toBe('Très bien');
    });

    it('ne touche jamais à « a » ni à « ou » isolés — trop ambigus', () => {
        expect(corrige('Il a la clé ou le badge')).toBe('Il a la clé ou le badge');
    });

    it('laisse les sigles en capitales tranquilles', () => {
        expect(corrige('CA reste inchangé')).toBe('CA reste inchangé');
    });

    it('n’abîme pas un message anglais : aucun mot anglais courant n’est dans la table', () => {
        const anglais = 'Hello, your reservation at the hotel is confirmed. The telephone number is below.';

        expect(corrige(anglais)).toBe(anglais);
    });
});

describe('fautes fréquentes', () => {
    it('corrige les graphies de la liste', () => {
        expect(corrige('Notre addresse et notre acceuil')).toBe('Notre adresse et notre accueil');
        expect(corrige('Parmis les options, malgres tout')).toBe('Parmi les options, malgré tout');
    });

    it('corrige aussi derrière une élision, en gardant le « l’ » tapé', () => {
        expect(corrige("Voici l'addresse")).toBe("Voici l'adresse");
        expect(corrige('Voici l’addresse')).toBe('Voici l’adresse');
    });

    it('recolle les agglutinations de « jusqu’ », qui n’ont qu’une seule lecture', () => {
        // Hors du champ de la reconstruction d'élision du dictionnaire : le reste (« a »,
        // « au », « aux ») est trop court pour qu'une reconstruction soit sûre. La graphie
        // collée, elle, n'est ambiguë ni en français ni en anglais : on applique d'office.
        expect(corrige('Ouvert jusqua demain')).toBe("Ouvert jusqu'à demain");
        expect(corrige('Ouvert jusquau 12 mai')).toBe("Ouvert jusqu'au 12 mai");
        expect(corrige('Fermé jusquaux vacances')).toBe("Fermé jusqu'aux vacances");
    });

    it('corrige les locutions en un seul bloc', () => {
        expect(corrige('sil vous plait, rendez vous ce week end')).toBe("S'il vous plaît, rendez-vous ce week-end");
        expect(corrige('a bientot et a cote de la plage')).toBe('À bientôt et à côté de la plage');
    });

    it('ne se déclenche pas sur une orthographe déjà correcte', () => {
        expect(regles('Voici notre adresse, à bientôt')).toHaveLength(0);
    });
});

describe('zones protégées', () => {
    it('repère URLs, e-mails, heures et décimaux', () => {
        const texte = 'Écrire à contact@thelandlord.tn avant 14:30, tarif 3,5 €, voir https://thelandlord.tn';

        expect(findProtectedRanges(texte).length).toBeGreaterThanOrEqual(4);
    });

    it('laisse passer une insertion posée juste avant la zone', () => {
        const zones = [{ start: 10, end: 20 }];

        expect(isProtected(zones, 10, 10)).toBe(false);
        expect(isProtected(zones, 15, 15)).toBe(true);
        expect(isProtected(zones, 5, 12)).toBe(true);
        expect(isProtected(zones, 0, 5)).toBe(false);
    });
});

describe('applyCorrections', () => {
    it('applique remplacements, suppressions et insertions dans le bon ordre', () => {
        const texte = 'ab';
        const resultat = applyCorrections(texte, [
            { start: 1, end: 1, from: '', to: '-', rule: 'espace-apres-ponctuation' },
            { start: 0, end: 1, from: 'a', to: 'A', rule: 'majuscule-debut-de-phrase' },
        ]);

        expect(resultat).toBe('A-b');
    });

    it('ignore une correction qui chevauche la précédente plutôt que de corrompre le texte', () => {
        const resultat = applyCorrections('abcd', [
            { start: 0, end: 3, from: 'abc', to: 'X', rule: 'faute-frequente' },
            { start: 1, end: 2, from: 'b', to: 'Y', rule: 'accent-oublie' },
        ]);

        expect(resultat).toBe('Xd');
    });
});

describe('convergence', () => {
    it('est idempotente sur un message qui cumule toutes les règles', () => {
        const brouillon = 'bonjour,sil vous plait confirmez le le rendez vous   avant 18h00.merci tres  , beaucoup';
        const premier = corrige(brouillon);

        expect(corrige(premier)).toBe(premier);
    });

    it('ne trouve plus rien à détecter sur un texte déjà corrigé', () => {
        expect(detectTypography(corrige('bonjour,comment ca va deja'))).toHaveLength(0);
    });

    it('laisse un texte vide tel quel', () => {
        expect(applyTypography('')).toEqual({ corrected: '', corrections: [] });
    });
});

describe('utilitaires de casse', () => {
    it('normalise apostrophes et casse', () => {
        expect(normalizeWord('L’Adresse')).toBe("l'adresse");
    });

    it('reconnaît les sigles', () => {
        expect(isAllCaps('RIB')).toBe(true);
        expect(isAllCaps('Rib')).toBe(false);
        expect(isAllCaps('A')).toBe(false);
    });

    it('ne prend pas une écriture sans casse pour un sigle', () => {
        expect(isAllCaps('الحجز')).toBe(false);
    });

    it('reporte la capitale seulement quand il y en a une', () => {
        expect(restoreCapitalization('Tres', 'très')).toBe('Très');
        expect(restoreCapitalization('tres', 'très')).toBe('très');
        expect(restoreCapitalization('12', 'douze')).toBe('douze');
    });
});

describe('règles propres au français', () => {
    // Défaut trouvé en relisant de VRAIS messages avec le dictionnaire chargé :
    // « Could you confirm your arrival time? » ressortait en « … time ? ». L'espace
    // avant le point d'interrogation est une règle FRANÇAISE ; l'appliquer à un
    // message anglais AJOUTE une faute, dans des messages que les gérants écrivent
    // tous les jours aux voyageurs Airbnb.
    it("n'ajoute pas l'espace française avant la ponctuation quand la langue n'est pas le français", () => {
        const entree = 'Could you confirm your arrival time? Thanks!';

        expect(applyTypography(entree, false).corrected).toBe(entree);
        expect(applyTypography(entree, true).corrected).toBe('Could you confirm your arrival time ? Thanks !');
    });

    it("ne remet pas d'accents ni ne corrige de fautes françaises hors français", () => {
        expect(applyTypography('tres bien, a bientot', false).corrected).toBe('Tres bien, a bientot');
        expect(applyTypography('tres bien, a bientot', true).corrected).toBe('Très bien, à bientôt');
        expect(applyTypography('open jusquau 12 mai', false).corrected).toBe('Open jusquau 12 mai');
    });

    it('garde les règles valables dans toutes les langues', () => {
        // Espaces multiples, espace manquant après la virgule, mot doublé et
        // majuscule de début de phrase ne dépendent pas de la langue.
        expect(applyTypography('hello  world,how are are you', false).corrected).toBe('Hello world, how are you');
    });

    it('applique le français par défaut, sans drapeau', () => {
        expect(applyTypography('a bientot !').corrected).toBe(applyTypography('a bientot !', true).corrected);
    });
});
