/* global React */
// ============================================================
// Traduction automatique de TOUTE l'appli (FR -> EN-US) via l'API DeepL (clé gratuite, appelée
// depuis le Worker — /api/translate-batch dans _worker.js — jamais depuis le navigateur), demandé
// explicitement à la place du système i18n.jsx existant (qui ne couvre que ~27 chaînes codées en
// dur dans 3 fichiers) — ici on traduit le texte RÉELLEMENT AFFICHÉ à l'écran, page par page, sans
// avoir à ajouter des clés partout.
// (Les endpoints gratuits anonymes testés avant DeepL — Google Translate, MyMemory — se sont
// révélés bloqués/à quota épuisé depuis l'IP mutualisée du Worker Cloudflare : une clé API dédiée
// était nécessaire.)
//
// Approche : on parcourt les nœuds texte du DOM (pas le state React) et on remplace leur contenu
// par la traduction — c'est donc purement VISUEL. Comme React peut re-render une partie de l'UI à
// tout moment (chronos, flux vidéo, polling...) et écraserait alors la traduction en repeignant le
// texte français d'origine, un MutationObserver retraduit en continu les nouveaux textes tant que
// le mode anglais est actif. Revenir au français = un simple reload (le state React repart propre).
//
// Important : on ne touche JAMAIS aux VALEURS de champs (usernames, mots de passe, bios, UDID,
// IP...) — seulement le texte d'affichage statique — pour ne jamais risquer de coller une donnée
// traduite sur un téléphone ou de la sauvegarder par erreur.
// ============================================================

const TRANSLATE_CACHE = new Map(); // texte FR -> texte EN, évite de re-demander la même chaîne
const TRANSLATED_NODES = new WeakSet(); // nœuds texte déjà traités (pour ne pas les re-scanner)

// Heuristique : quels nœuds texte NE PAS traduire (données, pas de l'UI statique).
function shouldSkipText(text) {
  const s = text.trim();
  if (!s || s.length < 2) return true;
  if (/^[\d\s.,:%€$/+\-()]+$/.test(s)) return true;              // nombres/dates/pourcentages/prix
  if (/^[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\s]+$/u.test(s)) return true; // emoji seuls
  if (/^@[\w.]+$/.test(s)) return true;                          // @username
  if (/^[a-f0-9]{8}-[a-f0-9-]{27,}$/i.test(s)) return true;       // UDID/uuid
  if (/^\d{1,3}(\.\d{1,3}){3}$/.test(s)) return true;             // adresse IP
  return false;
}
function shouldSkipElement(el) {
  if (!el) return false;
  const tag = el.tagName;
  if (tag === "SCRIPT" || tag === "STYLE" || tag === "NOSCRIPT") return true;
  if (el.closest && el.closest("[data-no-translate]")) return true;
  // Les champs mono (UDID, code, username généré...) affichent quasi toujours des données, pas de l'UI.
  if (el.closest && el.closest(".mono")) return true;
  return false;
}

// Un seul appel réseau pour tout un lot de textes (DeepL accepte jusqu'à 50 segments par requête)
// — renvoie le tableau des traductions dans le même ordre, ou null si l'appel a échoué (clé
// absente, quota dépassé, réseau...), pour que l'appelant sache distinguer "rien à traduire" d'un
// vrai échec plutôt que de l'avaler en silence.
async function deeplTranslateBatch(texts) {
  try {
    const r = await fetch("/api/translate-batch", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ texts }),
    });
    const j = await r.json().catch(() => null);
    if (!j || !j.ok || !Array.isArray(j.translations) || j.translations.length !== texts.length) return null;
    return j.translations;
  } catch (e) {
    return null;
  }
}

// Traduit un lot de nœuds texte, en regroupant les textes par paquets de 50 (limite DeepL) pour
// minimiser le nombre de requêtes — avec quelques paquets en parallèle, pas 200 d'un coup.
// Renvoie des stats {attempted, succeeded} pour que l'appelant sache si l'opération a VRAIMENT
// fonctionné (et pas juste "n'a pas planté") — sert à détecter un échec silencieux de l'API.
async function translateNodesBatch(nodes, chunkSize = 50, concurrency = 3) {
  const toTranslate = [];
  for (const node of nodes) {
    const original = node.__origText != null ? node.__origText : node.textContent;
    if (shouldSkipText(original)) { TRANSLATED_NODES.add(node); continue; }
    toTranslate.push({ node, original });
  }
  const attempted = toTranslate.length;
  let succeeded = 0;

  const chunks = [];
  for (let i = 0; i < toTranslate.length; i += chunkSize) chunks.push(toTranslate.slice(i, i + chunkSize));

  let ci = 0;
  const worker = async () => {
    while (ci < chunks.length) {
      const chunk = chunks[ci++];
      const results = new Array(chunk.length);
      const missIdx = [];
      chunk.forEach((item, idx) => {
        if (TRANSLATE_CACHE.has(item.original)) results[idx] = TRANSLATE_CACHE.get(item.original);
        else missIdx.push(idx);
      });
      if (missIdx.length) {
        const fetched = await deeplTranslateBatch(missIdx.map(idx => chunk[idx].original));
        if (fetched) {
          missIdx.forEach((idx, k) => {
            results[idx] = fetched[k];
            TRANSLATE_CACHE.set(chunk[idx].original, fetched[k]);
          });
        }
      }
      chunk.forEach((item, idx) => {
        const en = results[idx];
        if (en != null) {
          item.node.__origText = item.original; // garde le FR d'origine sur le nœud (utile si le texte change ensuite)
          item.node.textContent = en;
          succeeded++;
        }
        TRANSLATED_NODES.add(item.node);
      });
    }
  };
  await Promise.all(Array.from({ length: concurrency }, worker));
  return { attempted, succeeded };
}

function collectTextNodes(root) {
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
    acceptNode(node) {
      if (!node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT;
      if (shouldSkipElement(node.parentElement)) return NodeFilter.FILTER_REJECT;
      return NodeFilter.FILTER_ACCEPT;
    },
  });
  const nodes = [];
  let n; while ((n = walker.nextNode())) nodes.push(n);
  return nodes;
}

let observer = null;
let pendingTimer = null;
const pendingRoots = new Set();

function scheduleTranslate(root) {
  pendingRoots.add(root);
  if (pendingTimer) return;
  pendingTimer = setTimeout(() => {
    pendingTimer = null;
    const roots = Array.from(pendingRoots); pendingRoots.clear();
    roots.forEach(r => {
      if (!r.isConnected) return;
      const fresh = collectTextNodes(r).filter(n => !TRANSLATED_NODES.has(n));
      if (fresh.length) translateNodesBatch(fresh);
    });
  }, 350);
}

// Renvoie les stats du scan INITIAL (le seul dont l'appelant a besoin pour savoir si la
// traduction a réellement fonctionné) — les lots suivants déclenchés par le MutationObserver
// tournent en arrière-plan sans que personne n'ait besoin d'attendre leur résultat.
function startAutoTranslate() {
  const initial = translateNodesBatch(collectTextNodes(document.body));
  if (observer) return initial;
  observer = new MutationObserver((mutations) => {
    for (const m of mutations) {
      if (m.type === "characterData" && m.target && !TRANSLATED_NODES.has(m.target)) {
        scheduleTranslate(m.target.parentElement || document.body);
      } else if (m.addedNodes && m.addedNodes.length) {
        m.addedNodes.forEach(node => {
          if (node.nodeType === 1 || node.nodeType === 3) scheduleTranslate(node.nodeType === 1 ? node : document.body);
        });
      }
    }
  });
  observer.observe(document.body, { childList: true, subtree: true, characterData: true });
  return initial;
}
function stopAutoTranslate() {
  if (observer) { observer.disconnect(); observer = null; }
  if (pendingTimer) { clearTimeout(pendingTimer); pendingTimer = null; }
  pendingRoots.clear();
}

// État EN MÉMOIRE uniquement (pas de sessionStorage) : sur un vrai reload, la page repart
// forcément en français (le DOM est reconstruit par React), donc persister "actif" au-delà d'un
// reload n'a jamais rien traduit de plus — ça ne faisait que coincer le bouton dans un état
// "actif" fantôme où chaque clic déclenchait juste `if (active) reload()` sans jamais rappeler
// `startAutoTranslate()`. C'était la cause du bug rapporté ("dès qu'on clique ça recharge et ça
// n'applique rien") : un premier clic malheureux (page encore en train de charger, traduction
// interrompue par une navigation...) suffisait à coincer TOUTE la session de l'onglet dans cette
// boucle, puisque sessionStorage survit aux reload/navigations internes.
const TranslateStore = (function () {
  let active = false;
  const subs = new Set();
  return {
    get() { return active; },
    set(v) { active = !!v; subs.forEach(f => f()); },
    sub(cb) { subs.add(cb); return () => subs.delete(cb); },
  };
})();

// Toggle FR/US flottant, présent sur toute l'appli (monté une fois dans main.jsx) — le côté
// actif (FR ou US) est mis en évidence ; cliquer sur l'autre côté bascule. Passer en US traduit
// tout le texte visible ; repasser en FR recharge la page (le state React français d'origine
// n'a jamais été modifié, seul le DOM affiché l'était).
function TranslateFab() {
  const { useState, useEffect } = React;
  const [active, setActiveState] = useState(TranslateStore.get()); // false = FR, true = US
  const [busy, setBusy] = useState(false);
  const [failed, setFailed] = useState(false);
  useEffect(() => TranslateStore.sub(() => setActiveState(TranslateStore.get())), []);

  const goFr = () => { if (active) window.location.reload(); };
  const goUs = async () => {
    if (active || busy) return;
    setBusy(true);
    setFailed(false);
    await new Promise(r => setTimeout(r, 0)); // laisse le spinner s'afficher avant le gros scan initial
    const stats = await startAutoTranslate();
    setBusy(false);
    // Rien n'a pu être traduit alors qu'il y avait bien du texte à traduire -> l'API a échoué
    // (relay en erreur, réponse inattendue...) : on ne bascule PAS en "actif" pour permettre de
    // réessayer, et on avale les traductions déjà tentées pour rien laisser en anglais bancal.
    if (stats.attempted > 0 && stats.succeeded === 0) {
      stopAutoTranslate();
      setFailed(true);
      setTimeout(() => setFailed(false), 4000);
      return;
    }
    TranslateStore.set(true);
  };

  const segBase = {
    padding: "6px 14px", fontSize: 13, fontWeight: 600, cursor: "pointer", border: "none",
    borderRadius: 999, transition: "background .15s, color .15s",
  };
  const onStyle = { background: "var(--accent-soft, #16321f)", color: "var(--accent, #22d3ee)" };
  const offStyle = { background: "transparent", color: "var(--faint, #888)" };

  return (
    <div title={failed ? "Échec de la traduction (API indisponible) — réessayer" : undefined}
      data-no-translate
      style={{
        position: "fixed", top: 10, left: "50%", transform: "translateX(-50%)", zIndex: 9999,
        display: "flex", alignItems: "center", gap: 2, padding: 3, borderRadius: 999,
        background: "var(--surface-2, #1a1a1a)",
        border: "1px solid " + (failed ? "#dc3232" : "var(--border, #333)"),
        boxShadow: "0 4px 16px rgba(0,0,0,.4)", opacity: busy ? 0.7 : 1,
      }}>
      <button onClick={goFr} disabled={busy} style={{ ...segBase, ...(!active ? onStyle : offStyle) }}>🇫🇷 FR</button>
      <button onClick={goUs} disabled={busy} style={{ ...segBase, ...(active ? onStyle : offStyle) }}>
        {busy ? "…" : failed ? "⚠️ US" : "🇺🇸 US"}
      </button>
    </div>
  );
}

Object.assign(window, { TranslateFab, TranslateStore });
