// i18n.jsx — shared translation runtime, loaded on every page (before
// landing-icons.jsx/landing-sections.jsx/landing-footer.jsx, all of which
// use it) so the navbar's language switcher works the same everywhere,
// even on pages that don't have their own dictionary entries yet.
//
// Scope: every page's own app file calls t() with real keys, on top of the
// shared chrome (Navbar/Footer/PageShell). The Portuguese literal passed at
// each call site is the last-resort fallback and must stay byte-identical to
// the pt.json value (that is what prerender emits and what the first paint
// shows while the dictionaries are still fetching). scripts/i18n-check.mjs
// keeps the three dictionaries in lockstep and validates the keys the
// sources reference; it runs ahead of every build.
//
// No bundler, no build step: three flat JSON files (i18n/pt.json/en.json/
// es.json), fetched once, kept in a plain module-level object, and a
// useI18n() hook every component calls for `t()` + the current `lang`.
// Prefixed per the site's one-global-scope rule (see CLAUDE.md): landing-
// sections.jsx already binds the bare `useState`/`useEffect`, and this file is
// concatenated into the same scope at build time, so bare names here collide.
const { useState: I18N_useState, useEffect: I18N_useEffect } = React;

(function () {
  const LOCALES = ["pt", "en", "es", "fr"];
  const DICTS = {};
  const listeners = new Set();
  let loaded = false;

  function notify() { listeners.forEach((fn) => { try { fn(); } catch (e) {} }); }

  // Fetch the dictionaries in the browser only. The build prerenders this app
  // in a Node vm (scripts/prerender.mjs) that provides no `fetch`, and its
  // whole contract is that module evaluation is side-effect-free there; so when
  // there is no `fetch` we simply skip loading and every t() falls back to its
  // Portuguese literal, which is exactly the markup prerender should emit.
  if (typeof fetch === "function") {
    Promise.all(
      LOCALES.map((l) =>
        fetch(`i18n/${l}.json`)
          .then((r) => r.json())
          .then((d) => { DICTS[l] = d; })
          .catch(() => { DICTS[l] = {}; }) // missing/broken file: t() just falls back to its own literal
      )
    ).then(() => { loaded = true; notify(); });
  }

  function getStoredLang() {
    try {
      const v = localStorage.getItem("jt-lang");
      if (LOCALES.indexOf(v) !== -1) return v;
    } catch (e) {}
    return "pt";
  }

  function setStoredLang(l) {
    if (LOCALES.indexOf(l) === -1) return;
    try { localStorage.setItem("jt-lang", l); } catch (e) {}
    notify();
  }

  // Lookup order: the requested language's dictionary, then the Portuguese
  // dictionary (in case a page/section has been ported to en/es keys that
  // pt.json also carries but this particular locale file doesn't yet),
  // then the literal Portuguese string given at the call site — so a page
  // that never loads any dictionary at all (or one still mid-fetch) renders
  // exactly as it always has.
  function translate(key, fallback, lang) {
    const inLang = DICTS[lang];
    if (inLang && Object.prototype.hasOwnProperty.call(inLang, key)) return inLang[key];
    const inPt = DICTS.pt;
    if (inPt && Object.prototype.hasOwnProperty.call(inPt, key)) return inPt[key];
    return fallback;
  }

  window.JT_I18N = {
    getStoredLang,
    setStoredLang,
    translate,
    isLoaded: () => loaded,
    subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); },
  };
})();

// useI18n() — call once per component that needs t() and/or the current
// language. Re-renders that component whenever the language changes
// (navbar switch) or the dictionaries finish loading (first paint on a
// non-Portuguese returning visitor briefly shows the Portuguese fallback
// until the fetch above resolves, then flips — see the report for this
// known pilot-stage limitation).
function useI18n() {
  const [lang, setLangState] = I18N_useState(window.JT_I18N.getStoredLang());
  const [, bump] = I18N_useState(0);
  I18N_useEffect(() => {
    const unsubscribe = window.JT_I18N.subscribe(() => {
      setLangState(window.JT_I18N.getStoredLang());
      bump((n) => n + 1);
    });
    // The dictionaries can finish loading between the first render and this
    // subscription (on the second page they come straight from HTTP cache),
    // and that notify then fires with no listeners. Without this re-render the
    // component would keep its Portuguese first paint until something else
    // happened to re-render it, which is exactly what the navbar's scroll
    // effects did while the page body stayed frozen.
    if (window.JT_I18N.isLoaded()) bump((n) => n + 1);
    return unsubscribe;
  }, []);
  function t(key, fallback) { return window.JT_I18N.translate(key, fallback, lang); }
  function setLang(l) { window.JT_I18N.setStoredLang(l); }
  return { lang, t, setLang };
}

/* Some dictionary strings carry one or two inline emphasis spans mid-sentence,
   which a plain t() string can't express as JSX. They store lightweight
   **bold** markers instead (translators keep the markers, just move them to
   wherever the emphasis lands in their language), and i18nRich() turns that
   back into the <strong> a reader sees. Pure function, no module-eval side
   effects, so the prerender vm contract holds. */
function i18nRich(str) {
  const parts = String(str).split(/\*\*(.+?)\*\*/g);
  return parts.map((part, i) => (i % 2 === 1 ? <strong key={i}>{part}</strong> : part));
}
