/**
 * SysAdmin: UI language sets (DB-backed i18n).
 */

import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { FaDownload, FaFileExport, FaFileImport, FaRedo, FaSync, FaTrash } from 'react-icons/fa';
import api from '../../api';
import axios from 'axios';
import { FormGeneratorTable, type ColumnConfig } from '../../components/FormGenerator/FormGeneratorTable/FormGeneratorTable';
import { useConfirm } from '../../hooks/useConfirm';
import { useLanguage } from '../../providers/language/LanguageContext';
import styles from './Admin.module.css';

type LangRow = {
  id: string;
  label: string;
  status: string;
  entriesCount: number;
  uiCount: number;
  gatewayCount: number;
  updating: boolean;
};

type ProgressInfo = {
  message: string;
  current: number;
  total: number;
  error?: string;
  done?: boolean;
  /** Short title (e.g. language label) to avoid long sentences in the overlay. */
  progressHeading?: string;
  /** Keys in language set before sync (from sync-diff). */
  keysCurrent?: number;
  /** New keys vs xx before PUT (from sync-diff). */
  keysPending?: number;
  /** Master (xx) key count (from sync-diff). */
  keysMasterTotal?: number;
  /** Keys AI-translated in last PUT (optional). */
  keysTranslated?: number;
};

function _getColumns(t: (key: string) => string): ColumnConfig[] {
  return [
    { key: 'id', label: t('Code'), type: 'text', sortable: true, filterable: true, width: 90 },
    { key: 'label', label: t('Bezeichnung'), type: 'text', sortable: true, filterable: true, width: 200 },
    {
      key: 'status',
      label: t('Status'),
      type: 'text',
      sortable: true,
      filterable: true,
      width: 160,
      formatter: (_val: any, row: any) => {
        const r = row as LangRow;
        if (r.updating) {
          return (
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: 'var(--color-warning, #e6a700)' }}>
              <FaSync style={{ animation: 'spin 1s linear infinite', fontSize: '0.85em' }} />
              {t('wird aktualisiert…')}
            </span>
          );
        }
        if (r.status === 'generating') {
          return (
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: 'var(--color-warning, #e6a700)' }}>
              <FaSync style={{ animation: 'spin 1s linear infinite', fontSize: '0.85em' }} />
              {t('wird erzeugt…')}
            </span>
          );
        }
        return r.status;
      },
    },
    { key: 'uiCount', label: t('UI'), type: 'number', sortable: true, width: 80 },
    { key: 'gatewayCount', label: t('API'), type: 'number', sortable: true, width: 80 },
    { key: 'entriesCount', label: t('Gesamt'), type: 'number', sortable: true, width: 80 },
  ];
}

// ISO 639 catalog (codes + native labels + priority order) is provided by the
// gateway via GET /api/i18n/iso-choices. We must NOT keep a local copy here --
// any divergence between frontend and backend caused subtle bugs (e.g. user
// could create a language code that the AI translation prompt did not know how
// to label). The catalog is fetched once on mount and held in component state.
type IsoChoice = { value: string; label: string };
type IsoCatalogResponse = { priorityCodes: string[]; choices: IsoChoice[] };

function _isAbortError(e: unknown): boolean {
  if (axios.isCancel(e)) return true;
  if (e && typeof e === 'object') {
    const err = e as { code?: string; name?: string };
    if (err.code === 'ERR_CANCELED' || err.name === 'AbortError' || err.name === 'CanceledError') return true;
  }
  return false;
}

// ---------------------------------------------------------------------------
// Progress overlay component
// ---------------------------------------------------------------------------

const _ProgressOverlay: React.FC<{
  progress: ProgressInfo;
  onAbort?: () => void;
}> = ({ progress, onAbort }) => {
  const { t } = useLanguage();
  const canAbort = Boolean(onAbort && !progress.done && !progress.error);
  const master = progress.keysMasterTotal;
  const pending = progress.keysPending ?? 0;
  const cur = progress.keysCurrent ?? 0;
  const translated = progress.keysTranslated;
  const hasMaster = master != null && master > 0;

  const entryJ = pending > 0 ? (translated ?? 0) : cur;
  const entryTotal = pending > 0 ? pending : (master ?? 0);
  const showEntryMeter = hasMaster && entryTotal > 0;
  const awaitingAi =
    pending > 0 &&
    translated === undefined &&
    !progress.done &&
    !progress.error;

  const [softEntryPct, setSoftEntryPct] = useState(0);
  useEffect(() => {
    if (!awaitingAi) {
      setSoftEntryPct(0);
      return;
    }
    const started = Date.now();
    const id = window.setInterval(() => {
      const elapsed = Date.now() - started;
      const cap = 94;
      setSoftEntryPct(Math.min(cap, cap * (1 - Math.exp(-elapsed / 14000))));
    }, 220);
    return () => clearInterval(id);
  }, [awaitingAi]);

  let entryPct = 0;
  if (pending > 0) {
    if (translated !== undefined && pending > 0) {
      entryPct = Math.min(100, Math.round((translated / pending) * 100));
    }
  } else if (master && master > 0) {
    entryPct = Math.min(100, Math.round((cur / master) * 100));
  }

  const entryBarPct = awaitingAi ? softEntryPct : entryPct;
  const entryJShown = awaitingAi
    ? Math.min(pending, Math.max(0, Math.floor((softEntryPct / 100) * pending)))
    : entryJ;

  const showLangMeter = progress.total > 1 || !showEntryMeter;
  const pctLang = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0;
  const title = progress.progressHeading ?? progress.message;

  const barBg = progress.error ? 'var(--error-color, #c53030)' : 'var(--primary-color, #3182ce)';

  return (
    <div
      style={{
        position: 'absolute',
        inset: 0,
        background: 'rgba(var(--bg-rgb, 255,255,255), 0.85)',
        backdropFilter: 'blur(2px)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        zIndex: 50,
        borderRadius: 'var(--border-radius, 8px)',
      }}
    >
      <div
        style={{
          background: 'var(--card-bg, #fff)',
          border: '1px solid var(--border-color, #ddd)',
          borderRadius: 'var(--border-radius, 8px)',
          padding: '2rem 2.5rem',
          minWidth: 320,
          maxWidth: 420,
          boxShadow: '0 4px 24px rgba(0,0,0,0.12)',
          textAlign: 'center',
        }}
      >
        <p style={{ fontWeight: 600, fontSize: '1.05rem', margin: '0 0 1rem', lineHeight: 1.3 }}>
          {title}
        </p>
        {showLangMeter && (
          <>
            <p
              style={{
                margin: '0 0 0.35rem',
                fontSize: '1rem',
                fontWeight: 600,
                fontVariantNumeric: 'tabular-nums',
                color: 'var(--text-primary, #1e293b)',
              }}
            >
              {progress.current} / {progress.total}
            </p>
            <div
              style={{
                width: '100%',
                height: 8,
                background: 'var(--border-color, #e2e8f0)',
                borderRadius: 4,
                overflow: 'hidden',
                marginBottom: showEntryMeter ? '0.85rem' : 0,
              }}
            >
              <div
                style={{
                  width: `${pctLang}%`,
                  height: '100%',
                  background: barBg,
                  borderRadius: 4,
                  transition: 'width 0.3s ease',
                }}
              />
            </div>
          </>
        )}
        {showEntryMeter && (
          <>
            <p
              style={{
                margin: '0 0 0.35rem',
                fontSize: '1rem',
                fontWeight: 600,
                fontVariantNumeric: 'tabular-nums',
                color: 'var(--text-primary, #1e293b)',
              }}
            >
              {entryJShown} / {entryTotal}
            </p>
            <div className={styles.i18nKeysProgressTrack}>
              <div
                className={styles.i18nKeysProgressFill}
                style={{
                  width: `${entryBarPct}%`,
                  background: barBg,
                  transition: awaitingAi ? 'width 0.25s ease-out' : 'width 0.35s ease',
                }}
              />
            </div>
          </>
        )}
        {progress.error && (
          <p style={{ color: 'var(--error-color, #c53030)', fontSize: '0.85rem', marginTop: '0.75rem' }}>
            {progress.error}
          </p>
        )}
        {canAbort && (
          <button
            type="button"
            onClick={onAbort}
            style={{
              marginTop: '1.25rem',
              padding: '0.5rem 1.25rem',
              borderRadius: 6,
              border: '1px solid var(--border-color, #cbd5e1)',
              background: 'var(--surface-color, #f8fafc)',
              color: 'var(--text-primary, #1e293b)',
              fontWeight: 600,
              cursor: 'pointer',
            }}
          >
            {t('Abbrechen')}
          </button>
        )}
      </div>
    </div>
  );
};

// ---------------------------------------------------------------------------
// Main component
// ---------------------------------------------------------------------------

export const AdminLanguagesPage: React.FC = () => {
  const { t, reloadLanguage, refreshAvailableLanguages } = useLanguage();
  const { confirm, ConfirmDialog } = useConfirm();
  const [rows, setRows] = useState<LangRow[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [addCode, setAddCode] = useState('');
  const [progress, setProgress] = useState<ProgressInfo | null>(null);
  const [search, setSearch] = useState('');
  const [isoCatalog, setIsoCatalog] = useState<IsoCatalogResponse>({ priorityCodes: [], choices: [] });
  const busyRef = useRef(false);
  const abortRef = useRef<AbortController | null>(null);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await api.get('/api/i18n/iso-choices');
        if (cancelled) return;
        const data = res.data as IsoCatalogResponse;
        setIsoCatalog({
          priorityCodes: Array.isArray(data?.priorityCodes) ? data.priorityCodes : [],
          choices: Array.isArray(data?.choices) ? data.choices : [],
        });
      } catch (e) {
        console.error('Failed to load ISO language catalog from /api/i18n/iso-choices:', e);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  const _endProgressSoon = useCallback((ms: number) => {
    window.setTimeout(() => {
      setProgress(null);
      busyRef.current = false;
      abortRef.current = null;
    }, ms);
  }, []);

  const _abortRunning = useCallback(() => {
    abortRef.current?.abort();
  }, []);

  const _load = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      const res = await api.get('/api/i18n/codes');
      const list = (res.data || []) as any[];
      setRows(
        list.map((r) => ({
          id: r.code,
          label: r.label || r.code,
          status: r.status || '',
          entriesCount: r.entriesCount ?? 0,
          uiCount: r.uiCount ?? 0,
          gatewayCount: r.gatewayCount ?? 0,
          updating: !!r.updating,
        })),
      );
    } catch (e: any) {
      setError(e.response?.data?.detail || e.message || 'Laden fehlgeschlagen');
    } finally {
      setLoading(false);
    }
  }, []);

  /** Einheitliche Abbruch-Meldung + Overlay-Ausblendung; optional Listen neu laden (teilweise fertige „Alle aktualisieren“). */
  const _finishProgressAborted = useCallback(
    async (
      partial: Pick<ProgressInfo, 'current' | 'total'> & Partial<Pick<ProgressInfo, 'progressHeading'>>,
      ms: number,
      refreshLists: boolean,
    ) => {
      setProgress({
        message: t('Vorgang abgebrochen.'),
        error: t('Die Operation wurde abgebrochen.'),
        done: true,
        ...partial,
      });
      if (refreshLists) {
        await _load();
        await refreshAvailableLanguages();
        await reloadLanguage();
      }
      _endProgressSoon(ms);
    },
    [t, _endProgressSoon, _load, refreshAvailableLanguages, reloadLanguage],
  );

  useEffect(() => {
    _load();
  }, [_load]);

  const _hasUpdating = rows.some((r) => r.updating || r.status === 'generating');

  useEffect(() => {
    if (!_hasUpdating) return;
    const id = window.setInterval(() => {
      _load();
    }, 5000);
    return () => window.clearInterval(id);
  }, [_hasUpdating, _load]);

  const displayRows = useMemo(() => {
    const term = search.trim().toLowerCase();
    const filtered = term
      ? rows.filter((r) => r.id.toLowerCase().includes(term) || r.label.toLowerCase().includes(term) || r.status.toLowerCase().includes(term))
      : rows;
    return [...filtered].sort((a, b) => {
      if (a.id === 'xx') return -1;
      if (b.id === 'xx') return 1;
      return a.id.localeCompare(b.id);
    });
  }, [rows, search]);

  const existingCodes = useMemo(() => new Set(rows.map((r) => r.id)), [rows]);

  const addChoices = useMemo(() => {
    const available = isoCatalog.choices.filter((c) => !existingCodes.has(c.value));
    const priority = isoCatalog.priorityCodes;
    available.sort((a, b) => {
      const aPrio = priority.indexOf(a.value);
      const bPrio = priority.indexOf(b.value);
      if (aPrio !== -1 && bPrio !== -1) return aPrio - bPrio;
      if (aPrio !== -1) return -1;
      if (bPrio !== -1) return 1;
      return a.label.localeCompare(b.label);
    });
    return available;
  }, [existingCodes, isoCatalog]);

  useEffect(() => {
    if (addChoices.length > 0 && (!addCode || !addChoices.find((c) => c.value === addCode))) {
      setAddCode(addChoices[0].value);
    }
  }, [addChoices, addCode]);

  const _fetchI18nEntriesFromBundle = useCallback(async (signal?: AbortSignal): Promise<any[]> => {
    const base = import.meta.env.BASE_URL || '/';
    const normalizedBase = base.endsWith('/') ? base : `${base}/`;
    const res = await fetch(`${normalizedBase}i18n-keys.json`, { signal });
    if (!res.ok) {
      throw new Error(
        t('i18n-keys.json nicht gefunden. Bitte Frontend neu bauen oder Dev-Server starten.'),
      );
    }
    const data = await res.json();
    if (!Array.isArray(data)) {
      throw new Error(t('Ungültiges i18n-keys.json'));
    }
    return data;
  }, [t]);

  // --- Actions with progress ------------------------------------------------

  const _syncXx = async () => {
    if (busyRef.current) return;
    busyRef.current = true;
    const ac = new AbortController();
    abortRef.current = ac;
    const { signal } = ac;
    setError(null);
    setProgress({ message: t('Basisset wird eingelesen…'), current: 0, total: 1 });
    try {
      const entries = await _fetchI18nEntriesFromBundle(signal);
      const res = await api.put('/api/i18n/sets/sync-xx', { entries }, { signal });
      const d = res.data || {};
      const addedCount = d.added?.length ?? 0;
      const removedCount = d.removed?.length ?? 0;
      const entriesCount = d.entriesCount ?? 0;
      setProgress({
        message: t('Basisset synchronisiert: {added} neu, {removed} entfernt, {total} Einträge.', {
          added: String(addedCount),
          removed: String(removedCount),
          total: String(entriesCount),
        }),
        current: 1,
        total: 1,
        done: true,
      });
      await _load();
      await refreshAvailableLanguages();
      await reloadLanguage();
      _endProgressSoon(2500);
    } catch (e: any) {
      if (_isAbortError(e)) {
        await _finishProgressAborted({ current: 0, total: 1 }, 2200, false);
        return;
      }
      const msg = e.response?.data?.detail || e.message;
      setProgress({ message: t('Fehler beim Einlesen'), current: 0, total: 1, error: msg, done: true });
      setError(msg);
      _endProgressSoon(2500);
    }
  };

  const _updateOne = async (code: string) => {
    if (busyRef.current) return;
    busyRef.current = true;
    const ac = new AbortController();
    abortRef.current = ac;
    const { signal } = ac;
    setError(null);
    const label = rows.find((r) => r.id === code)?.label || code;
    let keysCurrent: number | undefined;
    let keysPending: number | undefined;
    let keysMasterTotal: number | undefined;
    try {
      const dr = await api.get(`/api/i18n/sets/${encodeURIComponent(code)}/sync-diff`, { signal });
      keysCurrent = dr.data?.currentEntryCount;
      keysPending = dr.data?.addedCount;
      keysMasterTotal = dr.data?.masterEntryCount;
    } catch (e) {
      if (_isAbortError(e)) {
        await _finishProgressAborted({ progressHeading: label, current: 0, total: 1 }, 2200, false);
        return;
      }
      /* sync-diff optional */
    }
    setProgress({
      message: t('Aktualisiere {lang}…', { lang: label }),
      progressHeading: label,
      current: 0,
      total: 1,
      keysCurrent,
      keysPending,
      keysMasterTotal,
    });
    try {
      const putRes = await api.put(`/api/i18n/sets/${encodeURIComponent(code)}`, {}, { signal });
      const d = putRes.data || {};
      const pendingAfterPut = Array.isArray(d.added) ? d.added.length : (keysPending ?? 0);
      setProgress({
        message: t('{lang} aktualisiert.', { lang: label }),
        progressHeading: label,
        current: 1,
        total: 1,
        done: true,
        keysCurrent: typeof d.entriesCount === 'number' ? d.entriesCount : keysCurrent,
        keysPending: pendingAfterPut,
        keysMasterTotal,
        keysTranslated: typeof d.translated === 'number' ? d.translated : undefined,
      });
      await _load();
      await refreshAvailableLanguages();
      await reloadLanguage();
      _endProgressSoon(2000);
    } catch (e: any) {
      if (_isAbortError(e)) {
        await _finishProgressAborted({ progressHeading: label, current: 0, total: 1 }, 2200, false);
        return;
      }
      const msg = e.response?.data?.detail || e.message;
      setProgress({
        message: t('Fehler bei {lang}', { lang: label }),
        progressHeading: label,
        current: 0,
        total: 1,
        error: msg,
        done: true,
      });
      setError(msg);
      _endProgressSoon(2000);
    }
  };

  const _updateAll = async () => {
    if (busyRef.current) return;
    const ok = await confirm(t('Alle Sprachsets jetzt mit dem Basisset synchronisieren und per KI aktualisieren?'), {
      confirmLabel: t('Alle aktualisieren'),
      cancelLabel: t('Abbrechen'),
    });
    if (!ok) return;

    busyRef.current = true;
    const ac = new AbortController();
    abortRef.current = ac;
    const { signal } = ac;
    setError(null);

    const langCodes = rows.filter((r) => r.id !== 'xx').map((r) => r.id);
    const totalSteps = 1 + langCodes.length;
    let step = 0;

    setProgress({ message: t('Basisset wird eingelesen…'), current: step, total: totalSteps });

    try {
      const entries = await _fetchI18nEntriesFromBundle(signal);
      await api.put('/api/i18n/sets/sync-xx', { entries }, { signal });
      step++;
      setProgress({ message: t('Basisset synchronisiert.'), current: step, total: totalSteps });
    } catch (e: any) {
      if (_isAbortError(e)) {
        await _finishProgressAborted({ current: step, total: totalSteps }, 2800, false);
        return;
      }
      const msg = e.response?.data?.detail || e.message;
      setProgress({ message: t('Fehler beim Basisset'), current: step, total: totalSteps, error: msg, done: true });
      setError(msg);
      _endProgressSoon(3000);
      return;
    }

    const errors: string[] = [];
    for (const code of langCodes) {
      if (signal.aborted) {
        setProgress({
          message: t('Vorgang abgebrochen.'),
          current: step,
          total: totalSteps,
          error: t('Die Operation wurde abgebrochen.'),
          done: true,
        });
        await _load();
        await refreshAvailableLanguages();
        await reloadLanguage();
        _endProgressSoon(3500);
        return;
      }
      const label = rows.find((r) => r.id === code)?.label || code;
      let keysCurrent: number | undefined;
      let keysPending: number | undefined;
      let keysMasterTotal: number | undefined;
      try {
        const dr = await api.get(`/api/i18n/sets/${encodeURIComponent(code)}/sync-diff`, { signal });
        keysCurrent = dr.data?.currentEntryCount;
        keysPending = dr.data?.addedCount;
        keysMasterTotal = dr.data?.masterEntryCount;
      } catch (e) {
        if (_isAbortError(e)) {
          await _finishProgressAborted({ progressHeading: label, current: step + 1, total: totalSteps }, 3500, true);
          return;
        }
        /* sync-diff optional */
      }
      setProgress({
        message: t('Aktualisiere {lang}…', { lang: label }),
        current: step + 1,
        total: totalSteps,
        keysCurrent,
        keysPending,
        keysMasterTotal,
      });
      try {
        const putRes = await api.put(`/api/i18n/sets/${encodeURIComponent(code)}`, {}, { signal });
        const d = putRes.data || {};
        const pendingAfterPut = Array.isArray(d.added) ? d.added.length : (keysPending ?? 0);
        setProgress({
          message: t('Aktualisiere {lang}…', { lang: label }),
          progressHeading: label,
          current: step + 1,
          total: totalSteps,
          keysCurrent: typeof d.entriesCount === 'number' ? d.entriesCount : keysCurrent,
          keysPending: pendingAfterPut,
          keysMasterTotal,
          keysTranslated: typeof d.translated === 'number' ? d.translated : undefined,
        });
      } catch (e: any) {
        if (_isAbortError(e)) {
          await _finishProgressAborted({ progressHeading: label, current: step + 1, total: totalSteps }, 3500, true);
          return;
        }
        errors.push(`${code}: ${e.response?.data?.detail || e.message}`);
      }
      step++;
    }

    if (errors.length > 0) {
      const errMsg = errors.join('; ');
      setProgress({
        message: t('{ok} von {total} Sprachen aktualisiert.', { ok: String(langCodes.length - errors.length), total: String(langCodes.length) }),
        current: step,
        total: totalSteps,
        error: errMsg,
        done: true,
      });
      setError(errMsg);
    } else {
      setProgress({
        message: t('Alle {count} Sprachen erfolgreich aktualisiert.', { count: String(langCodes.length) }),
        current: totalSteps,
        total: totalSteps,
        done: true,
      });
    }

    await _load();
    await refreshAvailableLanguages();
    await reloadLanguage();
    _endProgressSoon(errors.length > 0 ? 5000 : 2500);
  };

  // --- Other actions (unchanged logic, but with busy guard) -----------------

  const _delete = async (code: string) => {
    if (busyRef.current) return;
    if (code === 'xx') return;
    const ok = await confirm(t('Sprachset {code} wirklich löschen?', { code }), {
      confirmLabel: t('Löschen'),
      cancelLabel: t('Abbrechen'),
      variant: 'danger',
    });
    if (!ok) return;
    try {
      await api.delete(`/api/i18n/sets/${encodeURIComponent(code)}`);
      await _load();
      await refreshAvailableLanguages();
      await reloadLanguage();
    } catch (e: any) {
      setError(e.response?.data?.detail || e.message);
    }
  };

  const _download = async (code: string) => {
    try {
      const response = await api.get(`/api/i18n/sets/${encodeURIComponent(code)}/download`, {
        responseType: 'blob',
      });
      const blob = new Blob([response.data], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `ui-language-${code}.json`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    } catch (e: any) {
      setError(e.response?.data?.detail || e.message);
    }
  };

  const _add = async () => {
    if (busyRef.current) return;
    const code = String(addCode).trim().toLowerCase();
    if (!code) return;
    const go = await confirm(
      t('Die Erstellung einer neuen Sprache kann AI-Guthaben auf Ihrem Mandats-Pool belasten. Fortfahren?'),
      { confirmLabel: t('Fortfahren'), cancelLabel: t('Abbrechen') },
    );
    if (!go) return;
    busyRef.current = true;
    const ac = new AbortController();
    abortRef.current = ac;
    const { signal } = ac;
    setProgress({ message: t('Sprache wird erstellt…'), current: 0, total: 1 });
    try {
      await api.post('/api/i18n/sets', { code }, { signal });
      setProgress({ message: t('Sprache erstellt. KI-Übersetzung läuft im Hintergrund.'), current: 1, total: 1, done: true });
      await _load();
      await refreshAvailableLanguages();
      _endProgressSoon(2500);
    } catch (e: any) {
      if (_isAbortError(e)) {
        await _finishProgressAborted({ current: 0, total: 1 }, 2200, false);
        return;
      }
      const msg = e.response?.data?.detail || e.message;
      setProgress({ message: t('Fehler'), current: 0, total: 1, error: msg, done: true });
      setError(msg);
      _endProgressSoon(2500);
    }
  };

  const _exportAll = async () => {
    try {
      const response = await api.get('/api/i18n/export', { responseType: 'blob' });
      const blob = new Blob([response.data], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'ui-languages-export.json';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    } catch (e: any) {
      setError(e.response?.data?.detail || e.message);
    }
  };

  const _importFile = async () => {
    if (busyRef.current) return;
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = '.json';
    input.onchange = async () => {
      const file = input.files?.[0];
      if (!file) return;
      const ok = await confirm(
        t('Sprachdatenbank aus Datei importieren? Bestehende Sets werden überschrieben.'),
        { confirmLabel: t('Importieren'), cancelLabel: t('Abbrechen') },
      );
      if (!ok) return;
      busyRef.current = true;
      const ac = new AbortController();
      abortRef.current = ac;
      const { signal } = ac;
      setProgress({ message: t('Importiere…'), current: 0, total: 1 });
      try {
        const formData = new FormData();
        formData.append('file', file);
        const res = await api.post('/api/i18n/import', formData, {
          headers: { 'Content-Type': 'multipart/form-data' },
          signal,
        });
        const d = res.data || {};
        setError(null);
        setProgress({
          message: t('Import abgeschlossen: {created} erstellt, {updated} aktualisiert.', {
            created: String(d.created?.length ?? 0),
            updated: String(d.updated?.length ?? 0),
          }),
          current: 1,
          total: 1,
          done: true,
        });
        await _load();
        await refreshAvailableLanguages();
        await reloadLanguage();
        _endProgressSoon(2500);
      } catch (e: any) {
        if (_isAbortError(e)) {
          await _finishProgressAborted({ current: 0, total: 1 }, 2200, false);
          return;
        }
        const msg = e.response?.data?.detail || e.message;
        setProgress({ message: t('Import fehlgeschlagen'), current: 0, total: 1, error: msg, done: true });
        setError(msg);
        _endProgressSoon(2500);
      }
    };
    input.click();
  };

  const isBusy = progress !== null;

  return (
    <div className={`${styles.adminPage} ${styles.adminPageFill}`} style={{ gap: '1rem', position: 'relative' }}>
      <header>
        <h1 className={styles.pageTitle}>{t('UI-Sprachen')}</h1>
        <p className={styles.pageSubtitle}>{t('Globale Sprachsets verwalten (SysAdmin).')}</p>
        {error && !progress && (
          <p style={{ color: 'var(--error-color, #c53030)' }}>
            {error}
          </p>
        )}
      </header>

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.75rem', alignItems: 'center' }}>
        <button type="button" className={styles.secondaryButton} onClick={_load} disabled={isBusy || loading} title={t('Daten neu laden')}>
          <FaSync style={loading ? { animation: 'spin 1s linear infinite' } : undefined} />
        </button>
        <input
          type="text"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          placeholder={t('Suche…')}
          style={{ padding: '0.35rem 0.5rem', minWidth: 140, maxWidth: 200 }}
        />
        <button type="button" className={styles.primaryButton} onClick={_updateAll} disabled={isBusy}>
          {t('Alle aktualisieren')}
        </button>
        <button type="button" className={styles.secondaryButton} onClick={_exportAll} disabled={isBusy}>
          <FaFileExport /> {t('Export')}
        </button>
        <button type="button" className={styles.secondaryButton} onClick={_importFile} disabled={isBusy}>
          <FaFileImport /> {t('Import')}
        </button>
        <span style={{ borderLeft: '1px solid var(--border-color)', height: '1.5rem' }} />
        <span style={{ opacity: 0.7 }}>{t('Neue Sprache')}</span>
        <select
          value={addCode}
          onChange={(e) => setAddCode(e.target.value)}
          style={{ padding: '0.35rem 0.5rem' }}
          disabled={isBusy}
        >
          {addChoices.map((c) => (
            <option key={c.value} value={c.value}>
              {c.label}
            </option>
          ))}
        </select>
        <button type="button" className={styles.primaryButton} onClick={_add} disabled={addChoices.length === 0 || isBusy}>
          {t('Hinzufügen')}
        </button>
      </div>

      <div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
        <FormGeneratorTable
          data={displayRows}
          columns={_getColumns(t)}
          loading={loading}
          pagination={false}
          selectable={false}
          searchable={false}
          customActions={[
            {
              id: 'sync-xx',
              title: t('UI-Keys einlesen'),
              icon: <FaSync />,
              onClick: () => _syncXx(),
              visible: (row: LangRow) => row.id === 'xx',
            },
            {
              id: 'upd',
              title: t('Aktualisieren'),
              icon: <FaRedo />,
              onClick: (row: LangRow) => _updateOne(row.id),
              visible: (row: LangRow) => row.id !== 'xx',
            },
            {
              id: 'dl',
              title: t('Herunterladen'),
              icon: <FaDownload />,
              onClick: (row: LangRow) => _download(row.id),
            },
            {
              id: 'del',
              title: t('Löschen'),
              icon: <FaTrash />,
              onClick: (row: LangRow) => _delete(row.id),
              visible: (row: LangRow) => row.id !== 'xx',
            },
          ]}
          emptyMessage={t('Keine Einträge')}
        />

        {progress && <_ProgressOverlay progress={progress} onAbort={_abortRunning} />}
      </div>

      <ConfirmDialog />
    </div>
  );
};

export default AdminLanguagesPage;
