// i18n.jsx — hand-rolled bilingual i18n layer (no build system).
// Loaded after tweaks-panel, before the strings-*.jsx catalogs and all components.
// German default; English retained as fallback. Switcher built but not mounted.

const OK_LANGS = ["de", "en"];
const OK_DEFAULT_LANG = "de";
const OK_I18N_REGISTRY = {}; // "Component.key" -> { en, de } | { en:{one,other}, de:{one,other} }

function registerStrings(namespace, entries) {
  for (const localKey in entries) {
    if (!Object.prototype.hasOwnProperty.call(entries, localKey)) continue;
    const key = namespace + "." + localKey;
    if (Object.prototype.hasOwnProperty.call(OK_I18N_REGISTRY, key)) {
      throw new Error("[i18n] duplicate key: " + key);
    }
    OK_I18N_REGISTRY[key] = entries[localKey];
  }
}

function readStoredLang() {
  try {
    const v = window.localStorage.getItem("ok_lang");
    return OK_LANGS.indexOf(v) >= 0 ? v : OK_DEFAULT_LANG;
  } catch (e) { return OK_DEFAULT_LANG; }
}

let OK_LANG = readStoredLang();
let __i18nRerender = null; // App registers a force-update here

function getLang() { return OK_LANG; }
function onLangChange(cb) { __i18nRerender = cb; }

function setLang(l) {
  if (OK_LANGS.indexOf(l) < 0) return;
  OK_LANG = l;
  try { window.localStorage.setItem("ok_lang", l); } catch (e) {}
  if (typeof document !== "undefined") document.documentElement.lang = l;
  if (__i18nRerender) __i18nRerender();
}

const OK_I18N_AUDIT =
  typeof window !== "undefined" && !!window.location &&
  /[?&]i18n=audit\b/.test(window.location.search || "");

function interpolate(str, vars) {
  if (!vars || typeof str !== "string") return str;
  return str.replace(/\{(\w+)\}/g, (m, k) =>
    Object.prototype.hasOwnProperty.call(vars, k) ? String(vars[k]) : m);
}

function t(key, vars) {
  const entry = OK_I18N_REGISTRY[key];
  if (!entry) return OK_I18N_AUDIT ? "⟦missing:" + key + "⟧" : key;
  let val = entry[OK_LANG];
  const translated = typeof val === "string" && val.length > 0;
  if (!translated) val = entry.en;
  let out = interpolate(val, vars);
  if (OK_I18N_AUDIT && OK_LANG !== "en" && !translated) out = "⟦" + out + "⟧";
  return out;
}

function tn(key, count, vars) {
  const entry = OK_I18N_REGISTRY[key];
  const allVars = Object.assign({ count: count }, vars || {});
  if (!entry) return OK_I18N_AUDIT ? "⟦missing:" + key + "⟧" : key;
  const form = count === 1 ? "one" : "other";
  const langForms = entry[OK_LANG] || {};
  const enForms = entry.en || {};
  let val = langForms[form];
  const translated = typeof val === "string" && val.length > 0;
  if (!translated) val = enForms[form];
  let out = interpolate(val, allVars);
  if (OK_I18N_AUDIT && OK_LANG !== "en" && !translated) out = "⟦" + out + "⟧";
  return out;
}

// Localize an API value. Tolerates BOTH the new bilingual objects the inspection API
// now returns for every free-text field ({ en, de }) AND legacy plain strings, so
// partial/old payloads never crash the UI:
//   • { en, de } object → the active language, falling back en → de → "".
//   • plain string (or anything else) → passed through unchanged (null/undefined → "").
// Enum *labels* arrive in the same { en, de } shape, so L() handles those too — but keep
// using the RAW enum value (e.g. difficulty === "Hard") for any logic/branching.
function L(v) {
  if (v && typeof v === "object" && ("en" in v || "de" in v)) {
    return v[getLang()] ?? v.en ?? v.de ?? "";
  }
  return v ?? "";
}

// True only for a LocalizedText object: exactly { en, de } (either key may be absent/null),
// nothing else. Guards against collapsing unrelated objects that merely contain an "en" key.
function isLocalizedText(v) {
  if (!v || typeof v !== "object" || Array.isArray(v)) return false;
  const keys = Object.keys(v);
  return keys.length > 0 && keys.every((k) => k === "en" || k === "de") && ("en" in v || "de" in v);
}

// Recursively collapse every { en, de } object in an API payload to its active-language string,
// leaving raw enum values, numbers, and structural objects/arrays untouched. Apply once at the
// boundary where inspection data enters a view — then components consume plain strings and can
// never accidentally render (or call string methods on) a { en, de } object.
function localizeDeep(value) {
  if (isLocalizedText(value)) return L(value);
  if (Array.isArray(value)) return value.map(localizeDeep);
  if (value && typeof value === "object") {
    const out = {};
    for (const k in value) out[k] = localizeDeep(value[k]);
    return out;
  }
  return value;
}

// Dormant DE/EN switcher — intentionally not mounted anywhere this round.
function LanguageToggle() {
  const cur = getLang();
  return (
    <div style={{ display: "inline-flex", gap: 4 }}>
      {OK_LANGS.map((l) => (
        <button key={l} type="button" onClick={() => setLang(l)} aria-pressed={cur === l}
          style={{ font: "inherit", cursor: "pointer", border: "none", background: "none",
            padding: "2px 6px", opacity: cur === l ? 1 : 0.5 }}>
          {l.toUpperCase()}
        </button>
      ))}
    </div>
  );
}

Object.assign(window, {
  OK_I18N_REGISTRY, registerStrings, t, tn, setLang, getLang, onLangChange,
  OK_LANGS, LanguageToggle, L, localizeDeep,
});
