import { Fragment, useMemo } from 'react';

/**
 * Affiche une valeur source en masquant la syntaxe ICU.
 *
 * `Séance avec {praticien}, le {date, date, long}` se lit mal : le `, date, long`
 * est une instruction de formatage destinée à la machine, pas au traducteur.
 * Elle est ici réduite à une pastille portant le seul nom de la variable, ce qui
 * rend la phrase lisible tout en signalant sans ambiguïté ce qui n'est pas du
 * texte — et donc ce qu'il ne faut pas traduire.
 *
 * La chaîne réelle reste intacte : ce composant ne fait que la RENDRE.
 */
export function SourceText({ value, className = '' }: { value: string; className?: string }) {
    const parts = useMemo(() => tokenize(value), [value]);
    const hasPlural = /\{\s*[\w.-]+\s*,\s*(?:plural|selectordinal)\s*,/.test(value);

    return (
        <span className={className}>
            {parts.map((part, index) =>
                part.kind === 'text' ? (
                    <Fragment key={index}>{part.text}</Fragment>
                ) : (
                    <span
                        key={index}
                        className="mx-0.5 inline-block rounded bg-indigo-50 px-1 font-mono text-[0.85em] text-indigo-700"
                        title={
                            part.detail === null
                                ? `Variable « ${part.text} »`
                                : `Variable « ${part.text} », formatée en ${part.detail}`
                        }
                    >
                        {part.text}
                    </span>
                ),
            )}

            {/* Le message varie selon un nombre : le signaler sans montrer la
                mécanique, que l'éditeur de pluriels prend en charge. */}
            {hasPlural && (
                <span
                    className="ml-1.5 inline-block rounded bg-ink-100 px-1 align-middle text-[0.7em] uppercase tracking-wide text-ink-500"
                    title="Ce message a plusieurs formes selon le nombre."
                >
                    pluriel
                </span>
            )}
        </span>
    );
}

type Token =
    | { kind: 'text'; text: string }
    | { kind: 'var'; text: string; detail: string | null };

/**
 * Réduit une construction plurielle à sa forme générale.
 *
 * `{count, plural, one {# séance} other {# séances}} à venir` devient
 * « # séances à venir ». Le traducteur a besoin de comprendre la PHRASE ; la
 * mécanique de sélection des formes, elle, est prise en charge par l'éditeur de
 * pluriels au moment de la saisie. Lui montrer les deux à la fois, c'est lui
 * demander de lire du code pour accéder à du texte.
 */
function flattenPlurals(value: string): string {
    const pattern = /\{\s*[\w.-]+\s*,\s*(?:plural|selectordinal|select)\s*,/;
    let result = value;

    for (let guard = 0; guard < 5; guard++) {
        const match = pattern.exec(result);
        if (!match || match.index === undefined) break;

        const close = matchingBrace(result, match.index);
        if (close === -1) break;

        const body = result.slice(match.index + match[0].length, close);
        const fallback = lastBranch(body);
        if (fallback === null) break;

        result = result.slice(0, match.index) + fallback + result.slice(close + 1);
    }

    return result;
}

/** Contenu de la branche `other`, ou à défaut de la dernière branche. */
function lastBranch(body: string): string | null {
    const branches: { selector: string; content: string }[] = [];
    let index = 0;

    while (index < body.length) {
        while (index < body.length && /\s/.test(body[index] ?? '')) index++;
        if (index >= body.length) break;

        const start = index;
        while (index < body.length && !/[\s{]/.test(body[index] ?? '')) index++;
        const selector = body.slice(start, index);

        while (index < body.length && /\s/.test(body[index] ?? '')) index++;
        if (body[index] !== '{') return null;

        const end = matchingBrace(body, index);
        if (end === -1) return null;

        branches.push({ selector, content: body.slice(index + 1, end) });
        index = end + 1;
    }

    if (branches.length === 0) return null;

    return (branches.find((b) => b.selector === 'other') ?? branches[branches.length - 1])!.content;
}

function matchingBrace(value: string, from: number): number {
    let depth = 0;

    for (let i = value.indexOf('{', from); i >= 0 && i < value.length; i++) {
        if (value[i] === '{') depth++;
        else if (value[i] === '}') {
            depth--;
            if (depth === 0) return i;
        }
    }

    return -1;
}

/**
 * Découpe volontairement simple : on ne cherche pas à parser l'ICU, seulement à
 * repérer les arguments de premier niveau, une fois les pluriels aplatis.
 */
function tokenize(source: string): Token[] {
    const value = flattenPlurals(source);
    const tokens: Token[] = [];
    const pattern = /\{\s*([\w.-]+)\s*(?:,\s*([^{}]*))?\}/g;

    let lastIndex = 0;
    let match: RegExpExecArray | null;

    while ((match = pattern.exec(value)) !== null) {
        const name = match[1];
        if (name === undefined) continue;

        // Un sélecteur de branche (`one {`, `other {`) n'est pas un argument.
        if (['zero', 'one', 'two', 'few', 'many', 'other'].includes(name) && match[2] === undefined) {
            continue;
        }

        if (match.index > lastIndex) {
            tokens.push({ kind: 'text', text: value.slice(lastIndex, match.index) });
        }

        tokens.push({ kind: 'var', text: name, detail: match[2]?.trim() || null });
        lastIndex = match.index + match[0].length;
    }

    if (lastIndex < value.length) {
        tokens.push({ kind: 'text', text: value.slice(lastIndex) });
    }

    return tokens;
}
