import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join, relative, resolve } from 'node:path';
import { describe, expect, it } from 'vitest';

/**
 * Garde-fou : un <Popover> imbriqué dans une modale DOIT porter `modal`.
 *
 * Pourquoi (TL-291) — le contenu d'un Popover est portalisé dans <body>, donc
 * hors du piège à focus du Dialog/Drawer qui l'entoure. En mode non-modal il n'a
 * pas de focus scope à lui : sous WebKit (Safari, et donc TOUS les navigateurs
 * iPhone/iPad) le FocusScope de la modale rapatrie le focus dès que le popover
 * s'ouvre, Radix voit le focus sortir et le referme en ~30 ms. Résultat : le
 * bouton ne fait « rien ». Chromium laisse le focus sur le déclencheur, d'où un
 * bug invisible en développement sur desktop.
 *
 * `modal` donne au popover son propre focus scope, qui met en pause celui de la
 * modale — c'est la réponse prévue par Radix, déjà appliquée dans
 * `fr-date-input.tsx` et dans le dialogue de simulation de `reservations/create`.
 *
 * Ce test lit le source : il coûte zéro dépendance et couvre tout l'écran de
 * l'app, pas seulement les deux popovers corrigés.
 */

const JS_ROOT = resolve(__dirname, '../..');
const MODAL_LAYERS = ['Dialog', 'Drawer', 'Sheet', 'AlertDialog'];

const OPEN_CONTENT = new RegExp(`<(${MODAL_LAYERS.join('|')})Content\\b`);
const CLOSE_CONTENT = new RegExp(`</(${MODAL_LAYERS.join('|')})Content>`);
// Un composant dont le `return` de premier niveau EST un contenu de modale : tout
// son JSX vit dans la modale, même si le corps est assemblé dans une variable
// (`const body = (...)` puis `{body}`), que l'analyse de plage ne peut pas suivre.
const RETURNS_MODAL_CONTENT = new RegExp(`return\\s*\\(\\s*<(${MODAL_LAYERS.join('|')})Content\\b`);
// La racine seule : ni <PopoverTrigger>, ni <PopoverContent>, ni <PopoverAnchor>.
const POPOVER_ROOT = /<Popover(?![A-Za-z])/g;

function tsxFiles(dir: string): string[] {
    return readdirSync(dir).flatMap((entry) => {
        const full = join(dir, entry);
        if (statSync(full).isDirectory()) return tsxFiles(full);
        return full.endsWith('.tsx') ? [full] : [];
    });
}

/** Plages [début, fin] (1-indexées) des `<XContent>…</XContent>` du fichier. */
function modalRanges(lines: string[]): [number, number][] {
    const ranges: [number, number][] = [];
    const stack: number[] = [];
    lines.forEach((line, index) => {
        if (OPEN_CONTENT.test(line)) stack.push(index + 1);
        if (CLOSE_CONTENT.test(line)) {
            const start = stack.pop();
            if (start !== undefined) ranges.push([start, index + 1]);
        }
    });
    return ranges;
}

/** Texte de la balise ouvrante à partir de `<Popover`, accolades équilibrées. */
function openingTag(source: string, from: number): string {
    let depth = 0;
    for (let i = from; i < source.length; i++) {
        const char = source[i];
        if (char === '{') depth++;
        else if (char === '}') depth--;
        else if (char === '>' && depth === 0) return source.slice(from, i + 1);
    }
    return source.slice(from, from + 400);
}

function isModal(tag: string): boolean {
    // `modal` nu ou `modal={true}` corrigent ; `modal={false}` ne corrige rien.
    return /\bmodal\b(?!\s*=\s*\{\s*false\s*\})/.test(tag);
}

function offenders(): string[] {
    const found: string[] = [];

    for (const file of tsxFiles(JS_ROOT)) {
        const source = readFileSync(file, 'utf8');
        if (!POPOVER_ROOT.test(source)) continue;
        POPOVER_ROOT.lastIndex = 0;

        const lines = source.split('\n');
        const ranges = modalRanges(lines);
        const wholeFileIsModal = RETURNS_MODAL_CONTENT.test(source);
        if (ranges.length === 0 && !wholeFileIsModal) continue;

        for (const match of source.matchAll(POPOVER_ROOT)) {
            const line = source.slice(0, match.index).split('\n').length;
            const insideModal = wholeFileIsModal || ranges.some(([start, end]) => line >= start && line <= end);
            if (!insideModal) continue;
            if (isModal(openingTag(source, match.index))) continue;
            found.push(`${relative(JS_ROOT, file)}:${line}`);
        }
    }

    return found.sort();
}

describe('popover imbriqué dans une modale', () => {
    it('porte toujours `modal`, sinon il est mort sous Safari/iOS', () => {
        expect(offenders()).toEqual([]);
    });

    it('détecte bien un popover non-modal dans une modale (le garde-fou lui-même)', () => {
        const tag = openingTag('<Popover open={o} onOpenChange={setO}>', 0);
        expect(isModal(tag)).toBe(false);
        expect(isModal(openingTag('<Popover open={o} modal>', 0))).toBe(true);
        expect(isModal(openingTag('<Popover open={o} modal={false}>', 0))).toBe(false);
        expect(isModal(openingTag('<Popover modal={isNested}>', 0))).toBe(true);
    });

    it('ne confond pas la racine avec PopoverTrigger / PopoverContent', () => {
        const source = '<PopoverTrigger asChild><PopoverContent /><PopoverAnchor />';
        expect([...source.matchAll(POPOVER_ROOT)]).toHaveLength(0);
    });
});
