/* global React */
/* ───────────────────────────────────────────────────────────────
   Abba Automotive — i18n engine
   Eigen, verzorgde vertalingen. Nederlands is de brontaal; de
   woordenboeken (dict-*.jsx) mappen elke NL-bronstring naar de
   doeltalen. De engine vervangt tekst direct in de DOM zodat de
   React-componenten zelf onaangeroerd blijven, en hervertaalt bij
   elke re-render via een MutationObserver.
   ─────────────────────────────────────────────────────────────── */

const LANGS = [
  { code: "nl", label: "Nederlands", short: "NL", dir: "ltr" },
  { code: "en", label: "English",    short: "EN", dir: "ltr" },
  { code: "de", label: "Deutsch",    short: "DE", dir: "ltr" },
  { code: "fr", label: "Français",   short: "FR", dir: "ltr" },
  { code: "ar", label: "العربية",    short: "AR", dir: "rtl" },
  { code: "pl", label: "Polski",     short: "PL", dir: "ltr" },
];
const LANG_BY_CODE = Object.fromEntries(LANGS.map((l) => [l.code, l]));

const I18N = {
  observer: null,
  current: "nl",
  reverse: null,    // any-translation-string -> NL source string
};

const SKIP_TAGS = new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEXTAREA"]);
const TRANSLATABLE_ATTRS = ["placeholder", "aria-label", "title", "alt"];

function getLang() {
  try { return localStorage.getItem("abba-lang") || "nl"; } catch (e) { return "nl"; }
}

/* Build a reverse index so we can recover the NL source from any already-shown
   translation — this lets us switch directly between two non-NL languages and
   back to NL without caching per-node state (which would freeze React-driven
   dynamic text). */
function buildReverse() {
  const rev = {};
  const D = window.I18N_DICT || {};
  for (const nl in D) {
    const entry = D[nl];
    for (const code in entry) {
      const v = entry[code];
      if (typeof v === "string" && v.length) {
        const k = v.replace(/\s+/g, " ").trim();
        if (k && !(k in rev)) rev[k] = nl;
      }
    }
  }
  I18N.reverse = rev;
}

/* Given the current trimmed text, find its NL source string (or null if this is
   not translatable content — e.g. numbers, names, React-controlled values). */
function resolveSource(cur) {
  const D = window.I18N_DICT || {};
  if (cur in D) return cur;            // already NL source
  if (!I18N.reverse) buildReverse();
  return I18N.reverse[cur] || null;    // some language's translation
}

function renderFor(src, lang) {
  if (lang === "nl") return src;
  const e = (window.I18N_DICT || {})[src];
  return (e && typeof e[lang] === "string" && e[lang].length) ? e[lang] : src;
}

function translateNodeText(node, lang) {
  const raw = node.nodeValue;
  if (!raw) return;
  const cur = raw.replace(/\s+/g, " ").trim();
  if (!cur) return;
  const src = resolveSource(cur);
  if (!src) return;                    // leave dynamic / unknown text untouched
  const lead = raw.match(/^\s*/)[0];
  const tail = raw.match(/\s*$/)[0];
  const target = lead + renderFor(src, lang) + tail;
  if (node.nodeValue !== target) node.nodeValue = target;
}

function translateAttr(el, attr, lang) {
  if (!el.hasAttribute(attr)) return;
  const raw = el.getAttribute(attr);
  const cur = (raw || "").replace(/\s+/g, " ").trim();
  if (!cur) return;
  const src = resolveSource(cur);
  if (!src) return;
  const target = renderFor(src, lang);
  if (el.getAttribute(attr) !== target) el.setAttribute(attr, target);
}

function translateDocument(lang) {
  const root = document.body;
  if (!root) return;
  const ob = I18N.observer;
  if (ob) ob.disconnect();

  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
    acceptNode(n) {
      if (!n.parentNode || SKIP_TAGS.has(n.parentNode.nodeName)) return NodeFilter.FILTER_REJECT;
      return NodeFilter.FILTER_ACCEPT;
    },
  });
  let n;
  while ((n = walker.nextNode())) translateNodeText(n, lang);

  const sel = TRANSLATABLE_ATTRS.map((a) => "[" + a + "]").join(",");
  root.querySelectorAll(sel).forEach((el) => {
    if (SKIP_TAGS.has(el.nodeName)) return;
    TRANSLATABLE_ATTRS.forEach((a) => { if (el.hasAttribute(a)) translateAttr(el, a, lang); });
  });

  if (ob) ob.observe(root, { childList: true, subtree: true, characterData: true });
}

let _pending = null;
function scheduleRetranslate() {
  if (_pending) return;
  _pending = requestAnimationFrame(() => {
    _pending = null;
    translateDocument(I18N.current);
  });
}

function applyLangChrome(lang) {
  const meta = LANG_BY_CODE[lang] || LANG_BY_CODE.nl;
  document.documentElement.setAttribute("lang", lang);
  document.documentElement.setAttribute("dir", meta.dir);
}

function setLang(lang) {
  if (!LANG_BY_CODE[lang]) lang = "nl";
  I18N.current = lang;
  try { localStorage.setItem("abba-lang", lang); } catch (e) {}
  applyLangChrome(lang);
  translateDocument(lang);
  window.dispatchEvent(new CustomEvent("abba-langchange", { detail: { lang } }));
}

function initI18n() {
  const lang = getLang();
  I18N.current = lang;
  applyLangChrome(lang);

  // Observe future React renders and re-translate the new Dutch nodes.
  I18N.observer = new MutationObserver(scheduleRetranslate);
  if (document.body) {
    I18N.observer.observe(document.body, { childList: true, subtree: true, characterData: true });
  }
  // Initial pass (also restores NL cleanly if lang === 'nl').
  translateDocument(lang);
}

/* ─── VLAGGEN (inline SVG, cross-platform — geen emoji) ───────── */
function Flag({ code }) {
  const flags = {
    nl: (
      <svg viewBox="0 0 22 15" width="22" height="15" aria-hidden="true">
        <rect width="22" height="5" y="0" fill="#AE1C28" />
        <rect width="22" height="5" y="5" fill="#fff" />
        <rect width="22" height="5" y="10" fill="#21468B" />
      </svg>
    ),
    en: (
      <svg viewBox="0 0 22 15" width="22" height="15" aria-hidden="true">
        <rect width="22" height="15" fill="#012169" />
        <g stroke="#fff" strokeWidth="3"><path d="M0 0l22 15M22 0L0 15" /></g>
        <g stroke="#C8102E" strokeWidth="1.5"><path d="M0 0l22 15M22 0L0 15" /></g>
        <rect x="9" width="4" height="15" fill="#fff" />
        <rect y="5.5" width="22" height="4" fill="#fff" />
        <rect x="9.7" width="2.6" height="15" fill="#C8102E" />
        <rect y="6.2" width="22" height="2.6" fill="#C8102E" />
      </svg>
    ),
    de: (
      <svg viewBox="0 0 22 15" width="22" height="15" aria-hidden="true">
        <rect width="22" height="5" y="0" fill="#000" />
        <rect width="22" height="5" y="5" fill="#DD0000" />
        <rect width="22" height="5" y="10" fill="#FFCE00" />
      </svg>
    ),
    fr: (
      <svg viewBox="0 0 22 15" width="22" height="15" aria-hidden="true">
        <rect width="7.34" height="15" x="0" fill="#0055A4" />
        <rect width="7.34" height="15" x="7.34" fill="#fff" />
        <rect width="7.34" height="15" x="14.66" fill="#EF4135" />
      </svg>
    ),
    ar: (
      <svg viewBox="0 0 22 15" width="22" height="15" aria-hidden="true">
        <rect width="22" height="15" fill="#006C35" />
        <g fill="#fff">
          <rect x="5" y="5" width="3" height="1.3" rx="0.5" />
          <rect x="9" y="5" width="4" height="1.3" rx="0.5" />
          <rect x="14" y="5" width="3" height="1.3" rx="0.5" />
          <rect x="4" y="9.3" width="14" height="1.1" rx="0.55" />
          <circle cx="4" cy="9.85" r="0.9" />
        </g>
      </svg>
    ),
    pl: (
      <svg viewBox="0 0 22 15" width="22" height="15" aria-hidden="true">
        <rect width="22" height="7.5" y="0" fill="#fff" />
        <rect width="22" height="7.5" y="7.5" fill="#DC143C" />
      </svg>
    ),
  };
  return <span className="langsw__flag">{flags[code] || flags.nl}</span>;
}

/* ─── LANGUAGE SWITCHER (React, used inside Nav) ─────────────── */
function LangSwitcher() {
  const { useState, useEffect, useRef } = React;
  const [lang, setLangState] = useState(getLang());
  const [open, setOpen] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    const onChange = () => setLangState(getLang());
    window.addEventListener("abba-langchange", onChange);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      window.removeEventListener("abba-langchange", onChange);
    };
  }, []);

  const meta = LANG_BY_CODE[lang] || LANG_BY_CODE.nl;
  const choose = (code) => { setOpen(false); setLangState(code); setLang(code); };

  return (
    <div className="langsw" ref={ref}>
      <button
        type="button"
        className={"langsw__btn " + (open ? "open" : "")}
        onClick={() => setOpen((v) => !v)}
        aria-haspopup="listbox"
        aria-expanded={open}
        aria-label="Kies taal / Choose language"
        title="Taal / Language"
      >
        <Flag code={meta.code} />
        <span className="langsw__caret" aria-hidden="true" />
      </button>
      {open && (
        <ul className="langsw__menu" role="listbox">
          {LANGS.map((l) => (
            <li key={l.code} role="option" aria-selected={l.code === lang}>
              <button
                type="button"
                className={"langsw__opt " + (l.code === lang ? "active" : "")}
                onClick={() => choose(l.code)}
                dir={l.dir}
              >
                <Flag code={l.code} />
                <span className="langsw__opt-label">{l.label}</span>
                {l.code === lang && <span className="langsw__check" aria-hidden="true" />}
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

window.I18N_DICT = window.I18N_DICT || {};
Object.assign(window, { LANGS, setLang, getLang, initI18n, LangSwitcher, Flag });

/* Boot once the DOM is ready; React mounts shortly after and the observer
   catches that render. */
if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", () => setTimeout(initI18n, 0));
} else {
  setTimeout(initI18n, 0);
}
