import { useEffect, useMemo, useRef, useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { ApiError, api } from '@/api/client';
import type { TranslationEntry, ValidationWarning } from '@/api/types';
import { composePlural, decomposePlural, isPluralMessage, selectorOrder } from '@/api/icu';

export type SaveState = 'idle' | 'saving' | 'saved' | 'error';

interface Props {
    keyUuid: string;
    locale: string;
    value: string | null;
    version: number;
    direction: 'ltr' | 'rtl';
    pluralCategories: string[];
    maxLength: number | null;
    autoFocus?: boolean;
    /** Rendu compact pour la grille, aéré pour le mode Focus. */
    dense?: boolean;
    onSaved?: (entry: TranslationEntry) => void;
    onRequestNext?: () => void;
}

/**
 * Saisie d'une traduction — le composant que le traducteur utilise toute la
 * journée.
 *
 * Trois partis pris :
 *
 *  1. **Pas de bouton « Enregistrer ».** L'enregistrement se déclenche à la
 *     perte de focus. Un bouton par ligne sur une grille de trente mille lignes
 *     est une plaisanterie ; un bouton global oblige à se souvenir de cliquer.
 *
 *  2. **Le traducteur ne voit jamais d'ICU.** Un message pluriel se présente
 *     comme un champ par forme, avec le nom de la catégorie CLDR en clair. La
 *     recomposition se fait à l'enregistrement.
 *
 *  3. **Les erreurs s'affichent sous le champ, pas dans une alerte.** Elles
 *     concernent ce texte-là ; les éloigner du texte oblige à faire le lien
 *     soi-même.
 */
export function TranslationInput({
    keyUuid,
    locale,
    value,
    version,
    direction,
    pluralCategories,
    maxLength,
    autoFocus = false,
    dense = false,
    onSaved,
    onRequestNext,
}: Props) {
    const queryClient = useQueryClient();

    const decomposed = useMemo(() => decomposePlural(value), [value]);
    const plural = decomposed !== null;

    const [simple, setSimple] = useState(value ?? '');
    const [branches, setBranches] = useState<Record<string, string>>(decomposed?.branches ?? {});
    const [state, setState] = useState<SaveState>('idle');
    const [error, setError] = useState<string | null>(null);
    const [warnings, setWarnings] = useState<ValidationWarning[]>([]);

    const currentVersion = useRef(version);

    // Le contenu peut changer sous nos pieds — changement de ligne dans la
    // grille, rafraîchissement au retour d'onglet. On ne resynchronise que si la
    // valeur serveur diffère de ce qu'on a écrit, sinon la frappe serait écrasée.
    useEffect(() => {
        currentVersion.current = version;
        setSimple(value ?? '');
        setBranches(decomposePlural(value)?.branches ?? {});
        setState('idle');
        setError(null);
        setWarnings([]);
    }, [keyUuid, locale, value, version]);

    const selectors = useMemo(
        () => selectorOrder(pluralCategories, Object.keys(decomposed?.branches ?? {})),
        [pluralCategories, decomposed],
    );

    const mutation = useMutation({
        mutationFn: (next: string | null) =>
            api.writeTranslation(keyUuid, locale, {
                value: next,
                version: currentVersion.current,
            }),
        onMutate: () => {
            setState('saving');
            setError(null);
        },
        onSuccess: (entry) => {
            currentVersion.current = entry.currentVersion;
            setWarnings(entry.warnings);
            setState('saved');
            onSaved?.(entry);

            // La grille et les compteurs sont désormais périmés. On invalide
            // plutôt que de patcher le cache à la main : le calcul d'avancement
            // dépend de règles serveur qu'on ne veut pas dupliquer ici.
            void queryClient.invalidateQueries({ queryKey: ['grid'] });
            // La vue par clé montre la MÊME traduction sous un autre angle :
            // l'oublier ici laisserait un onglet afficher une valeur périmée
            // après une saisie faite dans l'autre.
            void queryClient.invalidateQueries({ queryKey: ['keys'] });
            void queryClient.invalidateQueries({ queryKey: ['stats'] });
            void queryClient.invalidateQueries({ queryKey: ['namespaces'] });

            window.setTimeout(() => setState('idle'), 1500);
        },
        onError: (caught) => {
            setState('error');

            if (caught instanceof ApiError && caught.isConflict) {
                setError(
                    'Modifiée entre-temps par quelqu\'un d\'autre. Rechargez avant de réécrire.',
                );

                return;
            }

            setError(caught instanceof Error ? caught.message : 'Enregistrement impossible.');
        },
    });

    function currentValue(): string | null {
        if (!plural) return simple.trim() === '' ? null : simple;

        const composed = composePlural(
            {
                prefix: decomposed.prefix,
                suffix: decomposed.suffix,
                variable: decomposed.variable,
                branches,
            },
            selectors,
        );

        return composed === '' ? null : composed;
    }

    function save() {
        const next = currentValue();
        const unchanged = next === (value ?? null);

        if (unchanged || mutation.isPending) return;

        mutation.mutate(next);
    }

    function onKeyDown(event: React.KeyboardEvent) {
        // ⌘↵ enregistre et passe à la suivante : c'est le geste du mode Focus,
        // et il doit fonctionner à l'identique dans la grille.
        if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
            event.preventDefault();
            save();
            onRequestNext?.();

            return;
        }

        if (event.key === 'Escape') {
            event.preventDefault();
            setSimple(value ?? '');
            setBranches(decomposePlural(value)?.branches ?? {});
            (event.target as HTMLElement).blur();
        }
    }

    const length = plural
        ? Math.max(0, ...Object.values(branches).map((b) => b.length))
        : simple.length;

    const tooLong = maxLength !== null && length > maxLength;

    return (
        <div className="w-full">
            {plural ? (
                <div className="space-y-1">
                    {selectors.map((selector) => (
                        <div key={selector} className="flex items-start gap-2">
                            <span
                                className="mt-1.5 w-12 shrink-0 text-right font-mono text-[11px] text-ink-400"
                                title={
                                    selector.startsWith('=')
                                        ? `Valeur exacte ${selector.slice(1)}`
                                        : `Forme plurielle « ${selector} »`
                                }
                            >
                                {selector}
                            </span>
                            <textarea
                                rows={1}
                                dir={direction}
                                value={branches[selector] ?? ''}
                                onChange={(e) =>
                                    setBranches((prev) => ({ ...prev, [selector]: e.target.value }))
                                }
                                onBlur={save}
                                onKeyDown={onKeyDown}
                                className="min-h-8 flex-1 resize-y rounded border border-ink-200 bg-white px-2 py-1 text-sm outline-none focus:border-accent"
                            />
                        </div>
                    ))}
                </div>
            ) : (
                <textarea
                    rows={dense ? 1 : 3}
                    dir={direction}
                    autoFocus={autoFocus}
                    value={simple}
                    onChange={(e) => setSimple(e.target.value)}
                    onBlur={save}
                    onKeyDown={onKeyDown}
                    placeholder="Saisir la traduction…"
                    className={`w-full resize-y rounded border bg-white px-2 py-1 outline-none focus:border-accent ${
                        dense ? 'min-h-8 text-sm' : 'min-h-24 text-base'
                    } ${error !== null ? 'border-red-400' : 'border-ink-200'}`}
                />
            )}

            <div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px]">
                <SaveIndicator state={state} />

                {maxLength !== null && (
                    <span className={`tabular ${tooLong ? 'font-medium text-amber-700' : 'text-ink-400'}`}>
                        {length} / {maxLength}
                    </span>
                )}

                {error !== null && (
                    <span role="alert" className="font-medium text-red-700">
                        {error}
                    </span>
                )}

                {warnings.map((warning) => (
                    <span key={warning.code} className="text-amber-700">
                        {warning.message}
                    </span>
                ))}
            </div>
        </div>
    );
}

function SaveIndicator({ state }: { state: SaveState }) {
    if (state === 'saving') return <span className="text-ink-400">⋯ enregistrement</span>;
    if (state === 'saved') return <span className="text-emerald-600">✓ enregistré</span>;
    if (state === 'error') return <span className="text-red-600">✗ non enregistré</span>;

    return null;
}

export { isPluralMessage };
