(() => {
  "use strict";

  const language = (document.documentElement.lang || "es").split("-")[0].toLowerCase();
  if (language === "es") return;

  const ignoredTags = new Set(["SCRIPT", "STYLE", "CODE", "PRE", "TEXTAREA"]);
  const attributes = ["aria-label", "aria-description", "placeholder", "title"];
  let catalog = {};
  let fragments = [];
  let fragmentsByInitial = new Map();

  function translate(value) {
    if (!value) return value;
    if (Object.prototype.hasOwnProperty.call(catalog, value)) return catalog[value];
    const trimmed = value.trim();
    if (Object.prototype.hasOwnProperty.call(catalog, trimmed)) {
      return value.replace(trimmed, catalog[trimmed]);
    }
    let result = value;
    const candidates = new Set();
    for (const character of new Set(result)) {
      for (const source of fragmentsByInitial.get(character) || []) candidates.add(source);
    }
    for (const source of Array.from(candidates).sort((left, right) => right.length - left.length)) {
      if (source.length <= result.length && result.includes(source)) {
        result = result.split(source).join(catalog[source]);
      }
    }
    return result;
  }

  function isIgnored(node) {
    const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
    return !element || ignoredTags.has(element.tagName)
      || Boolean(element.closest("[data-i18n-skip]"))
      || Boolean(element.closest(".message-list h3, .message-list article > p, .message-list details, .message-list table"));
  }

  function translateNode(root) {
    if (!root || isIgnored(root)) return;
    if (root.nodeType === Node.TEXT_NODE) {
      const translated = translate(root.nodeValue);
      if (translated !== root.nodeValue) root.nodeValue = translated;
      return;
    }
    if (root.nodeType !== Node.ELEMENT_NODE && root.nodeType !== Node.DOCUMENT_NODE) return;
    if (root.nodeType === Node.ELEMENT_NODE) {
      for (const attribute of attributes) {
        if (root.hasAttribute(attribute)) {
          const value = root.getAttribute(attribute);
          const translated = translate(value);
          if (translated !== value) root.setAttribute(attribute, translated);
        }
      }
    }
    for (const child of root.childNodes) translateNode(child);
  }

  fetch(`/idioma/catalogo/${encodeURIComponent(language)}.json`, {
    headers: { Accept: "application/json" },
    credentials: "same-origin",
  })
    .then((response) => {
      if (!response.ok) throw new Error("language catalog unavailable");
      return response.json();
    })
    .then((payload) => {
      catalog = payload;
      fragments = Object.keys(catalog)
        .filter((source) => source.length >= 4
          && /[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]\s+[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]/.test(source)
          && !/[{}[\]=<>]/.test(source))
        .sort((left, right) => right.length - left.length);
      fragmentsByInitial = new Map();
      for (const source of fragments) {
        const initial = source[0];
        if (!fragmentsByInitial.has(initial)) fragmentsByInitial.set(initial, []);
        fragmentsByInitial.get(initial).push(source);
      }
      translateNode(document.documentElement);
      new MutationObserver((mutations) => {
        for (const mutation of mutations) {
          if (mutation.type === "characterData") translateNode(mutation.target);
          for (const node of mutation.addedNodes || []) translateNode(node);
        }
      }).observe(document.body, { childList: true, subtree: true, characterData: true });

      const originalAlert = window.alert.bind(window);
      const originalConfirm = window.confirm.bind(window);
      const originalPrompt = window.prompt.bind(window);
      window.alert = (message) => originalAlert(translate(String(message)));
      window.confirm = (message) => originalConfirm(translate(String(message)));
      window.prompt = (message, defaultValue) => originalPrompt(
        translate(String(message)), defaultValue,
      );
    })
    .catch(() => {});
})();
