import { useCallback, useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Link, useParams, useSearchParams } from 'react-router-dom';
import { api } from '@/api/client';
import type { GridRow, TranslationStatus } from '@/api/types';
import { NamespaceTree } from '@/editor/NamespaceTree';
import { TranslationGrid } from '@/editor/TranslationGrid';
import { KeyGrid } from '@/editor/KeyGrid';
import { ContextPanel } from '@/editor/ContextPanel';
import { FocusMode } from '@/editor/FocusMode';
import { KeyFocusMode } from '@/editor/KeyFocusMode';

const PAGE_SIZE = 200;

// Plus court en vue par clé : chaque ligne y porte autant d'entrées qu'il y a
// de langues. Charger 200 clés × 7 langues pour un écran qui en montre dix
// serait payer sept fois le prix d'un défilement qu'on ne fera pas.
const KEY_PAGE_SIZE = 50;

/**
 * L'écran, et ses deux façons de regarder le même contenu.
 *
 * **Par langue** (par défaut) — deux langues à l'écran, source et cible, jamais
 * douze. Personne ne traduit vers douze langues simultanément, et une grille à
 * douze colonnes est illisible. Le sélecteur « source → cible » est ici l'objet
 * de navigation principal.
 *
 * **Par clé** — une clé, toutes ses langues, repliées. La question n'est plus
 * « où en est mon travail en espagnol ? » mais « ce libellé est-il prêt
 * partout ? ». Elle se pose avant une mise en production, et juste après avoir
 * ajouté une clé.
 *
 * Le principe des douze colonnes n'est pas abandonné, il est respecté
 * autrement : la vue par clé empile les langues au lieu de les juxtaposer, et
 * les garde repliées. Sept lignes de vingt pixels se lisent ; sept colonnes ne
 * se lisent pas.
 *
 * Tout l'état de filtrage vit dans l'URL, y compris le choix de vue. Ce n'est
 * pas une commodité technique : c'est ce qui permet à une responsable de
 * localisation d'envoyer un lien dans Slack et à la traductrice d'atterrir
 * exactement sur le travail concerné, dans la vue qui convient.
 */
export function EditorPage() {
    const { projectUuid = '' } = useParams();
    const [params, setParams] = useSearchParams();
    const [selectedId, setSelectedId] = useState<string | null>(null);
    const [focusOpen, setFocusOpen] = useState(false);

    const locale = params.get('locale') ?? '';
    const platform = params.get('platform') ?? '';
    const status = (params.get('status') ?? '') as TranslationStatus | '';
    const namespace = params.get('namespace') ?? '';
    const search = params.get('q') ?? '';
    const unassigned = params.get('unassigned') === '1';

    // Deux façons de regarder le même contenu, et deux publics :
    //
    //  - `locale` (par défaut) — deux langues à l'écran, source et cible. La
    //    vue de la traductrice qui vide sa file dans UNE langue.
    //  - `key` — une clé, toutes ses langues. La vue de qui doit juger d'un
    //    libellé avant une mise en production, ou du développeur qui vient
    //    d'ajouter une clé et veut savoir où elle en est.
    //
    // Le choix vit dans l'URL comme le reste des filtres : un lien partagé
    // ouvre la bonne vue sur le bon travail.
    const view = params.get('view') === 'key' ? 'key' : 'locale';

    const stats = useQuery({
        queryKey: ['stats', projectUuid],
        queryFn: () => api.stats(projectUuid),
    });

    // Sans langue explicite, on prend la première cible : arriver sur un écran
    // vide en demandant de choisir est une étape de plus pour rien.
    const effectiveLocale =
        locale || stats.data?.locales.find((l) => !l.isSource)?.code || stats.data?.sourceLocale || '';

    const localeMeta = stats.data?.locales.find((l) => l.code === effectiveLocale);
    const sourceMeta = stats.data?.locales.find((l) => l.isSource);

    const targetLocales = useMemo(
        () => (stats.data?.locales ?? []).filter((l) => !l.isSource),
        [stats.data],
    );

    // Les langues que l'utilisateur peut réellement écrire. Le serveur le dit
    // dans /stats ; on ne le redéduit pas ici du rôle, ce serait une seconde
    // implémentation de la même règle d'autorisation.
    const writableLocales = useMemo(
        () => targetLocales.filter((l) => l.canWrite),
        [targetLocales],
    );

    // « * » = toutes langues. Sans cela l'arbre compterait le travail restant en
    // espagnol au-dessus d'une liste qui montre les sept langues : deux chiffres
    // qui ne parlent pas de la même chose, côte à côte.
    const countingLocale = view === 'key' ? '*' : effectiveLocale;

    const namespaces = useQuery({
        queryKey: ['namespaces', projectUuid, countingLocale],
        queryFn: () => api.namespaces(projectUuid, countingLocale),
        enabled: countingLocale !== '',
    });

    const gridQuery = useMemo(
        () => ({
            projectUuid,
            locale: effectiveLocale,
            platform: platform || undefined,
            status: status || undefined,
            namespace: namespace || undefined,
            q: search || undefined,
            unassigned: unassigned || undefined,
            itemsPerPage: PAGE_SIZE,
        }),
        [projectUuid, effectiveLocale, platform, status, namespace, search, unassigned],
    );

    const grid = useQuery({
        queryKey: ['grid', gridQuery],
        queryFn: () => api.grid(gridQuery),
        enabled: view === 'locale' && effectiveLocale !== '',
        placeholderData: (previous) => previous,
    });

    const keyQuery = useMemo(
        () => ({
            projectUuid,
            platform: platform || undefined,
            status: status || undefined,
            namespace: namespace || undefined,
            q: search || undefined,
            unassigned: unassigned || undefined,
            itemsPerPage: KEY_PAGE_SIZE,
        }),
        [projectUuid, platform, status, namespace, search, unassigned],
    );

    const keyRows = useQuery({
        queryKey: ['keys', keyQuery],
        queryFn: () => api.keys(keyQuery),
        enabled: view === 'key',
        placeholderData: (previous) => previous,
    });

    /**
     * Modification atomique de l'URL.
     *
     * Prend un LOT de paramètres, et non un seul, parce que deux appels
     * successifs dans le même gestionnaire d'événement s'écrasent : la fonction
     * de mise à jour reçoit les paramètres du rendu courant, pas ceux de la
     * navigation encore en attente. Le second appel repart donc de l'état
     * d'avant le premier et gagne.
     *
     * C'est ce qui rendait muets tous les filtres de la colonne de gauche : ils
     * touchent `status` et `unassigned` ensemble.
     */
    const update = useCallback(
        (patch: Record<string, string | null>) => {
            setParams(
                (current) => {
                    const next = new URLSearchParams(current);

                    for (const [key, value] of Object.entries(patch)) {
                        if (value === null || value === '') next.delete(key);
                        else next.set(key, value);
                    }

                    return next;
                },
                { replace: true },
            );
        },
        [setParams],
    );

    const rows = grid.data?.items ?? [];
    const selected = rows.find((row) => row.id === selectedId) ?? null;
    const selectedKey = keyRows.data?.items.find((row) => row.id === selectedId) ?? null;

    const total = (view === 'locale' ? grid.data?.total : keyRows.data?.total) ?? 0;

    // Le panneau de contexte ne parle que de la CLÉ ; les deux vues lui
    // fournissent donc la même chose, chacune extraite de son propre DTO.
    const context = view === 'locale' ? selected : selectedKey;

    const actionable = localeMeta
        ? localeMeta.untranslated + localeMeta.draft + localeMeta.needsReview
        : 0;

    // Lecture seule : la langue existe et se consulte, mais l'utilisateur n'y a
    // pas d'habilitation d'écriture. Le dire AVANT la saisie évite le pire
    // enchaînement possible — traduire, puis se voir refuser l'enregistrement.
    const readOnly = localeMeta !== undefined && !localeMeta.canWrite;

    return (
        <div className="flex h-full flex-col bg-ink-100">
            <header className="flex shrink-0 items-center gap-4 border-b border-ink-200 bg-white px-4 py-2.5">
                <Link
                    to="/projects"
                    className="shrink-0 text-sm font-semibold text-ink-900 hover:text-accent"
                >
                    TQ-Slator
                </Link>

                <div className="h-5 w-px bg-ink-200" />

                {/* La bascule précède le sélecteur de langue : elle décide de ce
                    que les contrôles suivants veulent dire. */}
                <div className="flex shrink-0 rounded-md bg-ink-100 p-0.5">
                    {(
                        [
                            ['locale', 'Par langue', 'Deux langues à l\'écran : la source et une cible.'],
                            ['key', 'Par clé', 'Une clé, toutes ses langues.'],
                        ] as const
                    ).map(([value, label, hint]) => (
                        <button
                            key={value}
                            onClick={() => update({ view: value === 'locale' ? null : value })}
                            title={hint}
                            className={`rounded px-2.5 py-1 text-xs font-medium transition-colors ${
                                view === value
                                    ? 'bg-white text-ink-900 shadow-sm'
                                    : 'text-ink-500 hover:text-ink-800'
                            }`}
                        >
                            {label}
                        </button>
                    ))}
                </div>

                {/* Le sélecteur de langue est l'objet de navigation principal de
                    la vue par langue — et n'a aucun sens dans l'autre, où toutes
                    les langues sont là. Le laisser affiché grisé suggérerait
                    qu'il reste quelque chose à y choisir. */}
                {view === 'locale' ? (
                    <div className="flex items-center gap-2 text-sm">
                        <span className="rounded bg-ink-100 px-2 py-1 font-mono text-xs text-ink-500">
                            {sourceMeta?.code ?? '…'}
                        </span>
                        <span className="text-ink-300">→</span>
                        <select
                            value={effectiveLocale}
                            onChange={(e) => update({ locale: e.target.value })}
                            className="rounded border border-ink-300 bg-white px-2 py-1 text-sm font-medium outline-none focus:border-accent"
                        >
                            {stats.data?.locales
                                .filter((l) => !l.isSource)
                                .map((l) => (
                                    <option key={l.code} value={l.code}>
                                        {l.nativeName}
                                        {!l.canWrite ? ' — lecture seule' : ''}
                                    </option>
                                ))}
                        </select>
                    </div>
                ) : (
                    <span className="shrink-0 text-sm text-ink-500">
                        <span className="rounded bg-ink-100 px-2 py-1 font-mono text-xs">
                            {sourceMeta?.code ?? '…'}
                        </span>
                        <span className="ml-2 text-ink-300">→</span>
                        <span className="ml-2 text-xs">
                            {targetLocales.length} langue{targetLocales.length > 1 ? 's' : ''}
                        </span>
                    </span>
                )}

                <select
                    value={platform}
                    onChange={(e) => update({ platform: e.target.value })}
                    className="rounded border border-ink-300 bg-white px-2 py-1 text-sm outline-none focus:border-accent"
                >
                    <option value="">Toutes plateformes</option>
                    {stats.data?.platforms.map((p) => (
                        <option key={p.slug} value={p.slug}>
                            {p.name}
                        </option>
                    ))}
                </select>

                <input
                    type="search"
                    value={search}
                    onChange={(e) => update({ q: e.target.value })}
                    placeholder="Rechercher une clé ou un texte…"
                    className="min-w-0 flex-1 rounded border border-ink-300 px-2.5 py-1 text-sm outline-none focus:border-accent"
                />

                {view === 'locale' && localeMeta && (
                    <div className="flex shrink-0 items-center gap-3 text-xs">
                        {localeMeta.needsReview > 0 && (
                            <span className="tabular font-medium text-red-600">
                                ⚠ {localeMeta.needsReview} obsolète
                                {localeMeta.needsReview > 1 ? 's' : ''}
                            </span>
                        )}
                        <span className="tabular text-ink-500">
                            {localeMeta.completion}% traduit
                        </span>
                    </div>
                )}

                {/* Le bouton principal n'est pas « enregistrer » mais « traiter la
                    file » : un traducteur ne vient pas explorer, il vient vider
                    une liste. Chaque vue a la sienne — celle d'une langue, ou
                    celle de toutes les langues qu'on a le droit d'écrire. */}
                {view === 'locale' ? (
                    <button
                        onClick={() => setFocusOpen(true)}
                        disabled={actionable === 0 || readOnly}
                        className="shrink-0 rounded-md bg-accent px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-indigo-700 disabled:bg-ink-300"
                    >
                        {readOnly
                            ? 'Lecture seule'
                            : actionable > 0
                              ? `Traiter les ${actionable}`
                              : 'Rien à traiter'}
                    </button>
                ) : (
                    <button
                        onClick={() => setFocusOpen(true)}
                        disabled={writableLocales.length === 0}
                        title={
                            writableLocales.length === 0
                                ? 'Vous n\'êtes habilité à écrire dans aucune langue de ce projet.'
                                : `Enchaîner les clés à traiter en ${writableLocales.map((l) => l.code).join(', ')}.`
                        }
                        className="shrink-0 rounded-md bg-accent px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-indigo-700 disabled:bg-ink-300"
                    >
                        {writableLocales.length === 0
                            ? 'Lecture seule'
                            : `Traiter (${writableLocales.length} langue${writableLocales.length > 1 ? 's' : ''})`}
                    </button>
                )}
            </header>

            {view === 'locale' && readOnly && (
                <div className="shrink-0 border-b border-amber-200 bg-amber-50 px-4 py-1.5 text-xs text-amber-900">
                    Vous n'êtes pas habilité à écrire en {localeMeta?.nativeName}. Consultation
                    uniquement.
                </div>
            )}

            <div className="flex min-h-0 flex-1">
                <NamespaceTree
                    tree={namespaces.data}
                    selected={namespace}
                    status={status}
                    unassigned={unassigned}
                    total={total}
                    onSelectNamespace={(path) => update({ namespace: path })}
                    onSelectFilter={({ status: next, unassigned: on }) =>
                        update({ status: next || null, unassigned: on ? '1' : null })
                    }
                />

                {view === 'locale' ? (
                    <TranslationGrid
                        rows={rows}
                        total={total}
                        loading={grid.isLoading}
                        locale={effectiveLocale}
                        direction={localeMeta?.direction ?? 'ltr'}
                        pluralCategories={localeMeta?.pluralCategories ?? ['other']}
                        selectedId={selectedId}
                        onSelect={setSelectedId}
                        readOnly={readOnly}
                        gridKey={gridQuery}
                    />
                ) : (
                    <KeyGrid
                        rows={keyRows.data?.items ?? []}
                        total={total}
                        loading={keyRows.isLoading}
                        locales={targetLocales}
                        sourceCode={sourceMeta?.code ?? ''}
                        selectedId={selectedId}
                        onSelect={setSelectedId}
                    />
                )}

                <ContextPanel
                    row={context}
                    sourceCode={sourceMeta?.code ?? ''}
                    target={
                        view === 'locale' && selected !== null
                            ? {
                                  localeCode: effectiveLocale,
                                  status: selected.status,
                                  isStale: selected.isStale,
                                  isMachineTranslated: selected.isMachineTranslated,
                                  updatedAt: selected.updatedAt,
                                  updatedBy: selected.updatedBy,
                              }
                            : null
                    }
                    targets={view === 'key' ? (selectedKey?.targets ?? null) : null}
                />
            </div>

            {focusOpen &&
                (view === 'locale' ? (
                    <FocusMode
                        projectUuid={projectUuid}
                        locale={effectiveLocale}
                        direction={localeMeta?.direction ?? 'ltr'}
                        pluralCategories={localeMeta?.pluralCategories ?? ['other']}
                        platform={platform || undefined}
                        onClose={() => setFocusOpen(false)}
                    />
                ) : (
                    <KeyFocusMode
                        projectUuid={projectUuid}
                        locales={targetLocales}
                        platform={platform || undefined}
                        namespace={namespace || undefined}
                        onClose={() => setFocusOpen(false)}
                    />
                ))}
        </div>
    );
}

export type { GridRow };
