(() => {
  "use strict";

  const body = document.body;
  const events = document.getElementById("sr-events");
  const shortcutDialog = document.getElementById("shortcut-dialog");
  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || "";
  const playEffect = (name) => window.EclipseAudio?.playEffect(name);
  let inputBindings = {};
  try {
    inputBindings = JSON.parse(document.getElementById("input-bindings")?.textContent || "{}");
  } catch (_error) {
    inputBindings = {};
  }
  let shortcutOpener = null;

  function announce(message) {
    if (events) events.textContent = message;
  }

  document.addEventListener("submit", (event) => {
    const form = event.target.closest("form[data-cost-metal], form[data-cost-crystal], form[data-cost-deuterium]");
    if (!form || form.dataset.spendConfirmed === "1") return;
    const threshold = Number(body.dataset.confirmSpendPercent || 0);
    if (threshold <= 0) return;
    const quantityField = form.dataset.costQuantity
      ? form.elements.namedItem(form.dataset.costQuantity)
      : null;
    const quantity = Math.max(1, Number(quantityField?.value || 1));
    const percentages = ["metal", "crystal", "deuterium"].map((resource) => {
      const cost = Number(form.dataset[`cost${resource[0].toUpperCase()}${resource.slice(1)}`] || 0) * quantity;
      const reserveElement = document.getElementById(`resource-${resource}`);
      const reserve = Math.max(0, Number(reserveElement?.dataset.value || 0));
      return { resource, cost, reserve, percent: reserve ? cost / reserve * 100 : cost ? 100 : 0 };
    });
    const largest = percentages.reduce((current, item) => item.percent > current.percent ? item : current);
    if (largest.percent < threshold) return;
    event.preventDefault();
    const resourceNames = { metal: "metal", crystal: "cristal", deuterium: "deuterio" };
    const accepted = window.confirm(
      `Esta acción gastará aproximadamente el ${Math.round(largest.percent)} % de tu reserva de ${resourceNames[largest.resource]}. ¿Quieres continuar?`,
    );
    if (accepted) {
      form.dataset.spendConfirmed = "1";
      form.requestSubmit(event.submitter);
    } else {
      announce("Acción cancelada. No se gastaron recursos.");
    }
  });

  function focusDialogStart(dialog) {
    const start = dialog?.querySelector("[data-dialog-start]");
    if (!start) return;
    const resetScroll = () => {
      dialog.scrollTop = 0;
      dialog.scrollLeft = 0;
      dialog.querySelectorAll(".table-wrap, [data-audio-surface], [data-dialog-scroll]").forEach((region) => {
        region.scrollTop = 0;
        region.scrollLeft = 0;
      });
    };
    resetScroll();
    window.requestAnimationFrame(() => {
      start.focus({ preventScroll: true });
      resetScroll();
    });
  }
  window.EclipseFocusDialogStart = focusDialogStart;

  const settings = {
    contrast: body.dataset.highContrast === "1",
    textSize: body.dataset.largeText === "1",
    updatesPaused: body.dataset.updatesPaused === "1",
    compactPanels: body.dataset.compactPanels !== "0",
    selectCurrentFirst: body.dataset.selectCurrentFirst !== "0",
    shortcutShift: body.dataset.shortcutShift === "1",
    navigationMode: ["classic", "tabs"].includes(body.dataset.navigationMode)
      ? body.dataset.navigationMode
      : "classic",
  };
  if (body.classList.contains("setup-page") || body.dataset.publicDocumentation === "1") {
    try {
      const storedSetupMode = localStorage.getItem("eclipse-navigation-mode");
      if (["classic", "tabs"].includes(storedSetupMode)) settings.navigationMode = storedSetupMode;
    } catch (_error) {
      // El selector sigue funcionando durante esta visita si el navegador bloquea el almacenamiento.
    }
  }

  function keyLabel(code) {
    if (/^Key[A-Z]$/.test(code)) return code.slice(3);
    if (/^Digit[0-9]$/.test(code)) return code.slice(5);
    return code;
  }

  function bindingLabel(binding) {
    if (!binding) return "";
    return [
      binding.ctrl ? "Control" : "", binding.alt ? "Alt" : "",
      binding.shift ? "Shift" : "", keyLabel(binding.code || ""),
    ].filter(Boolean).join("+");
  }

  function updateBindingLabels() {
    Object.values(inputBindings).forEach((binding) => {
      document.querySelectorAll(`[data-binding-action="${binding.action}"]`).forEach((element) => {
        const label = bindingLabel(binding);
        if (element.matches("[aria-keyshortcuts]")) element.setAttribute("aria-keyshortcuts", label);
        if (element.matches("[data-binding-text]")) element.textContent = label;
      });
    });
  }

  function applyPreferences() {
    body.classList.toggle("high-contrast", settings.contrast);
    body.classList.toggle("large-text", settings.textSize);
    document.querySelectorAll('[data-action="contrast"]').forEach((button) => button.setAttribute("aria-pressed", String(settings.contrast)));
    document.querySelectorAll('[data-action="text-size"]').forEach((button) => button.setAttribute("aria-pressed", String(settings.textSize)));
    document.querySelectorAll('[data-action="updates"]').forEach((button) => {
      button.setAttribute("aria-pressed", String(settings.updatesPaused));
      if (button.classList.contains("icon-button")) button.textContent = settings.updatesPaused ? "Reanudar avisos" : "Pausar avisos";
    });
    document.querySelectorAll('[data-action="compact-panels"]').forEach((button) => {
      button.setAttribute("aria-pressed", String(settings.compactPanels));
      button.textContent = settings.compactPanels ? "Mostrar todos los paneles abiertos" : "Usar portada compacta";
    });
    document.querySelectorAll('[data-action="select-current-first"]').forEach((button) => {
      button.setAttribute("aria-pressed", String(settings.selectCurrentFirst));
      button.textContent = settings.selectCurrentFirst ? "Mantener orden fijo en desplegables" : "Abrir desplegables desde la opción activa";
    });
    document.querySelectorAll('[data-action="shortcut-shift"]').forEach((button) => {
      button.setAttribute("aria-pressed", String(settings.shortcutShift));
      button.textContent = settings.shortcutShift ? "Quitar Shift adicional de todos los atajos" : "Añadir Shift a todos los atajos";
    });
    document.querySelectorAll("[data-shortcut-mode-description]").forEach((description) => {
      description.textContent = "Además del modificador global puedes asignar una combinación distinta a cada acción en la tabla siguiente.";
    });
    document.querySelectorAll("[data-shortcut-description]").forEach((description) => {
      description.textContent = "Cada acción puede tener su propia combinación. Funcionan también desde un desplegable y se desactivan mientras escribes texto. Pulsa Escape para cerrar este cuadro y volver al control anterior.";
    });
    body.dataset.shortcutShift = settings.shortcutShift ? "1" : "0";
    body.dataset.navigationMode = settings.navigationMode;
    body.classList.toggle("tabbed-navigation", settings.navigationMode === "tabs");
    document.querySelectorAll("[data-navigation-mode-select]").forEach((select) => {
      select.value = settings.navigationMode;
    });
    updateBindingLabels();
  }

  function persistInterfacePreference(key, enabled, rollback) {
    const payload = key === "navigation_mode"
      ? { key, value: enabled }
      : { key, enabled };
    fetch("/api/preferencias/interfaz", {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
      body: JSON.stringify(payload),
    }).then((response) => {
      if (!response.ok) throw new Error("No se pudo guardar");
      announce("Preferencia de accesibilidad guardada en la partida portátil.");
    }).catch(() => {
      rollback();
      applyPreferences();
      announce("No se pudo guardar la preferencia de accesibilidad.");
    });
  }

  document.addEventListener("click", (event) => {
    const button = event.target.closest("[data-action]");
    if (!button) return;
    if (button.dataset.action === "shortcut-help") {
      if (shortcutDialog && !shortcutDialog.open) {
        shortcutOpener = button;
        shortcutDialog.showModal();
        focusDialogStart(shortcutDialog);
      }
      return;
    } else if (button.dataset.action === "contrast") {
      const previous = settings.contrast;
      settings.contrast = !settings.contrast;
      persistInterfacePreference("high_contrast", settings.contrast, () => { settings.contrast = previous; });
    } else if (button.dataset.action === "text-size") {
      const previous = settings.textSize;
      settings.textSize = !settings.textSize;
      persistInterfacePreference("large_text", settings.textSize, () => { settings.textSize = previous; });
    } else if (button.dataset.action === "updates") {
      const previous = settings.updatesPaused;
      settings.updatesPaused = !settings.updatesPaused;
      persistInterfacePreference("updates_paused", settings.updatesPaused, () => { settings.updatesPaused = previous; });
    } else if (button.dataset.action === "compact-panels") {
      const previous = settings.compactPanels;
      settings.compactPanels = !settings.compactPanels;
      document.querySelectorAll("[data-collapsible-panel]").forEach((panel) => sessionStorage.removeItem(`eclipse-panel:${panel.dataset.panelKey}`));
      persistInterfacePreference("compact_panels", settings.compactPanels, () => { settings.compactPanels = previous; });
      applyPanelMode();
    } else if (button.dataset.action === "select-current-first") {
      const previous = settings.selectCurrentFirst;
      settings.selectCurrentFirst = !settings.selectCurrentFirst;
      persistInterfacePreference("select_current_first", settings.selectCurrentFirst, () => { settings.selectCurrentFirst = previous; });
    } else if (button.dataset.action === "shortcut-shift") {
      const previous = settings.shortcutShift;
      settings.shortcutShift = !settings.shortcutShift;
      Object.values(inputBindings).forEach((binding) => { binding.shift = settings.shortcutShift; });
      persistInterfacePreference("shortcut_shift", settings.shortcutShift, () => {
        settings.shortcutShift = previous;
        Object.values(inputBindings).forEach((binding) => { binding.shift = previous; });
      });
    }
    applyPreferences();
  });

  document.addEventListener("change", (event) => {
    const select = event.target.closest("[data-navigation-mode-select]");
    if (!select) return;
    const requested = select.value;
    if (!["classic", "tabs"].includes(requested)) return;
    const previous = settings.navigationMode;
    settings.navigationMode = requested;
    try {
      localStorage.setItem("eclipse-navigation-mode", requested);
    } catch (_error) {
      // La preferencia de la partida sigue siendo suficiente dentro del juego.
    }
    applyPreferences();
    applyNavigationMode(true);
    if (!body.classList.contains("setup-page") && body.dataset.publicDocumentation !== "1") {
      persistInterfacePreference("navigation_mode", requested, () => {
        settings.navigationMode = previous;
        applyPreferences();
        applyNavigationMode(false);
      });
    }
  });

  applyPreferences();

  function placeSelectedOptionFirst(select) {
    if (!settings.selectCurrentFirst || select.multiple || select.options.length < 2) return;
    const selected = select.selectedOptions[0];
    if (selected && selected !== select.options[0]) select.insertBefore(selected, select.options[0]);
  }

  document.querySelectorAll("select").forEach(placeSelectedOptionFirst);
  document.addEventListener("focusin", (event) => {
    if (event.target.matches("select")) placeSelectedOptionFirst(event.target);
  });

  function setPanelExpanded(panel, expanded, persist = false) {
    const button = panel.querySelector(":scope > .collapsible-heading button");
    const content = panel.querySelector(":scope > [data-panel-content]");
    if (!button || !content) return;
    button.setAttribute("aria-expanded", String(expanded));
    content.hidden = !expanded;
    const indicator = button.querySelector("[data-panel-indicator]");
    if (indicator) indicator.textContent = expanded ? "−" : "+";
    if (persist) sessionStorage.setItem(`eclipse-panel:${panel.dataset.panelKey}`, expanded ? "1" : "0");
  }

  function applyPanelMode() {
    document.querySelectorAll("[data-collapsible-panel]").forEach((panel) => {
      const stored = sessionStorage.getItem(`eclipse-panel:${panel.dataset.panelKey}`);
      const expanded = stored === null ? (!settings.compactPanels || panel.dataset.defaultOpen === "true") : stored === "1";
      setPanelExpanded(panel, expanded);
    });
  }

  document.addEventListener("click", (event) => {
    const button = event.target.closest("[data-collapsible-panel] > .collapsible-heading button");
    if (!button) return;
    const panel = button.closest("[data-collapsible-panel]");
    const expanded = button.getAttribute("aria-expanded") === "true";
    setPanelExpanded(panel, !expanded, true);
  });
  applyPanelMode();
  document.querySelectorAll("[data-manual-panels]").forEach((button) => {
    button.addEventListener("click", () => {
      const expanded = button.dataset.manualPanels === "open";
      document.querySelectorAll(".manual-chapter[data-collapsible-panel]").forEach((panel) => setPanelExpanded(panel, expanded, true));
      announce(expanded ? "Todos los capítulos del manual están abiertos." : "Todos los capítulos del manual están cerrados.");
    });
  });

  shortcutDialog?.addEventListener("cancel", () => announce("Tabla de teclas cerrada con Escape."));
  shortcutDialog?.addEventListener("close", () => {
    shortcutOpener?.focus();
    shortcutOpener = null;
  });
  document.addEventListener("keydown", (event) => {
    if (event.key !== "Escape") return;
    const openDialog = document.querySelector("dialog[open]");
    if (!openDialog) return;
    event.preventDefault();
    openDialog.close();
    announce("Cuadro emergente cerrado. El foco vuelve al control que lo abrió.");
  }, true);

  const main = document.getElementById("main");
  document.querySelectorAll(".table-wrap").forEach((region) => {
    const caption = region.querySelector("caption")?.textContent.trim();
    region.tabIndex = 0;
    region.setAttribute("role", "region");
    region.setAttribute("aria-label", caption ? `Tabla desplazable: ${caption}` : "Tabla desplazable");
  });
  const pageIndex = document.getElementById("page-section-index");
  const pageIndexList = pageIndex?.querySelector("[data-page-section-links]");
  if (main && pageIndex && pageIndexList) {
    const headings = [...main.querySelectorAll("h2")].filter((heading) =>
      !pageIndex.contains(heading) && !heading.classList.contains("sr-only") &&
      !heading.closest("article") && !heading.closest("[hidden]")
    );
    headings.forEach((heading, index) => {
      if (!heading.id) heading.id = `page-section-${index + 1}`;
      heading.tabIndex = -1;
      const item = document.createElement("li");
      const link = document.createElement("a");
      link.href = `#${heading.id}`;
      link.textContent = heading.querySelector("button[aria-label]")?.getAttribute("aria-label") || heading.textContent.trim();
      item.appendChild(link);
      pageIndexList.appendChild(item);
    });
    pageIndexList.addEventListener("click", (event) => {
      const link = event.target.closest("a[href^='#']");
      if (!link) return;
      const heading = document.getElementById(link.getAttribute("href").slice(1));
      if (!heading) return;
      event.preventDefault();
      heading.focus({ preventScroll: true });
      heading.scrollIntoView({ block: "start" });
      history.replaceState(null, "", link.getAttribute("href"));
      if (events) events.textContent = `Sección: ${heading.textContent.trim()}.`;
    });
    if (headings.length) {
      pageIndex.hidden = false;
      const pageHeading = main.querySelector(".page-heading");
      if (pageHeading) pageHeading.insertAdjacentElement("afterend", pageIndex);
    }
  }

  const formatDuration = (seconds) => {
    const safe = Math.max(0, Math.floor(seconds));
    if (safe < 60) return `${safe} s`;
    const minutes = Math.floor(safe / 60);
    const rest = safe % 60;
    if (minutes < 60) return `${minutes} min ${rest} s`;
    return `${Math.floor(minutes / 60)} h ${minutes % 60} min`;
  };

  let serverClockOffset = 0;
  let sectionRefreshInProgress = false;
  let pendingSectionRefresh = false;

  function updateCountdowns() {
    const now = Date.now() / 1000 + serverClockOffset;
    document.querySelectorAll(".countdown[data-end]").forEach((element) => {
      element.textContent = formatDuration(Number(element.dataset.end) - now);
    });
  }

  function updateUnitDurations(root = document) {
    root.querySelectorAll("[data-duration-quantity]").forEach((input) => {
      const card = input.closest(".tech-card");
      const output = card?.querySelector(".unit-total-duration[data-unit-duration]");
      if (!output) return;
      const amount = Math.max(1, Number(input.value || 1));
      output.textContent = formatDuration(Number(output.dataset.unitDuration) * amount);
    });
  }

  document.addEventListener("input", (event) => {
    if (event.target.matches("[data-duration-quantity]")) {
      updateUnitDurations(event.target.closest(".tech-card"));
    }
  });

  function controlKey(control) {
    if (control.id) return `id:${control.id}`;
    if (!control.name) return "";
    const form = control.closest("form");
    const item = form?.querySelector('input[name="item"]')?.value || "";
    return `name:${control.name}|${form?.getAttribute("action") || ""}|${item}`;
  }

  async function refreshVisibleSection() {
    if (sectionRefreshInProgress) return false;
    if (!["overview", "economy", "research", "shipyard", "frontier"].includes(body.dataset.section)) {
      return false;
    }
    const sectionContent = document.getElementById("section-content");
    if (!sectionContent) return false;
    const focused = document.activeElement;
    if (
      sectionContent.contains(focused)
      && (focused.closest(".active-queue") || !controlKey(focused))
    ) {
      return false;
    }
    sectionRefreshInProgress = true;
    try {
      const response = await fetch(window.location.href, {
        headers: { Accept: "text/html", "X-Requested-With": "fetch" },
        cache: "no-store",
      });
      if (!response.ok) return false;
      const parsed = new DOMParser().parseFromString(await response.text(), "text/html");
      const replacement = parsed.getElementById("section-content");
      if (!replacement) return false;

      const values = new Map();
      sectionContent.querySelectorAll("input, select, textarea").forEach((control) => {
        const key = controlKey(control);
        if (key) values.set(key, {
          value: control.value, checked: control.checked,
        });
      });
      const focusedKey = sectionContent.contains(focused) ? controlKey(focused) : "";
      const selectionStart = focusedKey && "selectionStart" in focused
        ? focused.selectionStart : null;
      const selectionEnd = focusedKey && "selectionEnd" in focused
        ? focused.selectionEnd : null;
      const scrollX = window.scrollX;
      const scrollY = window.scrollY;

      sectionContent.innerHTML = replacement.innerHTML;
      sectionContent.querySelectorAll("input, select, textarea").forEach((control) => {
        const saved = values.get(controlKey(control));
        if (!saved) return;
        if (control.type === "checkbox" || control.type === "radio") {
          control.checked = saved.checked;
        } else {
          control.value = saved.value;
        }
      });
      if (focusedKey) {
        const nextFocused = [...sectionContent.querySelectorAll("input, select, textarea, button, a")]
          .find((control) => controlKey(control) === focusedKey);
        if (nextFocused) {
          nextFocused.focus({ preventScroll: true });
          if (selectionStart !== null && nextFocused.setSelectionRange) {
            nextFocused.setSelectionRange(selectionStart, selectionEnd);
          }
        }
      }
      window.scrollTo(scrollX, scrollY);
      applyPanelMode();
      updateUnitDurations(sectionContent);
      updateCountdowns();
      return true;
    } catch (_error) {
      return false;
    } finally {
      sectionRefreshInProgress = false;
    }
  }

  updateCountdowns();
  updateUnitDurations();
  window.setInterval(updateCountdowns, 1000);

  const resourceIds = ["metal", "crystal", "deuterium"];
  let knownQueues = new Map(
    [...document.querySelectorAll("[data-queue-id]")].map((element) => [
      Number(element.dataset.queueId),
      element.dataset.queueCategory || ({
        economy: "building", research: "research", shipyard: "shipyard",
      }[body.dataset.section] || ""),
    ]),
  );
  let knownFleetIds = null;
  let knownExpeditionIds = null;
  let knownProjectIds = new Set(
    [...document.querySelectorAll("[data-project-id]")]
      .map((element) => Number(element.dataset.projectId)),
  );
  let knownUnread = null;
  const messageCategoryCues = {
    "logro": "achievement", "alianza": "alliance", "combate": "combat",
    "evento": "notification", "mercado": "trade", "espionaje": "espionage",
    "expedición": "expedition", "expedición profunda": "expedition",
    "cartografía": "survey", "rescate": "rescue", "sabotaje": "espionage",
    "producción": "construction", "diplomacia": "diplomacy",
  };
  async function refreshState() {
    if (!document.querySelector(".app-shell") || body.dataset.gameStatus === "paused") return;
    try {
      const response = await fetch("/api/estado", { headers: { Accept: "application/json" } });
      if (!response.ok) return;
      const state = await response.json();
      if (Number.isFinite(Number(state.server_time))) {
        serverClockOffset = Number(state.server_time) - Date.now() / 1000;
      }
      resourceIds.forEach((resource) => {
        const element = document.getElementById(`resource-${resource}`);
        if (element) {
          element.dataset.value = state.resources[resource];
          element.textContent = new Intl.NumberFormat("es-ES").format(state.resources[resource]);
        }
      });
      const food = document.getElementById("resource-food");
      if (food) {
        food.dataset.value = state.food;
        food.textContent = new Intl.NumberFormat("es-ES").format(state.food);
      }
      const credits = document.getElementById("resource-credits");
      if (credits && state.credits !== undefined) {
        credits.dataset.value = state.credits;
        credits.textContent = new Intl.NumberFormat("es-ES").format(state.credits);
      }
      const strategic = document.getElementById("resource-strategic");
      if (strategic && state.strategic) {
        const total = Object.values(state.strategic).reduce((sum, value) => sum + Number(value || 0), 0);
        strategic.dataset.value = total;
        strategic.textContent = new Intl.NumberFormat("es-ES", { maximumFractionDigits: 1 }).format(total);
      }
      const queues = new Map(state.queues.map((item) => [item.id, item.category]));
      const fleetIds = new Set(state.fleets.map((item) => item.id));
      const expeditionIds = new Set((state.expeditions || []).map((item) => item.id));
      const projectIds = new Set((state.projects || []).map((item) => item.id));
      const completedQueue = knownQueues ? [...knownQueues].find(([id]) => !queues.has(id)) : null;
      const queueChanged = knownQueues && (
        knownQueues.size !== queues.size
        || [...knownQueues].some(([id, category]) => queues.get(id) !== category)
      );
      const projectChanged = knownProjectIds && (
        knownProjectIds.size !== projectIds.size
        || [...knownProjectIds].some((id) => !projectIds.has(id))
      );
      let contextualCue = "";
      if (!settings.updatesPaused && events && completedQueue) {
        events.textContent = "Una cola ha finalizado. Abre Resumen o Informes para consultar el resultado.";
        contextualCue = { building: "construction", research: "research", shipyard: "shipyard" }[completedQueue[1]] || "notification";
      }
      if (!settings.updatesPaused && events && knownFleetIds && [...knownFleetIds].some((id) => !fleetIds.has(id))) {
        events.textContent = "Una flota ha completado su movimiento. Abre Informes para consultar el resultado.";
        contextualCue ||= "fleet";
      }
      if (!settings.updatesPaused && events && knownExpeditionIds && [...knownExpeditionIds].some((id) => !expeditionIds.has(id))) {
        events.textContent = "Una expedición profunda ha terminado. Abre Expediciones para leer su diario y consultar los hallazgos.";
        contextualCue ||= "expedition";
      }
      if (!settings.updatesPaused && events && knownProjectIds && [...knownProjectIds].some((id) => !projectIds.has(id))) {
        events.textContent = "Un megaproyecto imperial ha terminado. Abre Frontera para consultar sus efectos permanentes.";
        contextualCue ||= "project";
      }
      if (!settings.updatesPaused && knownUnread !== null && state.unread > knownUnread) {
        contextualCue ||= messageCategoryCues[state.latest_message_category] || "message";
      }
      if (contextualCue) playEffect(contextualCue);
      if (queueChanged && ["overview", "economy", "research", "shipyard"].includes(body.dataset.section)) {
        pendingSectionRefresh = true;
      }
      if (projectChanged && ["overview", "frontier"].includes(body.dataset.section)) {
        pendingSectionRefresh = true;
      }
      if (pendingSectionRefresh) {
        const refreshed = await refreshVisibleSection();
        if (refreshed && events) {
          pendingSectionRefresh = false;
          events.textContent = "La cola ha finalizado. La pantalla se ha actualizado automáticamente.";
        }
      }
      knownQueues = queues;
      knownFleetIds = fleetIds;
      knownExpeditionIds = expeditionIds;
      knownProjectIds = projectIds;
      knownUnread = state.unread;
    } catch (_error) {
      // La interfaz sigue funcionando sin actualizaciones automáticas.
    }
  }
  window.setInterval(refreshState, 5000);
  refreshState();

  function sectionDestination(path) {
    const destination = new URL(path, window.location.href);
    const selectedPlanet = document.getElementById("planet-select")?.value;
    if (selectedPlanet) destination.searchParams.set("planet", selectedPlanet);
    return destination;
  }

  function navigateSection(path) {
    const destination = sectionDestination(path);
    sessionStorage.setItem("eclipse-focus-page-title", "1");
    window.location.assign(destination.href);
  }

  function openManual(path) {
    const destination = sectionDestination(path);
    const manualWindow = window.open(destination.href, "_blank");
    if (manualWindow) {
      manualWindow.opener = null;
      announce("Manual abierto en una pestaña nueva. La pestaña del juego permanece disponible.");
    } else {
      announce("El navegador bloqueó la pestaña del manual. Activa las ventanas emergentes para esta aplicación.");
    }
  }

  document.addEventListener("click", (event) => {
    const manualLink = event.target.closest("a[data-open-manual]");
    if (!manualLink) return;
    event.preventDefault();
    openManual(manualLink.href);
  });

  const requestedFocus = new URLSearchParams(window.location.search).get("focus");
  if (requestedFocus === "main" || requestedFocus === "result") {
    sessionStorage.removeItem("eclipse-focus-page-title");
    window.requestAnimationFrame(() => {
      const hashTarget = window.location.hash ? document.getElementById(window.location.hash.slice(1)) : null;
      const target = requestedFocus === "result"
        ? (hashTarget || main?.querySelector(".notice[role='alert'], .notice[role='status']") || main?.querySelector("h1"))
        : main?.querySelector("h1");
      if (!target) return;
      target.tabIndex = -1;
      target.focus({ preventScroll: true });
      target.scrollIntoView({ block: "start" });
    });
  } else if (sessionStorage.getItem("eclipse-focus-page-title") === "1") {
    sessionStorage.removeItem("eclipse-focus-page-title");
    window.requestAnimationFrame(() => {
      const title = main?.querySelector("h1");
      if (!title) return;
      title.tabIndex = -1;
      title.focus({ preventScroll: true });
      title.scrollIntoView({ block: "start" });
    });
  }

  function focusRegion(id, announcement) {
    const target = document.getElementById(id);
    if (!target) return;
    target.focus({ preventScroll: false });
    target.scrollIntoView({ block: "start" });
    if (events && announcement) events.textContent = announcement;
  }

  function resourceAnnouncement() {
    const rows = document.querySelectorAll("#resources dl > div");
    return `Recursos de este planeta. ${[...rows].map((row) => `${row.querySelector("dt")?.textContent}: ${row.querySelector("dd")?.textContent}`).join(". ")}`;
  }

  function bindingForEvent(event) {
    const binding = Object.values(inputBindings).find((item) => item.code === event.code &&
      item.ctrl === event.ctrlKey && item.alt === event.altKey &&
      item.shift === event.shiftKey);
    if (!binding) return null;
    return !event.metaKey ? binding : null;
  }

  document.addEventListener("keydown", (event) => {
    const editable = event.target.closest("input, textarea, [contenteditable='true']");
    if (editable) return;
    const binding = bindingForEvent(event);
    if (!binding) return;
    event.preventDefault();
    const target = binding.target || "";
    if (target === "action:shortcuts") {
      if (shortcutDialog && !shortcutDialog.open) {
        shortcutOpener = document.activeElement;
        shortcutDialog.showModal();
        focusDialogStart(shortcutDialog);
      }
    } else if (target === "focus:resources") {
      focusRegion("resources", resourceAnnouncement());
    } else if (target === "focus:primary-navigation") {
      focusRegion("primary-navigation", "Navegación principal.");
    } else if (target === "focus:planet-select") {
      focusRegion("planet-controls", "Selector de planeta activo.");
    } else if (target === "action:headings") {
      const activeTabSet = settings.navigationMode === "tabs" ? tabSets[0] : null;
      if (activeTabSet?.buttons.length) {
        const activeButton = activeTabSet.buttons[activeTabSet.activeIndex];
        activeButton.focus({ preventScroll: false });
        activeButton.scrollIntoView({ block: "nearest", inline: "nearest" });
        announce(`Pestañas de esta pantalla. Activa: ${activeButton.textContent.trim()}.`);
      } else {
        focusRegion("page-section-index", "Índice de secciones de esta pantalla.");
      }
    } else if (target.startsWith("route:")) {
      const routeParts = target.split(":");
      const path = `/juego/${routeParts[1]}`;
      if (routeParts[2] === "new") openManual(path);
      else navigateSection(path);
    }
  });

  let installPrompt = null;
  const installButton = document.querySelector('[data-action="install-app"]');
  window.addEventListener("beforeinstallprompt", (event) => {
    event.preventDefault();
    installPrompt = event;
    if (installButton) installButton.hidden = false;
  });
  installButton?.addEventListener("click", async () => {
    if (!installPrompt) return;
    await installPrompt.prompt();
    const result = await installPrompt.userChoice;
    announce(result.outcome === "accepted" ? "Instalación aceptada." : "Instalación cancelada.");
    installPrompt = null;
    installButton.hidden = true;
  });
  if ("serviceWorker" in navigator) {
    window.addEventListener("load", () => navigator.serviceWorker.register("/static/sw.js").catch(() => {
      announce("La interfaz sigue disponible, pero no se pudo registrar la caché estática instalable.");
    }));
  }

  const wikiSearch = document.querySelector("[data-wiki-search-input]");
  const wikiCategoryFilter = document.querySelector("[data-wiki-category-filter]");
  const wikiClear = document.querySelector("[data-wiki-clear]");
  const wikiResult = document.getElementById("wiki-results");
  const wikiEmpty = document.querySelector("[data-wiki-empty]");
  const wikiEntries = [...document.querySelectorAll("[data-wiki-entry]")];
  const wikiCategories = [...document.querySelectorAll("[data-wiki-category]")];
  const wikiDialogs = [...document.querySelectorAll("[data-wiki-dialog]")];
  const wikiDialogOpeners = new WeakMap();
  let wikiAnnouncementTimer = null;

  function normalizeWikiText(value) {
    return String(value || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLocaleLowerCase("es");
  }

  function filterWiki({ announceResult = true } = {}) {
    if (!wikiSearch || !wikiCategoryFilter) return;
    const terms = normalizeWikiText(wikiSearch.value).split(/\s+/).filter(Boolean);
    const selectedCategory = wikiCategoryFilter.value;
    let visibleTotal = 0;
    wikiCategories.forEach((category) => {
      const categoryMatches = !selectedCategory || category.dataset.wikiCategory === selectedCategory;
      let categoryTotal = 0;
      category.querySelectorAll("[data-wiki-entry]").forEach((entry) => {
        const searchable = normalizeWikiText(entry.textContent);
        const matches = categoryMatches && terms.every((term) => searchable.includes(term));
        entry.hidden = !matches;
        if (matches) categoryTotal += 1;
      });
      category.dataset.wikiMatchCount = String(categoryTotal);
      if (settings.navigationMode !== "tabs") category.hidden = categoryTotal === 0;
      const count = category.querySelector("[data-wiki-category-count]");
      if (count) count.textContent = `${categoryTotal} ${categoryTotal === 1 ? "artículo" : "artículos"}`;
      visibleTotal += categoryTotal;
    });
    if (wikiEmpty) wikiEmpty.hidden = visibleTotal !== 0;
    if (wikiResult) {
      const message = visibleTotal === wikiEntries.length && !terms.length && !selectedCategory
        ? `Mostrando ${visibleTotal} ${visibleTotal === 1 ? "artículo" : "artículos"}.`
        : `${visibleTotal} ${visibleTotal === 1 ? "artículo encontrado" : "artículos encontrados"}.`;
      if (announceResult) {
        window.clearTimeout(wikiAnnouncementTimer);
        wikiAnnouncementTimer = window.setTimeout(() => { wikiResult.textContent = message; }, 180);
      } else {
        wikiResult.textContent = message;
      }
    }
    if (document.documentElement.dataset.tabsReady === "true") syncWikiTabFilter();
  }

  function revealWikiEntry(entry) {
    if (!entry) return;
    if (entry.hidden || entry.closest("[data-wiki-category]")?.hidden) {
      wikiSearch.value = "";
      wikiCategoryFilter.value = "";
      filterWiki({ announceResult: false });
    }
    activateTabContainingElement(entry);
  }

  function wikiEntryById(id) {
    return wikiEntries.find((entry) => (
      entry.id === id || entry.dataset.wikiCanonicalId === id
    ));
  }

  function openWikiDialog(entry, opener, updateHash = true) {
    const dialog = entry?.querySelector("[data-wiki-dialog]");
    if (!dialog || dialog.open) return;
    revealWikiEntry(entry);
    wikiDialogOpeners.set(dialog, opener);
    dialog.showModal();
    focusDialogStart(dialog);
    if (updateHash) {
      history.replaceState(null, "", `#${entry.dataset.wikiCanonicalId || entry.id}`);
    }
    announce(`Ficha abierta: ${entry.querySelector("h3")?.textContent || "artículo de la enciclopedia"}. Pulsa Escape para cerrarla.`);
  }

  wikiDialogs.forEach((dialog) => {
    dialog.addEventListener("close", () => {
      if (dialog.dataset.wikiSkipRestore === "1") {
        delete dialog.dataset.wikiSkipRestore;
        return;
      }
      const opener = wikiDialogOpeners.get(dialog);
      wikiDialogOpeners.delete(dialog);
      if (opener?.isConnected) {
        activateTabContainingElement(opener);
        opener.focus({ preventScroll: true });
      }
      announce("Ficha cerrada. El foco ha vuelto al control que la abrió.");
    });
  });

  if (wikiSearch && wikiCategoryFilter) {
    wikiSearch.addEventListener("input", () => filterWiki());
    wikiCategoryFilter.addEventListener("change", () => filterWiki());
    wikiClear?.addEventListener("click", () => {
      wikiSearch.value = "";
      wikiCategoryFilter.value = "";
      filterWiki({ announceResult: false });
      wikiSearch.focus();
    });
    filterWiki({ announceResult: false });
    document.querySelector("[data-wiki-catalog]")?.addEventListener("click", (event) => {
      const openButton = event.target.closest("[data-wiki-open]");
      if (openButton) {
        const entry = openButton.closest("[data-wiki-entry]");
        if (!entry) return;
        event.preventDefault();
        openWikiDialog(entry, openButton);
        return;
      }
      const link = event.target.closest("a[href^='#wiki-']");
      if (!link) return;
      const targetId = link.dataset.wikiTarget || link.getAttribute("href").slice(1);
      const target = wikiEntryById(targetId);
      if (!target) return;
      event.preventDefault();
      const currentDialog = link.closest("[data-wiki-dialog]");
      const originalOpener = currentDialog
        ? wikiDialogOpeners.get(currentDialog)
        : link;
      if (currentDialog?.open) {
        currentDialog.dataset.wikiSkipRestore = "1";
        currentDialog.close();
      }
      openWikiDialog(target, originalOpener);
    });
    document.querySelector(".wiki-toc")?.addEventListener("click", (event) => {
      const link = event.target.closest("a[href^='#wiki-']");
      if (!link) return;
      const category = document.getElementById(link.getAttribute("href").slice(1));
      const heading = category?.querySelector("h2");
      if (!heading) return;
      event.preventDefault();
      activateTabContainingElement(heading);
      heading.focus({ preventScroll: true });
      heading.scrollIntoView({ block: "start" });
      history.replaceState(null, "", link.getAttribute("href"));
      announce(`Categoría: ${heading.textContent}.`);
    });
    if (window.location.hash.startsWith("#wiki-")) {
      const hashId = window.location.hash.slice(1);
      const initialTarget = wikiEntryById(hashId) || document.getElementById(hashId);
      window.requestAnimationFrame(() => {
        if (initialTarget?.matches("[data-wiki-entry]")) {
          const opener = initialTarget.querySelector("[data-wiki-open]");
          openWikiDialog(initialTarget, opener, false);
        } else {
          const focusTarget = initialTarget?.querySelector("h2");
          activateTabContainingElement(focusTarget);
          focusTarget?.focus({ preventScroll: false });
        }
      });
    }
  }

  const levelNumber = new Intl.NumberFormat("es-ES", { maximumFractionDigits: 1 });
  function levelProjection(kind, level) {
    const previous = Math.max(0, level - 1);
    const growth = (base, coefficient, exponent = 1.1) => (
      base + coefficient * level * exponent ** level
    );
    const priorGrowth = (base, coefficient, exponent = 1.1) => (
      base + coefficient * previous * exponent ** previous
    );
    if (kind === "metal_production") {
      const value = growth(30, 30);
      return `Producción teórica sin penalización energética: ${levelNumber.format(value)} metal por hora; aumento frente al nivel anterior: ${levelNumber.format(value - priorGrowth(30, 30))}.`;
    }
    if (kind === "crystal_production") {
      const value = growth(15, 20);
      return `Producción teórica sin penalización energética: ${levelNumber.format(value)} cristal por hora; aumento frente al nivel anterior: ${levelNumber.format(value - priorGrowth(15, 20))}.`;
    }
    if (kind === "deuterium_production") {
      const value = 10 * level * 1.1 ** level * 1.36;
      const prior = 10 * previous * 1.1 ** previous * 1.36;
      return `Producción teórica a 20 °C y sin penalización energética: ${levelNumber.format(value)} deuterio por hora; aumento: ${levelNumber.format(value - prior)}. La temperatura real modifica este dato.`;
    }
    if (kind === "solar_energy") {
      const value = 20 * level * 1.1 ** level;
      return `Energía total aportada por esta planta: ${levelNumber.format(value)}.`;
    }
    if (kind === "fusion_energy") {
      const value = 45 * level * 1.08 ** level;
      return `Energía total aportada por este reactor: ${levelNumber.format(value)}.`;
    }
    if (kind === "storage") return `Capacidad aproximada del depósito: ${levelNumber.format(10000 * 2.5 ** level)} unidades.`;
    if (kind === "terraformer") return `Campos adicionales acumulados: ${levelNumber.format(level * 10)}.`;
    if (kind === "fleet_slots") return `Ranuras simultáneas de flota: ${levelNumber.format(1 + level)}.`;
    if (kind === "colonies") return `Máximo de planetas: ${levelNumber.format(1 + Math.ceil(level / 2))}.`;
    if (kind === "agronomy") return `Bonificación acumulada de las granjas: +${levelNumber.format(level * 12)} %.`;
    if (kind === "combat_bonus") return `Multiplicador acumulado de esta estadística: +${levelNumber.format(level * 10)} %.`;
    return "";
  }

  function updateLevelCalculator(calculator) {
    const input = calculator.querySelector("[data-level-input]");
    const result = calculator.querySelector("[data-level-result]");
    if (!input || !result) return;
    let base;
    try {
      base = JSON.parse(calculator.dataset.levelBase || "[]");
    } catch (_error) {
      base = [];
    }
    const level = Math.min(100, Math.max(1, Math.trunc(Number(input.value) || 1)));
    input.value = String(level);
    const factor = Math.max(1, Number(calculator.dataset.levelFactor || 1));
    const exact = base.map((value) => Math.floor(Number(value) * factor ** (level - 1)));
    const cumulative = base.map((_value, resourceIndex) => {
      let total = 0;
      for (let current = 1; current <= level; current += 1) {
        total += Math.floor(Number(base[resourceIndex]) * factor ** (current - 1));
      }
      return total;
    });
    const labels = ["metal", "cristal", "deuterio"];
    const exactText = exact.map((value, index) => `${levelNumber.format(value)} de ${labels[index]}`).join(", ");
    const cumulativeText = cumulative.map((value, index) => `${levelNumber.format(value)} de ${labels[index]}`).join(", ");
    const seconds = Math.max(0, Number(calculator.dataset.levelSeconds || 0));
    const levelSeconds = seconds * factor ** (level - 1);
    const projection = levelProjection(calculator.dataset.levelProjection || "", level);
    result.textContent = `Nivel ${level}. Coste exacto de ese nivel: ${exactText}. Inversión acumulada desde nivel 1: ${cumulativeText}. Tiempo base del encargo: ${levelNumber.format(levelSeconds)} segundos, antes de velocidad y bonificaciones. ${projection}`;
  }

  document.querySelectorAll("[data-level-calculator]").forEach((calculator) => {
    calculator.querySelector("[data-level-calculate]")?.addEventListener("click", () => {
      updateLevelCalculator(calculator);
    });
    calculator.querySelector("[data-level-input]")?.addEventListener("change", () => {
      updateLevelCalculator(calculator);
    });
    updateLevelCalculator(calculator);
  });

  const fleetTemplateSelect = document.getElementById("fleet-template-select");
  document.querySelector("[data-apply-fleet-template]")?.addEventListener("click", () => {
    const option = fleetTemplateSelect?.selectedOptions[0];
    const missionForm = document.querySelector(".mission-form");
    if (!option || !missionForm) return;
    let ships = {};
    let cargo = {};
    try {
      ships = JSON.parse(option.dataset.ships || "{}");
      cargo = JSON.parse(option.dataset.cargo || "{}");
    } catch (_error) {
      announce("La plantilla no se pudo interpretar.");
      return;
    }
    let unavailable = 0;
    missionForm.querySelectorAll("input[name^='ship_']").forEach((input) => {
      const key = input.name.slice(5);
      const desired = Math.max(0, Number(ships[key] || 0));
      const available = Number(input.max || desired);
      input.value = Math.min(desired, available);
      if (desired > available) unavailable += desired - available;
    });
    ["metal", "crystal", "deuterium", "food"].forEach((key) => {
      const input = missionForm.querySelector(`[name='cargo_${key}']`);
      if (input) input.value = Math.max(0, Number(cargo[key] || 0));
    });
    const mission = missionForm.querySelector("select[name='mission']");
    if (mission && [...mission.options].some((item) => item.value === option.dataset.mission)) mission.value = option.dataset.mission;
    const speed = missionForm.querySelector("select[name='speed_percent']");
    if (speed && [...speed.options].some((item) => item.value === option.dataset.speed)) speed.value = option.dataset.speed;
    announce(unavailable
      ? `Plantilla ${option.textContent} aplicada. Faltan ${unavailable} naves y las cantidades se limitaron a las disponibles.`
      : `Plantilla ${option.textContent} aplicada. Revisa destino, carga y consumo antes de enviar.`);
      missionForm.querySelector("input[name^='ship_']")?.focus({ preventScroll: false });
  });

  const planType = document.querySelector("[data-plan-type]");
  const planItem = document.querySelector("[data-plan-item]");
  function syncPlanCatalog() {
    if (!planType || !planItem) return;
    let first = null;
    [...planItem.querySelectorAll("optgroup")].forEach((group) => {
      const active = group.dataset.planGroup === planType.value;
      group.disabled = !active;
      group.hidden = !active;
      [...group.querySelectorAll("option")].forEach((option) => {
        option.disabled = !active;
        if (active && !first) first = option;
      });
    });
    if (first && first.disabled === false && planItem.selectedOptions[0]?.disabled) {
      first.selected = true;
    }
  }
  planType?.addEventListener("change", syncPlanCatalog);
  syncPlanCatalog();

  async function deliverExternalNotifications() {
    if (!("Notification" in window) || Notification.permission !== "granted") return;
    try {
      const response = await fetch("/api/notificaciones/pendientes", { headers: { Accept: "application/json" } });
      if (!response.ok) return;
      const payload = await response.json();
      const ids = [];
      (payload.notifications || []).forEach((notice) => {
        new Notification(notice.title, { body: notice.body, tag: `eclipse-${notice.tag || notice.id}` });
        ids.push(notice.id);
      });
      if (ids.length) {
        await fetch("/api/notificaciones/entregadas", {
          method: "POST",
          headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
          body: JSON.stringify({ ids }),
        });
      }
    } catch (_error) {
      // Los avisos internos siguen disponibles si el sistema operativo los bloquea.
    }
  }
  document.querySelector("[data-request-notifications]")?.addEventListener("click", async () => {
    if (!("Notification" in window)) {
      announce("Este navegador no permite avisos externos. El centro de avisos interno sigue disponible.");
      return;
    }
    const permission = await Notification.requestPermission();
    announce(permission === "granted"
      ? "Permiso de avisos externos concedido."
      : "No se concedió el permiso. El centro interno sigue disponible.");
    if (permission === "granted") deliverExternalNotifications();
  });
  window.setInterval(deliverExternalNotifications, 30000);
  deliverExternalNotifications();

  const ledgerFilter = document.querySelector("[data-ledger-filter]");
  const ledgerRows = [...document.querySelectorAll("[data-ledger-row]")];
  const ledgerStatus = document.getElementById("ledger-filter-status");
  ledgerFilter?.addEventListener("input", () => {
    const query = normalizeWikiText(ledgerFilter.value);
    let visible = 0;
    ledgerRows.forEach((row) => {
      const matches = normalizeWikiText(row.textContent).includes(query);
      row.hidden = !matches;
      if (matches) visible += 1;
    });
    if (ledgerStatus) ledgerStatus.textContent = `Mostrando ${visible} ${visible === 1 ? "planeta" : "planetas"}.`;
  });

  const debrisMetal = document.querySelector("[data-debris-metal]");
  const debrisCrystal = document.querySelector("[data-debris-crystal]");
  const debrisDeuterium = document.querySelector("[data-debris-deuterium]");
  const recyclerResult = document.querySelector("[data-recycler-result]");
  function updateRecyclerEstimate() {
    if (!recyclerResult) return;
    const capacity = Math.max(1, Number(recyclerResult.dataset.recyclerCapacity || 1));
    const debris = Math.max(0, Number(debrisMetal?.value || 0))
      + Math.max(0, Number(debrisCrystal?.value || 0))
      + Math.max(0, Number(debrisDeuterium?.value || 0));
    const recyclers = Math.ceil(debris / capacity);
    recyclerResult.textContent = `Se necesitan ${new Intl.NumberFormat("es-ES").format(recyclers)} recicladores para ${new Intl.NumberFormat("es-ES").format(debris)} unidades de escombros.`;
  }
  debrisMetal?.addEventListener("input", updateRecyclerEstimate);
  debrisCrystal?.addEventListener("input", updateRecyclerEstimate);
  debrisDeuterium?.addEventListener("input", updateRecyclerEstimate);

  const tabScopes = [...document.querySelectorAll("[data-tab-scope]")];

  function escapeHtml(value) {
    const text = document.createElement("span");
    text.textContent = String(value);
    return text.innerHTML;
  }

  function panelsInScope(scope) {
    if (scope.hasAttribute("data-tab-auto")) {
      [...scope.querySelectorAll(":scope > section")].forEach((panel, index) => {
        const heading = panel.querySelector("h2");
        if (!heading || panel.classList.contains("metric-grid")) return;
        panel.dataset.tabPanel = "";
        if (!panel.dataset.tabLabel) panel.dataset.tabLabel = heading.textContent.trim();
        if (!panel.dataset.tabId) {
          panel.dataset.tabId = heading.id ? `${heading.id}-panel` : `${scope.dataset.tabScope}-apartado-${index + 1}`;
        }
      });
    }
    return [...scope.querySelectorAll("[data-tab-panel]")]
      .filter((panel) => panel.closest("[data-tab-scope]") === scope);
  }

  function tabState(scope) {
    return {
      scope,
      name: scope.dataset.tabScope,
      navigation: scope.querySelector("[data-tab-navigation]"),
      list: scope.querySelector("[data-tab-list]"),
      status: scope.querySelector("[data-tab-status]"),
      panels: panelsInScope(scope),
      buttons: [],
      activeIndex: 0,
    };
  }

  const tabSets = tabScopes.map(tabState).filter((state) => (
    state.name && state.navigation && state.list && state.panels.length > 1
  ));

  function storedTabIndex(state) {
    const hashId = decodeURIComponent(window.location.hash.slice(1));
    const hashTarget = hashId ? document.getElementById(hashId) : null;
    const hashIndex = state.panels.findIndex((panel) => (
      panel.id === hashId || panel.dataset.tabId === hashId
      || (hashTarget && panel.contains(hashTarget))
    ));
    if (hashIndex >= 0) return hashIndex;
    try {
      const storedId = sessionStorage.getItem(`eclipse-tab:${state.name}`);
      const storedIndex = state.panels.findIndex((panel) => panel.id === storedId);
      if (storedIndex >= 0) return storedIndex;
    } catch (_error) {
      // La primera pestaña es una alternativa segura.
    }
    return 0;
  }

  function updateSetupReview(scope) {
    const review = scope.querySelector("[data-setup-review]");
    if (!review) return;
    const form = scope.closest("form") || scope;
    const descriptions = [
      ["Comandante", "commander"],
      ["Imperio", "empire"],
      ["Universo", "universe"],
      ["Rivales IA", "ai_count"],
      ["Dificultad", "difficulty"],
      ["Velocidad", "speed"],
      ["Galaxias", "galaxy_count"],
      ["Origen", "origin"],
      ["Gobierno", "government_type"],
      ["Modo de interfaz", "navigation_mode"],
      ["Tiempo sin conexión", "offline_mode"],
    ];
    const items = descriptions.map(([label, name]) => {
      const field = form.elements?.namedItem(name);
      if (!field) return "";
      const value = field.matches?.("select")
        ? field.selectedOptions[0]?.textContent.trim()
        : field.value.trim();
      return `<li><strong>${label}:</strong> ${escapeHtml(value || "Sin indicar")}</li>`;
    }).filter(Boolean);
    review.innerHTML = items.join("");
  }

  function setActiveTab(state, index, options = {}) {
    const { focus = false, announceChange = false, updateHistory = false } = options;
    if (index < 0 || index >= state.panels.length) return;
    state.activeIndex = index;
    state.panels.forEach((panel, panelIndex) => {
      const active = panelIndex === index;
      panel.hidden = !active;
      state.buttons[panelIndex].setAttribute("aria-selected", String(active));
      state.buttons[panelIndex].tabIndex = active ? 0 : -1;
    });
    const activePanel = state.panels[index];
    const activeButton = state.buttons[index];
    try {
      sessionStorage.setItem(`eclipse-tab:${state.name}`, activePanel.id);
    } catch (_error) {
      // El fragmento de URL sigue permitiendo recuperar el apartado.
    }
    if (focus) activeButton.focus();
    if (updateHistory && window.location.hash !== `#${activePanel.id}`) {
      window.history.pushState({ eclipseTab: activePanel.id }, "", `#${activePanel.id}`);
    }
    updateSetupReview(state.scope);
    if (announceChange && state.status) {
      state.status.textContent = `${activeButton.textContent.trim()}, pestaña ${index + 1} de ${state.panels.length}.`;
      playEffect?.("click");
    }
  }

  function initializeTabSet(state) {
    state.list.replaceChildren();
    state.panels.forEach((panel, index) => {
      const panelId = panel.dataset.tabId || panel.id || `${state.name}-panel-${index + 1}`;
      const buttonId = `${panelId}-tab`;
      panel.id = panelId;
      const button = document.createElement("button");
      button.type = "button";
      button.id = buttonId;
      button.className = "tab-button";
      button.textContent = panel.dataset.tabLabel || `Apartado ${index + 1}`;
      button.setAttribute("role", "tab");
      button.setAttribute("aria-controls", panelId);
      button.setAttribute("aria-selected", "false");
      button.tabIndex = -1;
      button.addEventListener("click", () => {
        state.buttons.forEach((item, itemIndex) => {
          item.tabIndex = itemIndex === index ? 0 : -1;
        });
        setActiveTab(state, index, { focus: true, announceChange: true, updateHistory: true });
      });
      button.addEventListener("keydown", (event) => {
        let targetIndex = -1;
        if (event.key === "ArrowRight") targetIndex = (index + 1) % state.buttons.length;
        else if (event.key === "ArrowLeft") targetIndex = (index - 1 + state.buttons.length) % state.buttons.length;
        else if (event.key === "Home") targetIndex = 0;
        else if (event.key === "End") targetIndex = state.buttons.length - 1;
        else if (event.key === "Enter" || event.key === " ") {
          event.preventDefault();
          setActiveTab(state, index, { focus: true, announceChange: true, updateHistory: true });
          return;
        } else {
          return;
        }
        event.preventDefault();
        state.buttons.forEach((item, itemIndex) => {
          item.tabIndex = itemIndex === targetIndex ? 0 : -1;
        });
        state.buttons[targetIndex].focus();
      });
      state.list.append(button);
      state.buttons.push(button);
      panel.setAttribute("role", "tabpanel");
      panel.setAttribute("aria-labelledby", buttonId);
      panel.tabIndex = 0;
    });
    const initialIndex = storedTabIndex(state);
    state.buttons[initialIndex].tabIndex = 0;
    setActiveTab(state, initialIndex);
  }

  tabSets.forEach(initializeTabSet);
  document.documentElement.dataset.tabsReady = "true";

  function tabSetEnabled(state) {
    return settings.navigationMode === "tabs" || state.scope.hasAttribute("data-tab-always");
  }

  function activateTabContainingElement(target, announceChange = false) {
    if (!target) return false;
    const panel = target.closest("[data-tab-panel]");
    const scope = panel?.closest("[data-tab-scope]");
    const state = tabSets.find((item) => item.scope === scope);
    if (!state || !panel || !tabSetEnabled(state)) return false;
    const index = state.panels.indexOf(panel);
    if (index < 0) return false;
    setActiveTab(state, index, { announceChange, updateHistory: false });
    return true;
  }

  function syncWikiTabFilter() {
    const state = tabSets.find((item) => item.name === "encyclopedia");
    if (!state || settings.navigationMode !== "tabs") return;
    const selectedCategory = wikiCategoryFilter?.value || "";
    const matchingIndexes = [];
    state.panels.forEach((panel, index) => {
      const count = Number(panel.dataset.wikiMatchCount || 0);
      if (count > 0) matchingIndexes.push(index);
      const original = panel.dataset.tabLabel || `Categoría ${index + 1}`;
      state.buttons[index].textContent = `${original} (${count})`;
    });
    let desired = selectedCategory
      ? state.panels.findIndex((panel) => panel.dataset.wikiCategory === selectedCategory)
      : state.activeIndex;
    if (!matchingIndexes.includes(desired)) desired = matchingIndexes[0] ?? state.activeIndex;
    const changedCategory = desired !== state.activeIndex;
    setActiveTab(state, desired);
    if (changedCategory && state.status) {
      const label = state.panels[desired].dataset.tabLabel || `Categoría ${desired + 1}`;
      state.status.textContent = `${label}, pestaña ${desired + 1} de ${state.panels.length}.`;
    }
  }
  syncWikiTabFilter();

  function applyNavigationMode(announceChange = false) {
    const tabbed = settings.navigationMode === "tabs";
    if (pageIndex) {
      const hasIndexLinks = Boolean(pageIndexList?.children.length);
      pageIndex.hidden = !hasIndexLinks || (tabbed && tabSets.length > 0);
    }
    document.querySelectorAll("[data-navigation-classic]").forEach((navigation) => {
      navigation.hidden = tabbed;
    });
    document.querySelectorAll("[data-navigation-grouped]").forEach((navigation) => {
      navigation.hidden = !tabbed;
    });
    tabSets.forEach((state) => {
      const stateTabbed = tabSetEnabled(state);
      state.navigation.hidden = !stateTabbed;
      if (stateTabbed) {
        state.panels.forEach((panel, index) => {
          panel.setAttribute("role", "tabpanel");
          panel.setAttribute("aria-labelledby", state.buttons[index].id);
          panel.tabIndex = 0;
        });
        setActiveTab(state, state.activeIndex, { announceChange });
      } else {
        state.panels.forEach((panel) => {
          panel.hidden = false;
          panel.removeAttribute("role");
          panel.removeAttribute("aria-labelledby");
          panel.removeAttribute("tabindex");
        });
      }
    });
    if (wikiSearch && wikiCategoryFilter) {
      filterWiki({ announceResult: false });
    }
    if (announceChange) {
      announce(tabbed
        ? "Modo por pestañas activado. Todo el contenido sigue disponible, organizado por apartados."
        : "Modo normal activado. Todos los apartados se muestran de forma continua.");
    }
  }

  document.addEventListener("invalid", (event) => {
    const panel = event.target.closest("[data-tab-panel]");
    const scope = panel?.closest("[data-tab-scope]");
    const state = tabSets.find((item) => item.scope === scope);
    if (!state || !panel || !tabSetEnabled(state)) return;
    const index = state.panels.indexOf(panel);
    setActiveTab(state, index, { announceChange: true, updateHistory: true });
    window.setTimeout(() => event.target.focus(), 0);
  }, true);

  window.addEventListener("popstate", () => {
    const hashId = decodeURIComponent(window.location.hash.slice(1));
    tabSets.forEach((state) => {
      const index = state.panels.findIndex((panel) => panel.id === hashId);
      if (index >= 0 && tabSetEnabled(state)) setActiveTab(state, index);
    });
  });

  document.querySelectorAll("[data-navigation-group]").forEach((group) => {
    const storageKey = `eclipse-navigation-group:${group.dataset.navigationGroup}`;
    const containsCurrent = Boolean(group.querySelector('[aria-current="page"]'));
    try {
      const stored = localStorage.getItem(storageKey);
      group.open = containsCurrent || stored === "1";
    } catch (_error) {
      group.open = containsCurrent;
    }
    group.addEventListener("toggle", () => {
      try {
        localStorage.setItem(storageKey, group.open ? "1" : "0");
      } catch (_error) {
        // El elemento details continúa siendo operable sin persistencia local.
      }
    });
  });

  applyNavigationMode(false);
})();
