(() => {
  "use strict";

  const boot = window.UI_BUILDER_BOOT || {};
  const storage = {
    language: "hockey.language",
    tournament: "hockey.selectedTournamentId",
    game: "hockey.selectedGameId",
    gameDate: "hockey.gameDate",
    session: "hockey.operatorSessionToken",
    vmixDevice: "hockey.vmix.selected_device",
    account: "hockey.accountId",
  };

  const state = {
    open: false,
    loading: false,
    language: localStorage.getItem(storage.language) || "ru",
    view: "tournaments",
    mode: "today",
    search: "",
    league: "",
    season: "",
    stage: "",
    selectedId: localStorage.getItem(storage.tournament) || "",
    selectedGameId: localStorage.getItem(storage.game) || "",
    selectedTournament: null,
    selectedGame: null,
    selectedMatchDetails: null,
    standings: null,
    tournamentStatistics: null,
    statLabels: null,
    gameDate: localStorage.getItem(storage.gameDate) || localDateIso(),
    navigation: null,
    games: null,
    teamSchedule: null,
    teamScheduleTournamentId: "",
    settings: null,
    settingsPromise: null,
    settingsLoadedAt: 0,
    root: null,
    drawer: null,
    backdrop: null,
    selectedChip: null,
    settingsModal: null,
    gamesAutoSyncAttempted: new Set(),
    scheduleSyncBusy: false,
    gameOpenBusy: false,
    gameOpenGeneration: 0,
    gamePollTimer: null,
    gamePollBusy: false,
    gamePollErrorCount: 0,
    gamePollStoppedGameId: "",
    currentUser: null,
    isAdmin: false,
    lastAttemptedGameId: "",
    lastGameOpenError: "",
  };

  const text = {
    ru: {
      title: "Хоккей",
      tournaments: "Турниры",
      matches: "Матчи",
      today: "Сегодня",
      all: "Все турниры",
      search: "Поиск турнира…",
      league: "Лига",
      season: "Сезон",
      stage: "Стадия",
      allValues: "Все",
      noToday: "На выбранную дату активных турниров нет",
      fallback: "Показываем последний доступный сезон",
      noItems: "Турниры не найдены",
      settings: "Настройки Stat2TV",
      sync: "Обновить турниры",
      configured: "Подключение настроено",
      notConfigured: "Нужно настроить подключение",
      selectedTournament: "Выбран турнир",
      selectedGame: "Выбран матч",
      changeTournament: "Сменить турнир",
      scheduleCached: "Расписание загружено · даты переключаются локально",
      chooseTournament: "Выбрать турнир",
      liveUpdate: "Автообновление 1 сек",
      updateStopped: "Матч завершён · обновление остановлено",
      close: "Закрыть",
      back: "Назад к турнирам",
      save: "Сохранить",
      test: "Проверить соединение",
      importSeed: "Импортировать приложенный XML",
      apiAddress: "Адрес API",
      username: "Логин",
      password: "Новый пароль",
      authMode: "Авторизация",
      displayLanguage: "Язык данных в вебе",
      fallbackLanguage: "Резервный язык",
      interval: "Интервал обновления, сек",
      verifySsl: "Проверять SSL",
      credentialsHint: "Пустой пароль оставляет прежний. Данные сохраняются только локально на сервере.",
      saved: "Настройки сохранены",
      connectionOk: "Соединение установлено",
      syncOk: "Турниры обновлены",
      sourceSeed: "Сейчас используются данные приложенного XML",
      sourceApi: "Последняя синхронизация из Stat2TV",
      offSeason: "Межсезонье",
      gamesForDate: "Матчи на дату",
      noGames: "На выбранную дату матчей нет",
      noGamesLoaded: "Календарь матчей ещё не загружен",
      updateGames: "Обновить матчи",
      discovering: "Ищем XML матчей…",
      loadingGames: "Загружаем календарь…",
      gamesLoaded: "Матчи загружены",
      openGame: "Открыть матч",
      gameOpened: "Матч открыт",
      loadingMatchTitle: "Загружаем матч",
      loadingMatchHint: "Пожалуйста, дождитесь окончания загрузки и ничего не нажимайте",
      loadingMatchDetails: "Получаем составы и статистику матча…",
      loadingMatchSession: "Создаём рабочую сессию…",
      loadingMatchStandings: "Обновляем турнирные данные…",
      loadingMatchFinishing: "Подготавливаем интерфейс…",
      endpointTemplate: "Шаблон XML матчей",
      endpointHint: "Оставьте пустым для автоматического поиска. Доступны переменные {tournament_id}, {date}, {season}.",
      endpointAuto: "Автоматически искать endpoint матчей",
      cacheSeconds: "Кэш матчей, сек",
      rediscover: "Найти endpoint заново",
      diagnostics: "Диагностика",
      connections: "Подключения",
      checkAgain: "Проверить ещё раз",
      deepMatchTest: "Проверить выбранный матч",
      matchSelectionReady: "Открытие матча",
      remoteSyncReady: "Удалённая синхронизация",
      database: "PostgreSQL",
      stat2tv: "Stat2TV",
      agentVmix: "Agent / vMix",
      selectedMatchCheck: "Выбранный матч",
      optionalConnection: "не требуется для открытия матча",
      adminOnly: "Только для администратора",
      startTime: "Начало",
      arena: "Арена",
      gameNumber: "Матч",
      score: "Счёт",
      unknownTeams: "Команды не распознаны",
      syncFailed: "Не удалось загрузить матчи",
      currentSession: "Активная сессия",
      datePrevious: "Предыдущий день",
      dateNext: "Следующий день",
      dateToday: "Перейти к сегодняшней дате",
      cached: "Из локальной базы",
      fromApi: "Stat2TV",
      endpointFound: "Endpoint матчей найден",
      endpointNotFound: "Endpoint матчей пока не найден",
      statusScheduled: "Не начался",
      statusLive: "Идёт",
      statusFinished: "Завершён",
      series: "Серия",
      manualMatchTitle: "Загрузка по ID матча",
      manualMatchHint: "Укажите ID матча — лига и турнир будут определены автоматически по доступному JSON.",
      manualMatchPlaceholder: "Например, 901687",
      manualMatchButton: "Загрузить",
      manualMatchInvalid: "Укажите корректный ID матча",
      liveDataUpdated: "Данные матча обновлены",
      finishOvertime: "ОТ",
      finishShootout: "Б",
    },
    en: {
      title: "Hockey",
      tournaments: "Tournaments",
      matches: "Games",
      today: "Today",
      all: "All tournaments",
      search: "Search tournaments…",
      league: "League",
      season: "Season",
      stage: "Stage",
      allValues: "All",
      noToday: "No active tournaments for this date",
      fallback: "Showing the latest available season",
      noItems: "No tournaments found",
      settings: "Stat2TV settings",
      sync: "Update tournaments",
      configured: "Connection configured",
      notConfigured: "Connection setup required",
      selectedTournament: "Selected tournament",
      selectedGame: "Selected game",
      changeTournament: "Change tournament",
      scheduleCached: "Schedule loaded · dates switch locally",
      chooseTournament: "Select tournament",
      liveUpdate: "Auto refresh 1 sec",
      updateStopped: "Game finished · refresh stopped",
      close: "Close",
      back: "Back to tournaments",
      save: "Save",
      test: "Test connection",
      importSeed: "Import attached XML",
      apiAddress: "API address",
      username: "Login",
      password: "New password",
      authMode: "Authentication",
      displayLanguage: "Web data language",
      fallbackLanguage: "Fallback language",
      interval: "Refresh interval, sec",
      verifySsl: "Verify SSL",
      credentialsHint: "Empty password keeps the current one. Credentials are stored locally on the server.",
      saved: "Settings saved",
      connectionOk: "Connection established",
      syncOk: "Tournaments updated",
      sourceSeed: "Attached XML data is currently used",
      sourceApi: "Last synchronization from Stat2TV",
      offSeason: "Off-season",
      gamesForDate: "Games on date",
      noGames: "No games on the selected date",
      noGamesLoaded: "The game calendar has not been loaded yet",
      updateGames: "Update games",
      discovering: "Discovering games XML…",
      loadingGames: "Loading schedule…",
      gamesLoaded: "Games loaded",
      openGame: "Open game",
      gameOpened: "Game opened",
      loadingMatchTitle: "Loading game",
      loadingMatchHint: "Please wait until loading is complete and do not click anything",
      loadingMatchDetails: "Loading rosters and game statistics…",
      loadingMatchSession: "Creating the operator session…",
      loadingMatchStandings: "Updating tournament data…",
      loadingMatchFinishing: "Preparing the interface…",
      endpointTemplate: "Games XML template",
      endpointHint: "Leave blank for automatic discovery. Variables: {tournament_id}, {date}, {season}.",
      endpointAuto: "Automatically discover games endpoint",
      cacheSeconds: "Games cache, sec",
      rediscover: "Rediscover endpoint",
      diagnostics: "Diagnostics",
      connections: "Connections",
      checkAgain: "Check again",
      deepMatchTest: "Check selected game",
      matchSelectionReady: "Game selection",
      remoteSyncReady: "Remote sync",
      database: "PostgreSQL",
      stat2tv: "Stat2TV",
      agentVmix: "Agent / vMix",
      selectedMatchCheck: "Selected game",
      optionalConnection: "not required to open a game",
      adminOnly: "Administrator only",
      startTime: "Start",
      arena: "Arena",
      gameNumber: "Game",
      score: "Score",
      unknownTeams: "Teams were not recognized",
      syncFailed: "Failed to load games",
      currentSession: "Active session",
      datePrevious: "Previous day",
      dateNext: "Next day",
      dateToday: "Go to today",
      cached: "Local database",
      fromApi: "Stat2TV",
      endpointFound: "Games endpoint found",
      endpointNotFound: "Games endpoint not found yet",
      statusScheduled: "Scheduled",
      statusLive: "Live",
      statusFinished: "Finished",
      series: "Series",
      manualMatchTitle: "Load by game ID",
      manualMatchHint: "Enter a game ID — the league and tournament will be detected automatically from the available JSON.",
      manualMatchPlaceholder: "For example, 901687",
      manualMatchButton: "Load",
      manualMatchInvalid: "Enter a valid game ID",
      liveDataUpdated: "Match data updated",
      finishOvertime: "OT",
      finishShootout: "SO",
    },
  };

  function t(key) {
    return text[state.language]?.[key] || text.ru[key] || key;
  }

  function escapeHtml(value) {
    return String(value ?? "")
      .replaceAll("&", "&amp;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;")
      .replaceAll('"', "&quot;")
      .replaceAll("'", "&#039;");
  }

  function localDateIso() {
    const now = new Date();
    const offset = now.getTimezoneOffset();
    return new Date(now.getTime() - offset * 60000).toISOString().slice(0, 10);
  }

  function shiftDate(value, days) {
    const date = new Date(`${value}T12:00:00`);
    date.setDate(date.getDate() + days);
    const offset = date.getTimezoneOffset();
    return new Date(date.getTime() - offset * 60000).toISOString().slice(0, 10);
  }

  function formatDate(value) {
    if (!value) return "—";
    try {
      return new Intl.DateTimeFormat(
        state.language === "en" ? "en-GB" : "ru-RU",
        { day: "2-digit", month: "long", year: "numeric" }
      ).format(new Date(`${value}T12:00:00`));
    } catch (_) {
      return value;
    }
  }

  function currentAgentDeviceId() {
    return String(window.HockeyAgentRuntime?.currentDeviceId?.() || localStorage.getItem(storage.vmixDevice) || "").trim();
  }

  function rememberAssignedAgent(deviceId) {
    const clean = String(deviceId || "").trim();
    if (!clean) return;
    localStorage.setItem(storage.vmixDevice, clean);
    try { window.HockeyAgentRuntime?.selectDevice?.(clean); } catch (_) {}
  }

  async function ensureSessionAgentAssignment(session) {
    const token = String(session?.token || localStorage.getItem(storage.session) || "").trim();
    let assignment = session?.agent_assignment && typeof session.agent_assignment === "object"
      ? session.agent_assignment
      : null;

    if (assignment?.device_id) rememberAssignedAgent(assignment.device_id);
    if (assignment?.delivered) return assignment;
    if (!token) return assignment;

    // BUILD111: opening the web match is not proof that Agent received
    // match.assign. Recover from stale/empty localStorage only when routing is
    // unambiguous: the selected live Agent or exactly one live active Agent.
    let devicesPayload = null;
    try {
      devicesPayload = await request("/api/hockey/agents/devices", { timeoutMs: 3500 });
    } catch (_) {
      return assignment;
    }
    const devices = Array.isArray(devicesPayload?.devices) ? devicesPayload.devices : [];
    const live = devices.filter((item) => (
      item?.paired_to_me
      && item?.active_for_account
      && item?.online
    ));
    const preferredIds = [
      String(assignment?.device_id || "").trim(),
      currentAgentDeviceId(),
    ].filter(Boolean);
    let target = null;
    for (const preferred of preferredIds) {
      target = live.find((item) => String(item.device_id || "") === preferred) || null;
      if (target) break;
    }
    if (!target && live.length === 1) target = live[0];
    if (!target?.device_id) return assignment;

    try {
      const rebound = await request(
        `/api/hockey/agents/devices/${encodeURIComponent(target.device_id)}/select-session`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ session_token: token, apply_mapping: false }),
          timeoutMs: 5000,
        }
      );
      assignment = rebound?.assignment || assignment;
      if (assignment?.device_id) rememberAssignedAgent(assignment.device_id);
      if (session && assignment) session.agent_assignment = assignment;
      return assignment;
    } catch (error) {
      console.warn("[Hockey] Agent match.assign retry failed:", error);
      return assignment;
    }
  }

  async function refreshSessionAgentMapping(session) {
    const assignment = await ensureSessionAgentAssignment(session);
    const deviceId = String(assignment?.device_id || "").trim();
    if (!deviceId || !assignment?.delivered) {
      return { ok: false, reason: "agent_match_not_delivered" };
    }
    return request(
      `/api/hockey/agents/devices/${encodeURIComponent(deviceId)}/apply-mapping`,
      { method: "POST", timeoutMs: 60000 }
    );
  }

  async function ensureAccountScopedRuntimeState() {
    let user = null;
    try {
      user = await request("/api/hockey/me");
    } catch (_) {
      return;
    }
    state.currentUser = user;
    state.isAdmin = Boolean(user?.is_admin);
    const editorButton = document.getElementById("runtimeEditorBtn");
    editorButton?.classList.toggle("hidden", !state.isAdmin);
    document.body.dataset.hockeyAdmin = state.isAdmin ? "1" : "0";
    const currentAccountId = String(user?.id || "").trim();
    if (!currentAccountId) return;
    const previousAccountId = String(localStorage.getItem(storage.account) || "").trim();
    if (previousAccountId && previousAccountId !== currentAccountId) {
      // Session tokens and Agent selection belong to one WFL account only.
      // Keeping them across logout/login can bind a new operator to the
      // previous account's match or vMix device.
      localStorage.removeItem(storage.session);
      localStorage.removeItem(storage.game);
      localStorage.removeItem(storage.vmixDevice);
      state.selectedGameId = "";
      state.selectedGame = null;
      state.selectedMatchDetails = null;
      stopSelectedGamePolling();
      try { window.HockeyAgentRuntime?.selectDevice?.(""); } catch (_) {}
    }
    localStorage.setItem(storage.account, currentAccountId);
  }

  async function request(path, options = {}) {
    const { timeoutMs = 15000, ...fetchOptions } = options;
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const response = await fetch(path, {
        cache: "no-store",
        credentials: "same-origin",
        ...fetchOptions,
        signal: controller.signal,
      });
      let payload = {};
      try {
        payload = await response.json();
      } catch (_) {}
      if (!response.ok) {
        if (response.status === 401) {
          window.location.assign("/");
        }
        const detail = payload.detail;
        throw new Error(
          typeof detail === "string"
            ? detail
            : detail?.message || `HTTP ${response.status}`
        );
      }
      return payload;
    } catch (error) {
      if (error?.name === "AbortError") {
        // BUILD109: this helper is used for local DB/session/Agent endpoints too.
        // Calling every HTTP timeout a Stat2TV timeout hides the real subsystem.
        throw new Error("Превышено время ожидания ответа сервера");
      }
      throw error;
    } finally {
      clearTimeout(timeout);
    }
  }

  function notify(message, error = false) {
    if (!error && window.HOCKEY_RUNTIME_TOASTS_ENABLED === false) return;
    let toast = document.querySelector(".hockey-module-toast");
    if (!toast) {
      toast = document.createElement("div");
      toast.className = "hockey-module-toast";
      document.body.appendChild(toast);
    }
    toast.textContent = message;
    toast.classList.toggle("error", error);
    toast.classList.add("show");
    clearTimeout(toast._hideTimer);
    toast._hideTimer = setTimeout(() => toast.classList.remove("show"), 3200);
  }

  function gameById(id) {
    return (state.games?.items || []).find(
      (item) => String(item?.external_id || item?.id || "") === String(id || "")
    ) || null;
  }

  function matchLabelById(id) {
    const game = gameById(id);
    if (!game) return "";
    const home = game.home?.name || game.home_name || game.team1 || "";
    const away = game.away?.name || game.away_name || game.team2 || "";
    return [home, away].filter(Boolean).join(" — ");
  }

  function setMatchLoading(visible, { game = "", stage = "" } = {}) {
    const overlay = state.root?.querySelector("[data-match-loading]");
    if (!overlay) return;
    const titleNode = overlay.querySelector("[data-match-loading-title]");
    const gameNode = overlay.querySelector("[data-match-loading-game]");
    const stageNode = overlay.querySelector("[data-match-loading-stage]");
    const hintNode = overlay.querySelector("[data-match-loading-hint]");
    if (titleNode) titleNode.textContent = t("loadingMatchTitle");
    if (gameNode) {
      gameNode.textContent = game || "";
      gameNode.classList.toggle("hidden", !game);
    }
    if (stageNode) stageNode.textContent = stage || t("loadingMatchDetails");
    if (hintNode) hintNode.textContent = t("loadingMatchHint");
    overlay.classList.toggle("show", Boolean(visible));
    overlay.setAttribute("aria-hidden", visible ? "false" : "true");
    document.documentElement.classList.toggle("hockey-match-loading-active", Boolean(visible));
  }

  function updateMatchLoading(stage) {
    const overlay = state.root?.querySelector("[data-match-loading]");
    const stageNode = overlay?.querySelector("[data-match-loading-stage]");
    if (stageNode) stageNode.textContent = stage || t("loadingMatchDetails");
  }

  function toolbarHost() {
    return boot.mode === "editor"
      ? document.querySelector(".topbar .top-actions")
      : document.querySelector(".runtime-actions");
  }

  function ensureShell() {
    if (state.root) return;

    const toolbar = toolbarHost();
    if (toolbar) {
      const chip = document.createElement("button");
      chip.type = "button";
      chip.className = "hockey-selected-chip placeholder";
      chip.addEventListener("click", () => {
        state.view = state.selectedTournament ? "games" : "tournaments";
        setOpen(true);
        renderView();
      });
      toolbar.prepend(chip);
      state.selectedChip = chip;
    }

    const root = document.createElement("div");
    root.className = "hockey-navigation-root";
    root.innerHTML = `
      <div class="hockey-navigation-backdrop"></div>
      <div class="hockey-match-loading" data-match-loading aria-hidden="true">
        <div class="hockey-match-loading-card" role="status" aria-live="polite" aria-atomic="true">
          <div class="hockey-match-loading-spinner" aria-hidden="true"><i></i></div>
          <small>STAT2TV / HOCKEY</small>
          <strong data-match-loading-title>${escapeHtml(t("loadingMatchTitle"))}</strong>
          <span class="hidden" data-match-loading-game></span>
          <p data-match-loading-stage>${escapeHtml(t("loadingMatchDetails"))}</p>
          <div class="hockey-match-loading-progress" aria-hidden="true"><i></i></div>
          <em data-match-loading-hint>${escapeHtml(t("loadingMatchHint"))}</em>
        </div>
      </div>
      <aside class="hockey-navigation-drawer" aria-label="${escapeHtml(t("title"))}">
        <header class="hockey-nav-head">
          <button type="button" class="hockey-nav-back hidden" data-back data-tooltip="${escapeHtml(t("back"))}">← <span>${escapeHtml(t("tournaments"))}</span></button>
          <div class="hockey-nav-title">
            <small>STAT2TV / HOCKEY</small>
            <strong data-nav-title>${escapeHtml(t("tournaments"))}</strong>
          </div>
          <div class="hockey-nav-head-actions">
            <div class="hockey-language-switch">
              <button type="button" data-language="ru">RU</button>
              <button type="button" data-language="en">EN</button>
            </div>
            ${state.isAdmin ? `
              <button type="button" class="hockey-nav-icon" data-open-connection-diagnostics data-tooltip="${escapeHtml(t("connections"))}">⌁</button>
              <button type="button" class="hockey-nav-icon" data-open-settings data-tooltip="${escapeHtml(t("settings"))}">⚙</button>
            ` : ""}
            ${boot.mode !== "editor" ? `
              <button type="button" class="hockey-nav-icon" data-logout data-tooltip="Выйти">⇥</button>
            ` : ""}
            <button type="button" class="hockey-nav-icon" data-close-nav data-tooltip="${escapeHtml(t("close"))}">×</button>
          </div>
        </header>

        ${state.isAdmin ? `
          <button type="button" class="hockey-connection-bar" data-connection-bar data-open-connection-diagnostics>
            <span></span>
            <strong>${escapeHtml(t("notConfigured"))}</strong>
          </button>
        ` : ""}

        <section class="hockey-tournament-view" data-view="tournaments">
          <div class="hockey-nav-tabs">
            <button type="button" data-nav-mode="today">${escapeHtml(t("today"))}</button>
            <button type="button" data-nav-mode="all">${escapeHtml(t("all"))}</button>
          </div>

          <div class="hockey-nav-search">
            <span>⌕</span>
            <input type="search" data-nav-search placeholder="${escapeHtml(t("search"))}">
          </div>

          <div class="hockey-nav-filters">
            <label>
              <span>${escapeHtml(t("league"))}</span>
              <select data-filter="league"></select>
            </label>
            <label>
              <span>${escapeHtml(t("season"))}</span>
              <select data-filter="season"></select>
            </label>
            <label>
              <span>${escapeHtml(t("stage"))}</span>
              <select data-filter="stage"></select>
            </label>
          </div>

          <form class="hockey-manual-game hockey-manual-game-global" data-manual-game-form>
            <div>
              <strong>${escapeHtml(t("manualMatchTitle"))}</strong>
              <small>${escapeHtml(t("manualMatchHint"))}</small>
            </div>
            <label>
              <input type="text" inputmode="numeric" autocomplete="off" data-manual-game-id placeholder="${escapeHtml(t("manualMatchPlaceholder"))}">
              <button type="submit">${escapeHtml(t("manualMatchButton"))}</button>
            </label>
          </form>
          <div class="hockey-nav-summary" data-nav-summary></div>
          <div class="hockey-nav-list" data-nav-list></div>

          ${boot.mode === "editor" ? `
            <footer class="hockey-nav-footer">
              <button type="button" class="btn" data-seed-import>${escapeHtml(t("importSeed"))}</button>
              <button type="button" class="btn btn-accent" data-sync>${escapeHtml(t("sync"))}</button>
            </footer>
          ` : ""}
        </section>

        <section class="hockey-games-view hidden" data-view="games">
          <div class="hockey-selected-tournament" data-selected-tournament></div>

          <div class="hockey-game-date-nav">
            <button type="button" data-date-shift="-1" data-tooltip="${escapeHtml(t("datePrevious"))}">‹</button>
            <label>
              <span>${escapeHtml(t("gamesForDate"))}</span>
              <input type="date" data-game-date value="${escapeHtml(state.gameDate)}">
            </label>
            <button type="button" data-date-today data-tooltip="${escapeHtml(t("dateToday"))}">${escapeHtml(t("today"))}</button>
            <button type="button" data-date-shift="1" data-tooltip="${escapeHtml(t("dateNext"))}">›</button>
          </div>

          <div class="hockey-games-summary" data-games-summary></div>
          <form class="hockey-manual-game" data-manual-game-form>
            <div>
              <strong>${escapeHtml(t("manualMatchTitle"))}</strong>
              <small>${escapeHtml(t("manualMatchHint"))}</small>
            </div>
            <label>
              <input type="text" inputmode="numeric" autocomplete="off" data-manual-game-id placeholder="${escapeHtml(t("manualMatchPlaceholder"))}">
              <button type="submit">${escapeHtml(t("manualMatchButton"))}</button>
            </label>
          </form>
          <div class="hockey-games-list" data-games-list></div>

          ${state.isAdmin ? `
            <footer class="hockey-games-footer">
              <button type="button" class="btn" data-games-diagnostics>${escapeHtml(t("diagnostics"))}</button>
              <button type="button" class="btn" data-games-rediscover>${escapeHtml(t("rediscover"))}</button>
            </footer>
          ` : ""}
        </section>
      </aside>
    `;
    document.body.appendChild(root);

    state.root = root;
    state.drawer = root.querySelector(".hockey-navigation-drawer");
    state.backdrop = root.querySelector(".hockey-navigation-backdrop");

    state.backdrop.addEventListener("click", () => setOpen(false));
    root.querySelector("[data-close-nav]")?.addEventListener("click", () => setOpen(false));
    root.querySelector("[data-back]")?.addEventListener("click", () => {
      state.view = "tournaments";
      renderView();
      renderTournamentList();
    });
    root.querySelector("[data-open-settings]")?.addEventListener("click", openSettings);
    root.querySelectorAll("[data-open-connection-diagnostics]").forEach((button) => {
      button.addEventListener("click", () => showConnectionDiagnostics({ deep: false }));
    });
    root.querySelector("[data-logout]")?.addEventListener("click", () => {
      const form = document.createElement("form");
      form.method = "post";
      form.action = "/logout";
      document.body.appendChild(form);
      form.submit();
    });
    root.querySelector("[data-sync]")?.addEventListener("click", syncTournaments);
    root.querySelector("[data-seed-import]")?.addEventListener("click", importSeed);
    root.querySelector("[data-games-rediscover]")?.addEventListener("click", () => syncGames(true));
    root.querySelector("[data-games-diagnostics]")?.addEventListener("click", () => showConnectionDiagnostics({ deep: false }));
    root.querySelectorAll("[data-manual-game-form]").forEach((form) => form.addEventListener("submit", (event) => {
      event.preventDefault();
      const input = event.currentTarget.querySelector("[data-manual-game-id]");
      const id = String(input?.value || "").trim();
      if (!/^\d+$/.test(id)) {
        notify(t("manualMatchInvalid"), true);
        input?.focus();
        return;
      }
      openGame(id, { manual: true });
    }));

    root.querySelectorAll("[data-language]").forEach((button) => {
      button.addEventListener("click", async () => {
        const nextLanguage = button.dataset.language === "en" ? "en" : "ru";
        if (state.language === nextLanguage) return;
        state.language = nextLanguage;
        localStorage.setItem(storage.language, state.language);
        renderStaticLabels();

        // RU/EN in the navigation is the operator's live language switch, not
        // merely a cosmetic localStorage preference. Persist it server-side so
        // Mapping's SQL resolver sees the same language, then immediately
        // re-apply the active Mapping to this operator's vMix.
        try {
          await request("/api/hockey/preferences", {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              display_language: state.language,
              vmix_language: state.language,
              tournament_external_id: String(state.selectedId || ""),
            }),
          });
          const selectedAgent = currentAgentDeviceId();
          const mappingRefresh = selectedAgent
            ? await request(`/api/hockey/agents/devices/${encodeURIComponent(selectedAgent)}/apply-mapping`, { method: "POST" })
            : await request("/api/hockey/agents/apply-active-mapping", { method: "POST" });
          if (mappingRefresh && mappingRefresh.ok === false && !["no_active_device", "no_match_assignment", "mapping_missing"].includes(String(mappingRefresh.reason || ""))) {
            notify(`vMix Mapping: ${mappingRefresh.reason || "не удалось применить"}`, true);
          }
        } catch (error) {
          notify(`Язык сохранён локально, но vMix не обновлён: ${error.message || error}`, true);
        }

        window.dispatchEvent(new CustomEvent("hockey:settings-updated", {
          detail: { settings: { ui_language: state.language } },
        }));
        loadNavigation();
        if (state.view === "games") loadGames({ autoSync: false });
        if (state.selectedId) loadTeamSchedule({ force: true });
        if (state.selectedGameId) reloadSelectedGameDetails();
        if (state.selectedId) {
          loadTournamentStandings({ sync: false });
          loadTournamentStatistics({ sync: false });
        }
      });
    });

    root.querySelectorAll("[data-nav-mode]").forEach((button) => {
      button.addEventListener("click", () => {
        state.mode = button.dataset.navMode;
        renderTournamentList();
      });
    });

    let searchTimer = null;
    root.querySelector("[data-nav-search]").addEventListener("input", (event) => {
      state.search = event.target.value;
      clearTimeout(searchTimer);
      searchTimer = setTimeout(loadNavigation, 250);
    });

    root.querySelectorAll("[data-filter]").forEach((select) => {
      select.addEventListener("change", () => {
        state[select.dataset.filter] = select.value;
        loadNavigation();
      });
    });

    root.querySelector("[data-game-date]").addEventListener("change", (event) => {
      setGameDate(event.target.value || localDateIso());
    });
    root.querySelectorAll("[data-date-shift]").forEach((button) => {
      button.addEventListener("click", () => {
        setGameDate(shiftDate(state.gameDate, Number(button.dataset.dateShift || 0)));
      });
    });
    root.querySelector("[data-date-today]").addEventListener("click", () => {
      setGameDate(localDateIso());
    });

    window.addEventListener("keydown", (event) => {
      if (event.key === "Escape" && state.open) {
        event.preventDefault();
        setOpen(false);
      }
    });
  }

  function setOpen(value) {
    state.open = Boolean(value);
    state.root?.classList.toggle("open", state.open);
    document.body.classList.toggle("hockey-navigation-open", state.open);
    window.dispatchEvent(new CustomEvent("hockey:navigation-toggle", {
      detail: { open: state.open },
    }));
    if (state.open) {
      stopSelectedGamePolling();
      if (!state.navigation) loadNavigation();
    } else if (state.selectedGameId && !selectedGameIsFinished()) {
      scheduleSelectedGamePoll(250);
    }
  }

  function renderStaticLabels() {
    if (!state.root) return;
    state.root.querySelector("[data-nav-search]").placeholder = t("search");
    state.root.querySelector('[data-nav-mode="today"]').textContent = t("today");
    state.root.querySelector('[data-nav-mode="all"]').textContent = t("all");
    state.root.querySelector('[data-filter="league"]').closest("label").querySelector("span").textContent = t("league");
    state.root.querySelector('[data-filter="season"]').closest("label").querySelector("span").textContent = t("season");
    state.root.querySelector('[data-filter="stage"]').closest("label").querySelector("span").textContent = t("stage");
    state.root.querySelector("[data-game-date]").closest("label").querySelector("span").textContent = t("gamesForDate");
    state.root.querySelector("[data-date-today]").textContent = t("today");
    state.root.querySelector("[data-sync]")?.replaceChildren(t("sync"));
    state.root.querySelector("[data-seed-import]")?.replaceChildren(t("importSeed"));
    state.root.querySelector("[data-games-rediscover]")?.replaceChildren(t("rediscover"));
    state.root.querySelector("[data-games-diagnostics]")?.replaceChildren(t("diagnostics"));
    state.root.querySelectorAll("[data-manual-game-form]").forEach((manualForm) => {
      manualForm.querySelector("div > strong").textContent = t("manualMatchTitle");
      manualForm.querySelector("div > small").textContent = t("manualMatchHint");
      manualForm.querySelector("[data-manual-game-id]").placeholder = t("manualMatchPlaceholder");
      manualForm.querySelector("button[type=submit]").textContent = t("manualMatchButton");
    });
    state.root.querySelectorAll("[data-language]").forEach((button) => {
      button.classList.toggle("active", button.dataset.language === state.language);
    });
    renderView();
    renderTournamentList();
    renderGames();
    renderSelectedChip();
  }

  function renderView() {
    if (!state.root) return;
    const gamesView = state.view === "games";
    state.root.querySelector('[data-view="tournaments"]').classList.toggle("hidden", gamesView);
    state.root.querySelector('[data-view="games"]').classList.toggle("hidden", !gamesView);
    state.root.querySelector("[data-back]").classList.toggle("hidden", !gamesView);
    state.root.querySelector("[data-nav-title]").textContent = gamesView ? t("matches") : t("tournaments");
    if (gamesView) renderSelectedTournament();
  }

  function tournamentQueryString() {
    const params = new URLSearchParams({
      language: state.language,
      on_date: localDateIso(),
      limit: "1000",
    });
    if (state.search.trim()) params.set("search", state.search.trim());
    if (state.league) params.set("league", state.league);
    if (state.season) params.set("season", state.season);
    if (state.stage) params.set("stage", state.stage);
    return params.toString();
  }

  async function loadNavigation() {
    ensureShell();
    state.loading = true;
    renderTournamentLoading();
    try {
      state.navigation = await request(`/api/hockey/navigation?${tournamentQueryString()}`);
      renderFilters();
      renderConnection();
      renderTournamentList();
      await restoreSelection();
    } catch (error) {
      renderTournamentError(error.message);
    } finally {
      state.loading = false;
    }
  }

  function renderTournamentLoading() {
    const list = state.root?.querySelector("[data-nav-list]");
    if (!list) return;
    list.innerHTML = Array.from({ length: 6 }, () => `
      <div class="hockey-nav-skeleton">
        <i></i><div><b></b><span></span></div>
      </div>
    `).join("");
  }

  function renderTournamentError(message) {
    const list = state.root?.querySelector("[data-nav-list]");
    if (!list) return;
    list.innerHTML = `
      <div class="hockey-nav-empty error">
        <strong>Stat2TV</strong>
        <p>${escapeHtml(message)}</p>
      </div>
    `;
  }

  function renderFilters() {
    const filters = state.navigation?.filters;
    if (!filters) return;
    const allOption = `<option value="">${escapeHtml(t("allValues"))}</option>`;

    const league = state.root.querySelector('[data-filter="league"]');
    league.innerHTML = allOption + filters.leagues.map((item) => `
      <option value="${escapeHtml(item.value)}">${escapeHtml(item.label)}</option>
    `).join("");
    league.value = state.league;

    const season = state.root.querySelector('[data-filter="season"]');
    season.innerHTML = allOption + filters.seasons.map((item) => `
      <option value="${escapeHtml(item)}">${escapeHtml(item)}</option>
    `).join("");
    season.value = state.season;

    const stage = state.root.querySelector('[data-filter="stage"]');
    stage.innerHTML = allOption + filters.stages.map((item) => `
      <option value="${escapeHtml(item.value)}">${escapeHtml(item.label)}</option>
    `).join("");
    stage.value = state.stage;
  }

  function renderConnection() {
    if (!state.isAdmin) return;
    const meta = state.navigation?.meta || {};
    const bar = state.root?.querySelector("[data-connection-bar]");
    if (!bar) return;
    const configured = Boolean(meta.credentials_configured);
    bar.classList.toggle("connected", configured);
    bar.querySelector("strong").textContent = configured
      ? (state.language === "en" ? "Stat2TV configured" : "Stat2TV настроен")
      : (state.language === "en" ? "Stat2TV credentials missing" : "Stat2TV: нет данных авторизации");
    const last = meta.last_sync;
    bar.dataset.tooltip = last?.source === "stat2tv" && last?.status === "success"
      ? `${t("sourceApi")} · ${last.item_count || 0} · ${t("connections")}`
      : `${t("connections")} · ${t("adminOnly")}`;
  }

  function tournamentListForMode() {
    const nav = state.navigation || {};
    if (state.mode === "all") return nav.items || [];
    return (nav.today?.length ? nav.today : nav.recommended) || [];
  }

  function groupTournaments(items) {
    const groups = new Map();
    items.forEach((item) => {
      const key = `${item.level}|${item.league}`;
      if (!groups.has(key)) groups.set(key, { level: item.level, label: item.league, items: [] });
      groups.get(key).items.push(item);
    });
    return [...groups.values()];
  }

  function renderTournamentList() {
    if (!state.root || !state.navigation) return;
    state.root.querySelectorAll("[data-nav-mode]").forEach((button) => {
      button.classList.toggle("active", button.dataset.navMode === state.mode);
    });

    const meta = state.navigation.meta;
    const summary = state.root.querySelector("[data-nav-summary]");
    if (state.mode === "today" && !meta.today_count) {
      summary.innerHTML = `
        <span>${escapeHtml(t("offSeason"))}</span>
        <strong>${escapeHtml(t("fallback"))}: ${escapeHtml(meta.latest_season || "—")}</strong>
      `;
    } else {
      summary.innerHTML = `
        <span>${escapeHtml(state.mode === "today" ? t("today") : t("all"))}</span>
        <strong>${state.mode === "today" ? meta.today_count : meta.shown}</strong>
      `;
    }

    const items = tournamentListForMode();
    const list = state.root.querySelector("[data-nav-list]");
    if (!items.length) {
      list.innerHTML = `
        <div class="hockey-nav-empty">
          <strong>${escapeHtml(t("noItems"))}</strong>
          <p>${escapeHtml(state.mode === "today" ? t("noToday") : "")}</p>
        </div>
      `;
      return;
    }

    list.innerHTML = groupTournaments(items).map((group) => `
      <section class="hockey-league-group">
        <header>
          <span>${escapeHtml(group.level.toUpperCase())}</span>
          <strong>${escapeHtml(group.label)}</strong>
          <i>${group.items.length}</i>
        </header>
        <div>${group.items.map(tournamentCard).join("")}</div>
      </section>
    `).join("");

    list.querySelectorAll("[data-tournament-id]").forEach((button) => {
      button.addEventListener("click", () => openTournament(button.dataset.tournamentId));
    });
  }

  function tournamentCard(item) {
    const selected = String(item.id) === String(state.selectedId);
    const dates = item.start_date && item.end_date
      ? `${item.start_date} — ${item.end_date}`
      : item.start_date || item.end_date || "—";
    return `
      <button type="button"
        class="hockey-tournament-card ${selected ? "selected" : ""}"
        data-tournament-id="${escapeHtml(item.id)}">
        <span class="hockey-tournament-stage">${escapeHtml(item.stage)}</span>
        <strong>${escapeHtml(item.name || item.full_name)}</strong>
        <small>
          <b>${escapeHtml(item.season || "—")}</b>
          <span>${escapeHtml(dates)}</span>
        </small>
        <i>›</i>
      </button>
    `;
  }

  async function restoreSelection() {
    if (!state.selectedId) return;
    try {
      state.selectedTournament = await request(
        `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}?language=${state.language}`
      );
      applyTournamentData();
      renderSelectedChip();
      await loadTournamentStandings({ sync: false });
      await loadTournamentStatistics({ sync: false });
    } catch (_) {}
    if (state.selectedGameId) {
      try {
        const loaded = await request(
          `/api/hockey/games/${encodeURIComponent(state.selectedGameId)}/details?language=${state.language}`
        );
        const restoredTournamentId = String(loaded.game?.tournament_external_id || "");
        if (restoredTournamentId && restoredTournamentId !== String(state.selectedId || "")) {
          state.selectedId = restoredTournamentId;
          localStorage.setItem(storage.tournament, state.selectedId);
          try {
            state.selectedTournament = await request(
              `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}?language=${state.language}`
            );
          } catch (_) {
            state.selectedTournament = null;
          }
          state.standings = null;
          state.tournamentStatistics = null;
          state.games = null;
          state.view = "games";
          applyTournamentData();
        }
        state.selectedGame = loaded.game;
        state.selectedMatchDetails = loaded.details || null;
        applyGameData();
        await loadTournamentStandings({ sync: false });
        renderSelectedChip();
        startSelectedGamePolling();
      } catch (_) {
        try {
          state.selectedGame = await request(
            `/api/hockey/games/${encodeURIComponent(state.selectedGameId)}?language=${state.language}`
          );
          state.selectedMatchDetails = null;
          applyGameData();
          await loadTournamentStandings({ sync: false });
          renderSelectedChip();
          startSelectedGamePolling();
        } catch (_) {}
      }
    }
  }

  async function reloadSelectedGameDetails() {
    if (!state.selectedGameId) return;
    try {
      const loaded = await request(
        `/api/hockey/games/${encodeURIComponent(state.selectedGameId)}/details?language=${state.language}`
      );
      state.selectedGame = loaded.game;
      state.selectedMatchDetails = loaded.details || null;
      applyGameData();
      renderSelectedChip();
    } catch (_) {}
  }

  async function openTournament(id) {
    try {
      if (String(id) !== String(state.selectedId)) {
        stopSelectedGamePolling();
        state.selectedGame = null;
        state.selectedMatchDetails = null;
        state.standings = null;
        state.tournamentStatistics = null;
        state.selectedGameId = "";
        localStorage.removeItem(storage.game);
        const oldToken = localStorage.getItem(storage.session);
        if (oldToken) {
          request(`/api/hockey/sessions/${encodeURIComponent(oldToken)}`, {
            method: "DELETE",
          }).catch(() => {});
          localStorage.removeItem(storage.session);
        }
      }
      state.selectedTournament = await request(
        `/api/hockey/tournaments/${encodeURIComponent(id)}?language=${state.language}`
      );
      state.selectedId = String(id);
      localStorage.setItem(storage.tournament, state.selectedId);
      state.games = null;
      state.teamSchedule = null;
      state.teamScheduleTournamentId = "";
      state.view = "games";
      applyTournamentData();
      renderSelectedChip();
      renderView();
      await loadGames({ autoSync: true });
      await loadTournamentStandings({ sync: true });
      await loadTournamentStatistics({ sync: true });
      await loadStatLabels();
    } catch (error) {
      notify(error.message, true);
    }
  }

  function schedulePayload() {
    const current = state.games && typeof state.games === "object"
      ? state.games
      : { meta: {}, items: [] };
    return {
      ...current,
      meta: {
        ...(current.meta || {}),
        date: state.gameDate,
        tournament_external_id: state.selectedId || "",
        count: Array.isArray(current.items) ? current.items.length : 0,
      },
      items: Array.isArray(current.items) ? current.items : [],
      selected_date: state.gameDate,
    };
  }

  function applyGamesData() {
    const schedule = schedulePayload();
    window.UIBuilderRuntime?.patchData?.({ hockey: { schedule } });
    window.dispatchEvent(new CustomEvent("hockey:schedule-updated", {
      detail: { schedule, language: state.language },
    }));
  }

  function applyTournamentData() {
    if (!state.selectedTournament) return;
    window.UIBuilderRuntime?.patchData?.({
      hockey: {
        selected_tournament: state.selectedTournament,
        schedule: schedulePayload(),
        team_schedule: state.teamSchedule || { meta: {}, items: [] },
        standings: state.standings,
        tournament_statistics: state.tournamentStatistics,
        stat_labels: state.statLabels,
        language: { display: state.language, available: ["ru", "en"] },
      },
    });
    window.dispatchEvent(new CustomEvent("hockey:tournament-selected", {
      detail: { tournament: state.selectedTournament, language: state.language },
    }));
  }

  function standingsUrl({ sync = false } = {}) {
    const params = new URLSearchParams({ language: state.language });
    if (state.selectedGameId) params.set("game_id", state.selectedGameId);
    return `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}/standings`
      + `${sync ? "/sync" : ""}?${params.toString()}`;
  }

  function applyStandingsData() {
    window.UIBuilderRuntime?.patchData?.({
      hockey: { standings: state.standings },
    });
  }

  async function loadTournamentStandings({ sync = false } = {}) {
    if (!state.selectedId) return;
    try {
      const loaded = await request(
        standingsUrl({ sync }),
        sync ? { method: "POST", timeoutMs: 30000 } : {}
      );
      state.standings = loaded.standings || loaded;
      applyStandingsData();
    } catch (error) {
      if (sync) {
        try {
          const cached = await request(standingsUrl({ sync: false }));
          state.standings = cached.standings || cached;
          applyStandingsData();
        } catch (_) {}
      }
    }
  }

  function tournamentStatisticsUrl({ sync = false, force = false } = {}) {
    const params = new URLSearchParams({ language: state.language });
    if (force) params.set("force", "true");
    return `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}/statistics`
      + `${sync ? "/sync" : ""}?${params.toString()}`;
  }

  function applyStatLabelsData() {
    window.UIBuilderRuntime?.patchData?.({
      hockey: { stat_labels: state.statLabels },
    });
  }

  async function loadStatLabels({ force = false } = {}) {
    if (!state.selectedId) return;
    try {
      const suffix = force ? "?force=true" : "";
      state.statLabels = await request(
        `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}/navigation-labels${suffix}`
      );
      applyStatLabelsData();
    } catch (_) {
      state.statLabels = null;
      applyStatLabelsData();
    }
  }

  function applyTournamentStatisticsData() {
    window.UIBuilderRuntime?.patchData?.({
      hockey: { tournament_statistics: state.tournamentStatistics },
    });
  }

  async function loadTournamentStatistics({ sync = false, force = false } = {}) {
    if (!state.selectedId) return;
    try {
      const loaded = await request(
        tournamentStatisticsUrl({ sync, force }),
        sync ? { method: "POST", timeoutMs: 30000 } : {}
      );
      state.tournamentStatistics = loaded.statistics || loaded;
      applyTournamentStatisticsData();
    } catch (_) {
      if (sync) {
        try {
          state.tournamentStatistics = await request(
            tournamentStatisticsUrl({ sync: false })
          );
          applyTournamentStatisticsData();
        } catch (_) {}
      }
    }
  }


  function renderSelectedTournament() {
    const host = state.root?.querySelector("[data-selected-tournament]");
    if (!host) return;
    const item = state.selectedTournament;
    if (!item) {
      host.innerHTML = "";
      return;
    }
    host.innerHTML = `
      <div>
        <span>${escapeHtml(item.league)} · ${escapeHtml(item.season || "")}</span>
        <strong>${escapeHtml(item.name || item.full_name || "")}</strong>
        <small>${escapeHtml(item.stage || "")}</small>
      </div>
      <button type="button" data-change-tournament>${escapeHtml(t("changeTournament"))}</button>
    `;
    host.querySelector("[data-change-tournament]")?.addEventListener("click", () => {
      state.view = "tournaments";
      renderView();
      renderTournamentList();
    });
  }

  function setGameDate(value) {
    state.gameDate = value || localDateIso();
    localStorage.setItem(storage.gameDate, state.gameDate);
    const input = state.root?.querySelector("[data-game-date]");
    if (input) input.value = state.gameDate;
    state.games = null;
    applyGamesData();
    loadGames({ autoSync: false });
  }

  function gamesUrl() {
    return `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}/games`
      + `?on_date=${encodeURIComponent(state.gameDate)}`
      + `&language=${encodeURIComponent(state.language)}`;
  }

  function allDatesGamesUrl() {
    return `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}/games`
      + `?on_date=${encodeURIComponent(state.gameDate)}`
      + `&language=${encodeURIComponent(state.language)}`
      + `&all_dates=true`;
  }

  function applyTeamScheduleData() {
    const current = state.teamSchedule && typeof state.teamSchedule === "object"
      ? state.teamSchedule
      : { meta: {}, items: [] };
    const teamSchedule = {
      ...current,
      meta: {
        ...(current.meta || {}),
        tournament_external_id: state.selectedId || "",
        count: Array.isArray(current.items) ? current.items.length : 0,
      },
      items: Array.isArray(current.items) ? current.items : [],
    };
    window.UIBuilderRuntime?.patchData?.({ hockey: { team_schedule: teamSchedule } });
    window.dispatchEvent(new CustomEvent("hockey:team-schedule-updated", {
      detail: { schedule: teamSchedule, language: state.language },
    }));
  }

  async function loadTeamSchedule({ force = false } = {}) {
    if (!state.selectedId) return;
    if (
      !force
      && state.teamSchedule
      && state.teamScheduleTournamentId === String(state.selectedId)
    ) {
      applyTeamScheduleData();
      return;
    }
    try {
      state.teamSchedule = await request(allDatesGamesUrl());
      state.teamScheduleTournamentId = String(state.selectedId);
    } catch (_) {
      state.teamSchedule = state.teamSchedule || { meta: {}, items: [] };
    }
    applyTeamScheduleData();
  }

  async function loadGames({ autoSync = false } = {}) {
    if (!state.selectedId) return;
    state.loading = true;
    renderGamesLoading(t("loadingGames"));
    try {
      state.games = await request(gamesUrl());
      applyGamesData();
      renderGames();
      await loadTeamSchedule();
      const syncKey = String(state.selectedId);
      if (
        autoSync
        && state.games.meta.credentials_configured
        && !state.games.meta.schedule_loaded
        && !state.gamesAutoSyncAttempted.has(syncKey)
        && !state.scheduleSyncBusy
      ) {
        state.gamesAutoSyncAttempted.add(syncKey);
        await syncGames(false, { silentSuccess: true });
      }
    } catch (error) {
      state.games = { meta: { date: state.gameDate, tournament_external_id: state.selectedId, count: 0 }, items: [] };
      applyGamesData();
      renderGamesError(error.message);
    } finally {
      state.loading = false;
    }
  }

  function renderGamesLoading(message) {
    const list = state.root?.querySelector("[data-games-list]");
    const summary = state.root?.querySelector("[data-games-summary]");
    if (summary) summary.innerHTML = `<span>${escapeHtml(message)}</span><i class="hockey-spinner"></i>`;
    if (list) {
      list.innerHTML = Array.from({ length: 4 }, () => `
        <div class="hockey-game-skeleton">
          <span></span><b></b><b></b><i></i>
        </div>
      `).join("");
    }
  }

  function renderGamesError(message) {
    const list = state.root?.querySelector("[data-games-list]");
    const summary = state.root?.querySelector("[data-games-summary]");
    if (summary) summary.innerHTML = `<span>${escapeHtml(t("syncFailed"))}</span>`;
    if (list) {
      list.innerHTML = `
        <div class="hockey-games-empty error">
          <strong>${escapeHtml(t("syncFailed"))}</strong>
          <p>${escapeHtml(message)}</p>
          <small>${escapeHtml(state.isAdmin ? `${t("diagnostics")} · ${t("adminOnly")}` : "")}</small>
        </div>
      `;
    }
  }

  function matchOpenErrorMarkup() {
    if (!state.lastGameOpenError) return "";
    const diagnosticButton = state.isAdmin
      ? `<button type="button" class="btn" data-match-error-diagnostics>${escapeHtml(t("connections"))}</button>`
      : "";
    return `
      <div class="hockey-games-empty error hockey-match-open-error">
        <strong>${escapeHtml(state.language === "en" ? "Failed to open game" : "Не удалось открыть матч")}</strong>
        <p>${escapeHtml(state.lastGameOpenError)}</p>
        ${diagnosticButton}
      </div>
    `;
  }

  function bindMatchErrorDiagnostics(root) {
    root?.querySelector("[data-match-error-diagnostics]")?.addEventListener("click", () => {
      showConnectionDiagnostics({ deep: true });
    });
  }

  function renderGames() {
    if (!state.root || state.view !== "games") return;
    renderSelectedTournament();
    const input = state.root.querySelector("[data-game-date]");
    input.value = state.gameDate;
    const summary = state.root.querySelector("[data-games-summary]");
    const list = state.root.querySelector("[data-games-list]");
    const items = state.games?.items || [];
    const meta = state.games?.meta || {};

    summary.innerHTML = `
      <div>
        <span>${escapeHtml(formatDate(state.gameDate))}</span>
        <strong>${items.length}</strong>
      </div>
      <small class="${meta.endpoint_template ? "found" : ""}">
        ${escapeHtml(meta.endpoint_template ? t("endpointFound") : t("endpointNotFound"))}
      </small>
    `;

    if (!items.length) {
      list.innerHTML = `${matchOpenErrorMarkup()}
        <div class="hockey-games-empty">
          <strong>${escapeHtml(meta.last_synced_at ? t("noGames") : t("noGamesLoaded"))}</strong>
          <p>${escapeHtml(formatDate(state.gameDate))}</p>
          <small>${escapeHtml(
            meta.schedule_loaded
              ? t("scheduleCached")
              : meta.credentials_configured
                ? t("loadingGames")
                : state.isAdmin
                  ? t("notConfigured")
                  : t("noGamesLoaded")
          )}</small>
        </div>
      `;
      bindMatchErrorDiagnostics(list);
      return;
    }

    list.innerHTML = `${matchOpenErrorMarkup()}${items.map(gameCard).join("")}`;
    bindMatchErrorDiagnostics(list);
    list.querySelectorAll("[data-game-id]").forEach((button) => {
      button.addEventListener("click", () => openGame(button.dataset.gameId));
    });
  }

  function gameFinishLabel(game) {
    const direct = String(game?.score?.finish_label || "").trim();
    if (direct) return direct;
    const finishType = String(game?.score?.finish_type || "").toUpperCase();
    if (finishType === "SO") return t("finishShootout");
    if (finishType === "OT") return t("finishOvertime");
    return "";
  }

  function gameStatusLabel(game) {
    if (game?.is_finished || game?.status === "finished") {
      const finish = gameFinishLabel(game);
      return `${t("statusFinished")}${finish ? ` · ${finish}` : ""}`;
    }
    if (game?.status === "live") return t("statusLive");
    if (game?.status === "scheduled") return t("statusScheduled");
    return game?.status || "";
  }

function gameCard(game) {
    const selected = String(game.external_id) === String(state.selectedGameId);
    const homeName = game.home?.name || t("unknownTeams");
    const awayName = game.away?.name || t("unknownTeams");
    const showScore = (
      game.is_finished
      || game.status === "live"
      || Number(game.home?.score)
      || Number(game.away?.score)
    );
    const round = game.round?.name || "";
    const series = game.score?.series || "";
    const status = gameStatusLabel(game);

    return `
      <button type="button"
        class="hockey-game-card ${selected ? "selected" : ""} ${game.status === "live" ? "live" : ""} ${game.is_finished ? "finished" : ""}"
        data-game-id="${escapeHtml(game.external_id)}">
        <header>
          <span>${escapeHtml(game.time || "—")}</span>
          <small>
            ${game.game_number ? `#${escapeHtml(game.game_number)}` : ""}
            ${round ? ` · ${escapeHtml(round)}` : ""}
          </small>
          ${status ? `<i>${escapeHtml(status)}</i>` : ""}
        </header>
        <div class="hockey-game-team">
          <strong>${escapeHtml(homeName)}</strong>
          ${showScore ? `<b>${Number(game.home?.score || 0)}</b>` : `<b>—</b>`}
        </div>
        <div class="hockey-game-team">
          <strong>${escapeHtml(awayName)}</strong>
          ${showScore ? `<b>${Number(game.away?.score || 0)}</b>` : `<b>—</b>`}
        </div>
        <footer>
          <span>${escapeHtml(game.arena || "")}</span>
          ${series ? `<em>${escapeHtml(t("series"))}: ${escapeHtml(series)}</em>` : ""}
          <i>${selected ? "✓" : "›"}</i>
        </footer>
      </button>
    `;
  }

  async function syncGames(forceDiscovery, { silentSuccess = false } = {}) {
    if (!state.selectedId || state.scheduleSyncBusy) return;
    state.scheduleSyncBusy = true;
    const rediscover = state.root?.querySelector("[data-games-rediscover]");
    if (rediscover) rediscover.disabled = true;
    renderGamesLoading(forceDiscovery ? t("discovering") : t("loadingGames"));
    try {
      const result = await request(
        `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}/games/sync`
          + `?on_date=${encodeURIComponent(state.gameDate)}`
          + `&force_discovery=${forceDiscovery ? "true" : "false"}`,
        { method: "POST" }
      );
      if (!silentSuccess) {
        const visibleCount = Number.isFinite(Number(result.selected_date_count))
          ? Number(result.selected_date_count)
          : Number(result.count || 0);
        const fallbackLabel = result.fallback_used
          ? (state.language === "en" ? " · tournament API fallback" : " · резервный API турнира")
          : "";
        notify(`${t("gamesLoaded")}: ${visibleCount} · ${result.endpoint}${fallbackLabel}`);
      }
      state.gamesAutoSyncAttempted.add(String(state.selectedId));
      state.games = await request(gamesUrl());
      applyGamesData();
      renderGames();
      await loadTeamSchedule({ force: true });
    } catch (error) {
      state.gamesAutoSyncAttempted.delete(String(state.selectedId));
      renderGamesError(error.message);
      notify(error.message, true);
    } finally {
      state.scheduleSyncBusy = false;
      if (rediscover) rediscover.disabled = false;
    }
  }

  async function enrichOpenedGame(id, tournamentId, generation, { opening = false } = {}) {
    try {
      const loaded = await request(
        `/api/hockey/games/${encodeURIComponent(id)}/details/sync`
          + `?tournament_id=${encodeURIComponent(tournamentId)}`
          + `&language=${encodeURIComponent(state.language)}`
          + `&opening=${opening ? "true" : "false"}`,
        { method: "POST", timeoutMs: opening ? 14000 : 16000 }
      );
      if (
        generation !== state.gameOpenGeneration
        || String(state.selectedGameId || "") !== String(id)
      ) return false;
      if (loaded?.game) state.selectedGame = loaded.game;
      state.selectedMatchDetails = loaded?.details || state.selectedMatchDetails || null;
      applyGameData();
      renderSelectedChip();
      renderGames();
      if (loaded?.details_sync_failed || loaded?.degraded) {
        console.warn("[Hockey] Match opened with cached/schedule data; full details are not available yet", loaded?.warnings || []);
      }
      return Boolean(loaded?.game);
    } catch (error) {
      // Full match JSON, rosters, shots and directories are enrichment data.
      // Their absence must never undo a match already selected by the operator.
      console.warn(`[Hockey] Optional match details sync failed for ${id}:`, error);
      return false;
    }
  }

  function clearRuntimeMatchSpecificData() {
    // BUILD110: patchData() performs a deep merge. A schedule row normally has
    // no home.players/away.players fields, so without an explicit reset the
    // previous match roster survives until the new details request completes.
    window.UIBuilderRuntime?.patchData?.({
      hockey: {
        selected_game: null,
        home: null,
        away: null,
        referees: [],
        match_details: null,
        game_control: null,
        selected_penalties: { home: null, away: null },
        active_penalties: { home: null, away: null },
      },
    });
  }

  function cachedMatchHasRosters(payload) {
    if (!payload?.game || !payload?.details?.loaded) return false;
    const home = Array.isArray(payload.game?.home?.players) ? payload.game.home.players.length : 0;
    const away = Array.isArray(payload.game?.away?.players) ? payload.game.away.players.length : 0;
    return (home + away) > 0;
  }

  async function openGame(id, { manual = false } = {}) {
    if (state.gameOpenBusy) return;
    state.lastAttemptedGameId = String(id || "");
    state.lastGameOpenError = "";
    state.gameOpenBusy = true;
    stopSelectedGamePolling();
    const generation = ++state.gameOpenGeneration;
    const loadingGame = matchLabelById(id) || `${t("gameNumber")} ID ${id}`;
    setMatchLoading(true, { game: loadingGame, stage: t("loadingMatchDetails") });
    try {
      // Match selection itself depends only on the local/cached schedule row.
      // Full Stat2TV match JSON is optional enrichment and is loaded later.
      const scheduleGame = gameById(id);
      if (manual) {
        // Manual ID entry still performs global tournament discovery because the
        // game may not exist in the local schedule yet.
        const discovered = await request(
          `/api/hockey/games/manual/${encodeURIComponent(id)}?language=${encodeURIComponent(state.language)}`,
          { method: "POST", timeoutMs: 60000 }
        );
        state.selectedGame = discovered.game;
        state.selectedMatchDetails = discovered.details || null;
      } else if (scheduleGame) {
        // BUILD109: the card the operator clicked is already a complete local
        // schedule snapshot. Opening the workspace must be immediate; cached
        // details/full match data are optional enrichment and are loaded below.
        state.selectedGame = scheduleGame;
        state.selectedMatchDetails = null;
      } else {
        // Defensive fallback for a stale DOM/list state. This path is local DB
        // only and is not used for the normal visible match-card click.
        state.selectedGame = await request(
          `/api/hockey/games/${encodeURIComponent(id)}?language=${encodeURIComponent(state.language)}`,
          { timeoutMs: 5000 }
        );
        state.selectedMatchDetails = null;
      }

      const actualTournamentId = String(
        state.selectedGame?.tournament_external_id || state.selectedId || ""
      ).trim();
      if (!actualTournamentId) throw new Error("Не удалось определить турнир выбранного матча");

      if (actualTournamentId !== String(state.selectedId || "")) {
        state.selectedId = actualTournamentId;
        localStorage.setItem(storage.tournament, state.selectedId);
        try {
          state.selectedTournament = await request(
            `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}?language=${encodeURIComponent(state.language)}`,
            { timeoutMs: 5000 }
          );
        } catch (_) {
          state.selectedTournament = state.selectedTournament || null;
        }
        state.standings = null;
        state.tournamentStatistics = null;
        state.games = null;
        state.teamSchedule = null;
        state.teamScheduleTournamentId = "";
        applyTournamentData();
      }

      if (manual && state.selectedGame?.tournament_external_id) {
        state.selectedId = String(state.selectedGame.tournament_external_id);
        localStorage.setItem(storage.tournament, state.selectedId);
      }

      // Read only the selected game from the local DB while the session is being
      // created. This never contacts Stat2TV and lets a previously loaded match
      // restore its own roster immediately, without exposing another game's data.
      const cachedDetailsPromise = manual
        ? Promise.resolve(null)
        : request(
            `/api/hockey/games/${encodeURIComponent(id)}/details?language=${encodeURIComponent(state.language)}`,
            { timeoutMs: 2000 }
          ).catch(() => null);

      updateMatchLoading(t("loadingMatchSession"));
      const previousToken = localStorage.getItem(storage.session);
      if (previousToken) {
        try {
          const oldSession = await request(`/api/hockey/sessions/${encodeURIComponent(previousToken)}`);
          if (String(oldSession.game_external_id || "") !== String(id)) {
            await request(`/api/hockey/sessions/${encodeURIComponent(previousToken)}`, { method: "DELETE" });
            localStorage.removeItem(storage.session);
          }
        } catch (_) {
          localStorage.removeItem(storage.session);
        }
      }

      let session = null;
      const selectedDeviceId = currentAgentDeviceId();
      const sessionPayload = {
        tournament_external_id: state.selectedId,
        game_external_id: String(id),
        display_language: state.language,
        vmix_language: state.language,
        vmix_device_id: selectedDeviceId,
        replace_existing: true,
      };
      try {
        session = await request("/api/hockey/sessions", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(sessionPayload),
        });
      } catch (sessionError) {
        // Retry the operator session without Agent if the browser carries a stale device.
        if (selectedDeviceId) {
          localStorage.removeItem(storage.vmixDevice);
          try { window.HockeyAgentRuntime?.selectDevice?.(""); } catch (_) {}
          session = await request("/api/hockey/sessions", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ ...sessionPayload, vmix_device_id: "" }),
          });
        } else {
          throw sessionError;
        }
      }

      // Confirm that the concrete Agent actually received this new match.
      // A successful /sessions response alone is not enough: assignment may be
      // null/offline when the browser stored a stale device id.
      const initialAgentAssignment = await ensureSessionAgentAssignment(session);
      if (initialAgentAssignment && session) session.agent_assignment = initialAgentAssignment;

      state.lastGameOpenError = "";
      state.selectedGameId = String(id);
      localStorage.setItem(storage.game, state.selectedGameId);
      if (session?.token) localStorage.setItem(storage.session, session.token);
      else localStorage.removeItem(storage.session);

      const selectedGameDate = String(state.selectedGame?.date || "").trim();
      if (selectedGameDate) {
        state.gameDate = selectedGameDate;
        localStorage.setItem(storage.gameDate, state.gameDate);
      }

      // Prefer cached details for this exact game. The Runtime was explicitly
      // cleared above, so even a missing cache can only show an empty new match,
      // never the previous match roster.
      // The new session is confirmed. From this point the old match-specific
      // Runtime payload must not survive even for a single render.
      clearRuntimeMatchSpecificData();

      const cachedDetails = await cachedDetailsPromise;
      const cachedRostersReady = cachedMatchHasRosters(cachedDetails);
      if (cachedDetails?.game) {
        state.selectedGame = cachedDetails.game;
        state.selectedMatchDetails = cachedDetails.details || null;
      }

      applyGameData(session);
      renderSelectedChip();
      setOpen(false);

      // For a first-time match, keep the loading overlay until the critical
      // roster card has arrived. Mapping and optional shots remain non-blocking.
      // If this game's own cached roster already exists, let the operator work
      // immediately and refresh it in the background.
      if (!manual && !cachedRostersReady) {
        updateMatchLoading(t("loadingMatchDetails"));
        await enrichOpenedGame(String(id), state.selectedId, generation, { opening: true });
        if (
          generation === state.gameOpenGeneration
          && String(state.selectedGameId || "") === String(id)
        ) {
          startSelectedGamePolling();
          void refreshSessionAgentMapping(session).then((mapping) => {
            if (mapping?.ok === false && mapping?.reason === "agent_match_not_delivered") {
              notify("Матч открыт, но Agent не получил новый матч", true);
            }
          }).catch((error) => console.warn("[Hockey] Mapping refresh after roster failed:", error));
        }
      } else if (!manual) {
        // This exact match already has its own cached roster, so vMix can be
        // refreshed immediately without waiting for the optional network sync.
        void refreshSessionAgentMapping(session).then((mapping) => {
          if (mapping?.ok === false && mapping?.reason === "agent_match_not_delivered") {
            notify("Матч открыт, но Agent не получил новый матч", true);
          }
        }).catch((error) => console.warn("[Hockey] Mapping refresh from cached roster failed:", error));
        void enrichOpenedGame(String(id), state.selectedId, generation, { opening: true })
          .finally(() => {
            if (
              generation === state.gameOpenGeneration
              && String(state.selectedGameId || "") === String(id)
            ) {
              startSelectedGamePolling();
            }
          });
      } else {
        startSelectedGamePolling();
        void refreshSessionAgentMapping(session).catch((error) => console.warn("[Hockey] Mapping refresh for manual match failed:", error));
      }

      updateMatchLoading(t("loadingMatchFinishing"));
      loadTournamentStandings({ sync: false }).catch(() => {});
      if (selectedGameDate) loadGames({ autoSync: false }).catch(() => {});

      const playersCount = (state.selectedGame?.home?.players?.length || 0)
        + (state.selectedGame?.away?.players?.length || 0);
      const refereesCount = state.selectedGame?.referees?.length || 0;
      const loadedText = state.selectedMatchDetails?.loaded
        ? ` · ${playersCount} ${state.language === "en" ? "players" : "игроков"}`
          + ` · ${refereesCount} ${state.language === "en" ? "officials" : "судей"}`
        : "";
      const homeName = state.selectedGame?.home?.name || "—";
      const awayName = state.selectedGame?.away?.name || "—";
      notify(`${t("gameOpened")}: ${homeName} — ${awayName}${loadedText}`);
      if (manual) state.root?.querySelectorAll("[data-manual-game-id]").forEach((manualInput) => { manualInput.value = ""; });
    } catch (error) {
      state.lastGameOpenError = String(error?.message || error || "Неизвестная ошибка");
      if (state.view === "games") renderGames();
      notify(state.lastGameOpenError, true);
    } finally {
      setMatchLoading(false);
      state.gameOpenBusy = false;
    }
  }

  function applyGameData(session = null) {
    if (!state.selectedGame) return;
    const token = session?.token || localStorage.getItem(storage.session) || "";
    window.UIBuilderRuntime?.patchData?.({
      hockey: {
        selected_game: state.selectedGame,
        schedule: schedulePayload(),
        team_schedule: state.teamSchedule || { meta: {}, items: [] },
        home: state.selectedGame.home || { players: [] },
        away: state.selectedGame.away || { players: [] },
        referees: state.selectedGame.referees || [],
        match_details: state.selectedMatchDetails || null,
        standings: state.standings,
        tournament_statistics: state.tournamentStatistics,
        stat_labels: state.statLabels,
        operator_session: session || (token ? { token } : null),
      },
    });
    window.dispatchEvent(new CustomEvent("hockey:game-selected", {
      detail: {
        game: state.selectedGame,
        details: state.selectedMatchDetails,
        session: session || (token ? { token } : null),
        language: state.language,
      },
    }));
  }

function renderSelectedChip() {
  if (!state.selectedChip) return;
  state.selectedChip.classList.remove("hidden", "placeholder", "polling", "finished");

  if (state.selectedGame) {
    const finished = Boolean(state.selectedGame.is_finished);
    state.selectedChip.classList.toggle("finished", finished);
    state.selectedChip.classList.toggle("polling", !finished);
    state.selectedChip.dataset.tooltip = finished
      ? t("updateStopped")
      : t("liveUpdate");
    state.selectedChip.innerHTML = `
      <span>
        ${finished ? "✓" : "<i></i>"}
        ${escapeHtml(state.selectedGame.time || t("matches"))}
      </span>
      <strong>${escapeHtml(state.selectedGame.home.name)} — ${escapeHtml(state.selectedGame.away.name)}</strong>
    `;
    return;
  }

  if (state.selectedTournament) {
    state.selectedChip.dataset.tooltip = t("selectedTournament");
    state.selectedChip.innerHTML = `
      <span>${escapeHtml(state.selectedTournament.league)}</span>
      <strong>${escapeHtml(state.selectedTournament.name)}</strong>
    `;
    return;
  }

  state.selectedChip.classList.add("placeholder");
  state.selectedChip.dataset.tooltip = t("chooseTournament");
  state.selectedChip.innerHTML = `
    <span>STAT2TV</span>
    <strong>${escapeHtml(t("chooseTournament"))}</strong>
  `;
}

function stopSelectedGamePolling() {
  if (state.gamePollTimer) {
    clearTimeout(state.gamePollTimer);
    state.gamePollTimer = null;
  }
  state.gamePollBusy = false;
}

function selectedGameIsFinished() {
  return Boolean(state.selectedGame?.is_finished);
}

function scheduleSelectedGamePoll(delay = 1000) {
  stopSelectedGamePolling();
  if (!state.selectedGameId || selectedGameIsFinished()) {
    renderSelectedChip();
    return;
  }
  state.gamePollTimer = setTimeout(pollSelectedGame, Math.max(250, delay));
}

function startSelectedGamePolling() {
  state.gamePollErrorCount = 0;
  state.gamePollStoppedGameId = "";
  if (selectedGameIsFinished()) {
    stopSelectedGamePolling();
    state.gamePollStoppedGameId = state.selectedGameId;
    renderSelectedChip();
    return;
  }
  scheduleSelectedGamePoll(1000);
}

async function pollSelectedGame() {
  if (
    state.gamePollBusy
    || !state.selectedGameId
    || !state.selectedId
    || state.open
    || selectedGameIsFinished()
  ) {
    scheduleSelectedGamePoll(1000);
    return;
  }

  if (document.hidden) {
    scheduleSelectedGamePoll(1000);
    return;
  }

  state.gamePollBusy = true;
  try {
    const live = await request(
      `/api/hockey/games/${encodeURIComponent(state.selectedGameId)}/live`
        + `?tournament_id=${encodeURIComponent(state.selectedId)}`
        + `&language=${encodeURIComponent(state.language)}`,
      { method: "POST", timeoutMs: 8000 }
    );
    const updated = live.game;
    const previous = state.selectedGame || {};
    if (live.changed && updated) {
      state.selectedGame = updated;
      state.selectedMatchDetails = live.details || state.selectedMatchDetails;
      const sections = Array.isArray(live.changed_sections) && live.changed_sections.length
        ? `: ${live.changed_sections.join(", ")}`
        : "";
      notify(`${t("liveDataUpdated")}${sections}`);
    } else {
      state.selectedGame = {
        ...previous,
        ...(updated || {}),
        home: { ...(previous.home || {}), ...(updated?.home || {}), players: previous.home?.players || [] },
        away: { ...(previous.away || {}), ...(updated?.away || {}), players: previous.away?.players || [] },
        referees: previous.referees || [],
      };
    }
    state.gamePollErrorCount = 0;
    applyGameData();
    renderSelectedChip();

    if (state.games?.items?.length) {
      const index = state.games.items.findIndex(
        (item) => String(item.external_id) === String(updated.external_id)
      );
      if (index >= 0) {
        state.games.items[index] = updated;
        renderGames();
      }
    }

    if (updated.is_finished) {
      stopSelectedGamePolling();
      state.gamePollStoppedGameId = updated.external_id;
      renderSelectedChip();
      return;
    }

    scheduleSelectedGamePoll(Number(updated.refresh_interval_ms || 1000));
  } catch (error) {
    state.gamePollErrorCount += 1;
    const backoff = Math.min(10000, 1000 * Math.max(2, state.gamePollErrorCount));
    scheduleSelectedGamePoll(backoff);
  } finally {
    state.gamePollBusy = false;
  }
}

document.addEventListener("visibilitychange", () => {
  if (
    !document.hidden
    && state.selectedGameId
    && !selectedGameIsFinished()
  ) {
    scheduleSelectedGamePoll(150);
  }
});

  function diagnosticStatus(ok) {
    return `<span class="hockey-diagnostic-status ${ok ? "success" : "error"}">${ok ? "● OK" : "● ERROR"}</span>`;
  }

  function diagnosticMessage(value) {
    if (!value) return "—";
    if (value.error) return String(value.error);
    if (value.url) return String(value.url);
    return "—";
  }

  async function showConnectionDiagnostics({ deep = false } = {}) {
    if (!state.isAdmin) return;
    const tournamentId = String(state.selectedId || "").trim();
    const gameId = String(state.selectedGameId || state.lastAttemptedGameId || "").trim();
    const query = new URLSearchParams({
      tournament_id: tournamentId,
      game_id: gameId,
      language: state.language,
      deep: deep ? "true" : "false",
    });

    let modal = document.querySelector(".hockey-connection-diagnostics-modal");
    if (!modal) {
      modal = document.createElement("div");
      modal.className = "hockey-diagnostics-modal hockey-connection-diagnostics-modal";
      modal.innerHTML = `
        <div class="hockey-settings-backdrop"></div>
        <div class="hockey-diagnostics-card hockey-connection-diagnostics-card">
          <header>
            <div><small>ADMIN / HEALTH</small><strong>${escapeHtml(t("connections"))}</strong></div>
            <button type="button" data-close>×</button>
          </header>
          <div class="hockey-diagnostics-content" data-connection-diagnostics-content>
            <div class="hockey-games-empty"><strong>…</strong></div>
          </div>
          <footer class="hockey-connection-diagnostics-actions">
            <button type="button" class="btn" data-refresh>${escapeHtml(t("checkAgain"))}</button>
            <button type="button" class="btn btn-accent" data-deep ${gameId && tournamentId ? "" : "disabled"}>${escapeHtml(t("deepMatchTest"))}</button>
          </footer>
        </div>
      `;
      document.body.appendChild(modal);
      const close = () => modal.remove();
      modal.querySelector("[data-close]")?.addEventListener("click", close);
      modal.querySelector(".hockey-settings-backdrop")?.addEventListener("click", close);
      modal.querySelector("[data-refresh]")?.addEventListener("click", () => showConnectionDiagnostics({ deep: false }));
      modal.querySelector("[data-deep]")?.addEventListener("click", () => showConnectionDiagnostics({ deep: true }));
    }

    const content = modal.querySelector("[data-connection-diagnostics-content]");
    const deepButton = modal.querySelector("[data-deep]");
    if (deepButton) deepButton.disabled = !(gameId && tournamentId);
    if (content) content.innerHTML = `<div class="hockey-games-empty"><strong>${escapeHtml(deep ? t("deepMatchTest") : t("checkAgain"))}…</strong></div>`;

    try {
      const payload = await request(`/api/hockey/admin/connection-diagnostics?${query.toString()}`, { timeoutMs: deep ? 26000 : 16000 });
      const checks = payload.checks || {};
      const database = checks.database || {};
      const stat2tv = checks.stat2tv || {};
      const agents = checks.agents || {};
      const settings = checks.settings || {};
      const match = checks.match || null;
      const tournament = checks.tournament || null;
      const selection = payload.required_for_match_selection || {};
      const remote = payload.required_for_remote_sync || {};
      const agentDevices = Array.isArray(agents.devices) ? agents.devices : [];
      const warningItems = Array.isArray(match?.warnings) ? match.warnings : [];

      content.innerHTML = `
        <div class="hockey-diagnostic-summary-grid">
          <div class="hockey-diagnostic-summary ${selection.ok ? "success" : "error"}">
            <small>${escapeHtml(t("matchSelectionReady"))}</small>
            <strong>${selection.ok ? "OK" : "ERROR"}</strong>
            <span>${escapeHtml(state.language === "en" ? "Depends on PostgreSQL" : "Зависит от PostgreSQL")}</span>
          </div>
          <div class="hockey-diagnostic-summary ${remote.ok ? "success" : "error"}">
            <small>${escapeHtml(t("remoteSyncReady"))}</small>
            <strong>${remote.ok ? "OK" : "ERROR"}</strong>
            <span>${escapeHtml(state.language === "en" ? "PostgreSQL + Stat2TV" : "PostgreSQL + Stat2TV")}</span>
          </div>
        </div>
        <section class="hockey-diagnostic-section">
          <header><strong>${escapeHtml(t("database"))}</strong>${diagnosticStatus(Boolean(database.ok))}</header>
          <dl>
            <dt>Latency</dt><dd>${escapeHtml(database.latency_ms ?? "—")} ms</dd>
            <dt>Host</dt><dd>${escapeHtml(database.target?.host || "—")}</dd>
            <dt>Database</dt><dd>${escapeHtml(database.target?.database || "—")}</dd>
            <dt>Error</dt><dd>${escapeHtml(database.error || "—")}</dd>
          </dl>
        </section>
        <section class="hockey-diagnostic-section">
          <header><strong>${escapeHtml(t("stat2tv"))}</strong>${diagnosticStatus(Boolean(stat2tv.ok))}</header>
          <dl>
            <dt>Configured</dt><dd>${settings.credentials_configured ? "YES" : "NO"}</dd>
            <dt>Credentials source</dt><dd>${escapeHtml(settings.credentials_source || "missing")}</dd>
            <dt>Login</dt><dd>${escapeHtml(settings.username_masked || "—")}</dd>
            <dt>Base URL</dt><dd>${escapeHtml(settings.base_url || "—")}</dd>
            <dt>HTTP</dt><dd>${escapeHtml(stat2tv.status_code ?? "—")}</dd>
            <dt>Items</dt><dd>${escapeHtml(stat2tv.items_found ?? "—")}</dd>
            <dt>Latency</dt><dd>${escapeHtml(stat2tv.latency_ms ?? "—")} ms</dd>
            <dt>Error</dt><dd>${escapeHtml(diagnosticMessage(stat2tv))}</dd>
          </dl>
        </section>
        <section class="hockey-diagnostic-section">
          <header><strong>${escapeHtml(t("agentVmix"))}</strong>${diagnosticStatus(Boolean(agents.ok))}</header>
          <p class="hockey-diagnostic-note">${escapeHtml(t("optionalConnection"))}</p>
          <dl>
            <dt>Agents online</dt><dd>${escapeHtml(agents.online ?? 0)}</dd>
            <dt>vMix connected</dt><dd>${escapeHtml(agents.vmix_connected ?? 0)}</dd>
            <dt>Error</dt><dd>${escapeHtml(agents.error || "—")}</dd>
          </dl>
          ${agentDevices.length ? `<div class="hockey-diagnostic-devices">${agentDevices.map((item) => `
            <div><strong>${escapeHtml(item.name || item.device_id || "Agent")}</strong><span>${item.online ? "ONLINE" : "OFFLINE"} · vMix ${item.vmix_connected ? "OK" : "OFF"}${item.owner ? ` · ${escapeHtml(item.owner)}` : ""}</span></div>
          `).join("")}</div>` : ""}
        </section>
        ${tournament ? `<section class="hockey-diagnostic-section">
          <header><strong>${escapeHtml(state.language === "en" ? "Tournament" : "Турнир")} ${escapeHtml(tournament.tournament_id || "")}</strong>${diagnosticStatus(Boolean(tournament.ok))}</header>
          <dl><dt>Local</dt><dd>${tournament.local_found ? "FOUND" : "NOT FOUND"}</dd></dl>
        </section>` : ""}
        ${match ? `<section class="hockey-diagnostic-section">
          <header><strong>${escapeHtml(t("selectedMatchCheck"))} ${escapeHtml(match.game_id || "")}</strong>${diagnosticStatus(Boolean(match.ok))}</header>
          <dl>
            <dt>Local</dt><dd>${match.local_found ? "FOUND" : "NOT FOUND"}</dd>
            <dt>Deep</dt><dd>${match.deep_requested ? "YES" : "NO"}</dd>
            <dt>Details</dt><dd>${match.details_loaded ? "LOADED" : (match.deep_requested ? "NOT LOADED" : "—")}</dd>
            <dt>Degraded</dt><dd>${match.degraded ? "YES" : "NO"}</dd>
            <dt>Latency</dt><dd>${escapeHtml(match.latency_ms ?? "—")} ms</dd>
            <dt>Error</dt><dd>${escapeHtml(match.error || "—")}</dd>
          </dl>
          ${warningItems.length ? `<div class="hockey-diagnostic-warnings">${warningItems.map((item) => `<p>${escapeHtml(item)}</p>`).join("")}</div>` : ""}
        </section>` : ""}
        ${state.lastGameOpenError ? `<section class="hockey-diagnostic-section error"><header><strong>${escapeHtml(state.language === "en" ? "Last open error" : "Последняя ошибка открытия")}</strong></header><p>${escapeHtml(state.lastGameOpenError)}</p></section>` : ""}
      `;
    } catch (error) {
      if (content) content.innerHTML = `<div class="hockey-games-empty error"><strong>${escapeHtml(t("diagnostics"))}</strong><p>${escapeHtml(error.message)}</p></div>`;
    }
  }

  async function showEndpointDiagnostics() {
    if (!state.selectedId) return;
    try {
      const payload = await request(
        `/api/hockey/tournaments/${encodeURIComponent(state.selectedId)}/games/diagnostics`
      );
      const latest = payload.items?.[0] || null;
      const modal = document.createElement("div");
      modal.className = "hockey-diagnostics-modal";
      modal.innerHTML = `
        <div class="hockey-settings-backdrop"></div>
        <div class="hockey-diagnostics-card">
          <header>
            <div><small>STAT2TV</small><strong>${escapeHtml(t("diagnostics"))}</strong></div>
            <button type="button" data-close>×</button>
          </header>
          <div class="hockey-diagnostics-content">
            ${latest ? `
              <dl>
                <dt>Status</dt><dd class="${latest.status === "success" ? "success" : "error"}">${escapeHtml(latest.status)}</dd>
                <dt>Template</dt><dd>${escapeHtml(latest.template || "—")}</dd>
                <dt>Endpoint</dt><dd>${escapeHtml(latest.endpoint || "—")}</dd>
                <dt>HTTP</dt><dd>${escapeHtml(latest.status_code || "—")}</dd>
                <dt>XML root</dt><dd>${escapeHtml(latest.root_tag || "—")}</dd>
                <dt>Item tag</dt><dd>${escapeHtml(latest.item_tag || "—")}</dd>
                <dt>Items</dt><dd>${escapeHtml(latest.item_count || 0)}</dd>
                <dt>Snapshot</dt><dd>${escapeHtml(latest.snapshot_path || "—")}</dd>
                <dt>Error</dt><dd>${escapeHtml(latest.error || "—")}</dd>
              </dl>
              <pre>${escapeHtml(JSON.stringify(latest.diagnostics || {}, null, 2))}</pre>
            ` : `<div class="hockey-games-empty"><strong>${escapeHtml(t("endpointNotFound"))}</strong></div>`}
          </div>
        </div>
      `;
      document.body.appendChild(modal);
      const close = () => modal.remove();
      modal.querySelector("[data-close]").addEventListener("click", close);
      modal.querySelector(".hockey-settings-backdrop").addEventListener("click", close);
    } catch (error) {
      notify(error.message, true);
    }
  }

  async function syncTournaments() {
    const button = state.root.querySelector("[data-sync]");
    button.disabled = true;
    try {
      const result = await request("/api/hockey/sync/tournaments", { method: "POST" });
      notify(`${t("syncOk")}: ${result.count}`);
      await loadNavigation();
    } catch (error) {
      notify(error.message, true);
    } finally {
      button.disabled = false;
    }
  }

  async function importSeed() {
    const button = state.root.querySelector("[data-seed-import]");
    button.disabled = true;
    try {
      const result = await request("/api/hockey/sync/tournaments/seed", { method: "POST" });
      notify(`${t("syncOk")}: ${result.count}`);
      await loadNavigation();
    } catch (error) {
      notify(error.message, true);
    } finally {
      button.disabled = false;
    }
  }

  function loadSettingsCached({ force = false } = {}) {
    const fresh = state.settings && (Date.now() - Number(state.settingsLoadedAt || 0)) < 30000;
    if (!force && fresh) return Promise.resolve(state.settings);
    if (state.settingsPromise) return state.settingsPromise;
    state.settingsPromise = request("/api/hockey/settings", { timeoutMs: 5000 })
      .then((payload) => {
        state.settings = payload || {};
        state.settingsLoadedAt = Date.now();
        return state.settings;
      })
      .finally(() => { state.settingsPromise = null; });
    return state.settingsPromise;
  }

  async function openSettings() {
    let loadingModal = null;
    if (!state.settings) {
      loadingModal = document.createElement("div");
      loadingModal.className = "hockey-settings-modal";
      loadingModal.innerHTML = `
        <div class="hockey-settings-backdrop"></div>
        <div class="hockey-settings-card">
          <header><div><small>STAT2TV</small><strong>${escapeHtml(t("settings"))}</strong></div></header>
          <div class="hockey-settings-body"><p class="wide">${state.language === "en" ? "Loading settings…" : "Загрузка настроек…"}</p></div>
        </div>`;
      document.body.appendChild(loadingModal);
    }
    try {
      state.settings = await loadSettingsCached();
    } catch (error) {
      loadingModal?.remove();
      notify(error.message, true);
      return;
    }
    loadingModal?.remove();

    const value = state.settings;
    const modal = document.createElement("div");
    modal.className = "hockey-settings-modal";
    modal.innerHTML = `
      <div class="hockey-settings-backdrop"></div>
      <form class="hockey-settings-card">
        <header>
          <div><small>STAT2TV</small><strong>${escapeHtml(t("settings"))}</strong></div>
          <button type="button" data-settings-close>×</button>
        </header>
        <div class="hockey-settings-body">
          <label class="wide">
            <span>${escapeHtml(t("apiAddress"))}</span>
            <input name="base_url" value="${escapeHtml(value.base_url || "")}">
          </label>
          <label>
            <span>${escapeHtml(t("username"))}</span>
            <input name="username" placeholder="${escapeHtml(value.username_masked || "")}" autocomplete="username">
          </label>
          <label>
            <span>${escapeHtml(t("password"))}</span>
            <input name="password" type="password" autocomplete="new-password">
          </label>
          <label>
            <span>${escapeHtml(t("authMode"))}</span>
            <select name="auth_mode">
              <option value="auto">Auto</option>
              <option value="basic">Basic</option>
              <option value="digest">Digest</option>
            </select>
          </label>
          <label>
            <span>${escapeHtml(t("displayLanguage"))}</span>
            <select name="ui_language">
              <option value="ru">Русский</option>
              <option value="en">English</option>
            </select>
          </label>
          <label>
            <span>${escapeHtml(t("fallbackLanguage"))}</span>
            <select name="fallback_language">
              <option value="ru">Русский</option>
              <option value="en">English</option>
            </select>
          </label>
          <label>
            <span>${escapeHtml(t("interval"))}</span>
            <input name="sync_interval_seconds" type="number" min="30" max="86400" value="${Number(value.sync_interval_seconds || 300)}">
          </label>
          <label>
            <span>${escapeHtml(t("cacheSeconds"))}</span>
            <input name="games_cache_seconds" type="number" min="0" max="86400" value="${Number(value.games_cache_seconds || 300)}">
          </label>

          <section class="hockey-settings-section wide">
            <strong>${state.language === "en" ? "GAME CLOCK" : "ТАЙМЕР МАТЧА"}</strong>
            <small>${state.language === "en" ? "Default duration for a new game and automatic reset when the period changes." : "Длительность по умолчанию для нового матча и автоматический сброс при смене периода."}</small>
          </section>
          <label>
            <span>${state.language === "en" ? "Regulation period, min" : "Обычный период, мин"}</span>
            <input name="timer_period_minutes" type="number" min="1" max="60" value="${Number(value.timer_period_minutes || 20)}">
          </label>
          <label>
            <span>${state.language === "en" ? "Regular-season OT, min" : "Овертайм регулярки, мин"}</span>
            <input name="timer_regular_overtime_minutes" type="number" min="1" max="60" value="${Number(value.timer_regular_overtime_minutes || 5)}">
          </label>
          <label>
            <span>${state.language === "en" ? "Playoff OT, min" : "Овертайм плей-офф, мин"}</span>
            <input name="timer_playoff_overtime_minutes" type="number" min="1" max="60" value="${Number(value.timer_playoff_overtime_minutes || 20)}">
          </label>
          <label class="check">
            <input name="timer_reset_on_period_change" type="checkbox" ${value.timer_reset_on_period_change !== false ? "checked" : ""}>
            <span>${state.language === "en" ? "Reset clock when period changes" : "Сбрасывать таймер при смене периода"}</span>
          </label>

          <section class="hockey-settings-section wide">
            <strong>${state.language === "en" ? "PENALTY / STRENGTH GRAPHIC" : "УДАЛЕНИЯ И ЧИСЛЕННЫЕ СОСТАВЫ"}</strong>
            <small>${state.language === "en" ? "Choose the base skater count and what the advantaged team receives in the graphic." : "Настройте базовый состав и подпись, которую получает команда в большинстве."}</small>
          </section>
          <label>
            <span>${state.language === "en" ? "Regulation base" : "Основное время, базовый состав"}</span>
            <input name="strength_regulation_skaters" type="number" min="3" max="6" value="${Number(value.strength_regulation_skaters || 5)}">
          </label>
          <label>
            <span>${state.language === "en" ? "Regular OT base" : "Овертайм регулярки, базовый состав"}</span>
            <input name="strength_regular_overtime_skaters" type="number" min="3" max="6" value="${Number(value.strength_regular_overtime_skaters || 3)}">
          </label>
          <label>
            <span>${state.language === "en" ? "Playoff OT base" : "Овертайм плей-офф, базовый состав"}</span>
            <input name="strength_playoff_overtime_skaters" type="number" min="3" max="6" value="${Number(value.strength_playoff_overtime_skaters || 5)}">
          </label>
          <label>
            <span>${state.language === "en" ? "Minimum skaters" : "Минимум полевых игроков"}</span>
            <input name="strength_min_skaters" type="number" min="2" max="5" value="${Number(value.strength_min_skaters || 3)}">
          </label>
          <div class="hockey-settings-note wide">
            <strong>${state.language === "en" ? "Bilingual strength labels" : "Двуязычные подписи составов"}</strong>
            <small>${state.language === "en" ? "Configure every 5×4, 4×4, 5×3, 4×3 and overtime label in Gear → Timers and lineups." : "Подписи для 5×4, 4×4, 5×3, 4×3 и всех вариантов овертайма настраиваются в разделе Шестерёнка → Таймеры и составы."}</small>
          </div>

          <label class="wide">
            <span>${escapeHtml(t("endpointTemplate"))}</span>
            <input name="games_endpoint_template" value="${escapeHtml(value.games_endpoint_template || "")}" placeholder="games.xml?tournament={tournament_id}">
            <small>${escapeHtml(t("endpointHint"))}</small>
          </label>
          <label class="wide">
            <span>Players XML</span>
            <input name="players_endpoint_template" value="${escapeHtml(value.players_endpoint_template || "{tournament_id}/players-{tournament_id}.xml")}" placeholder="{tournament_id}/players-{tournament_id}.xml">
            <small>${state.language === "en" ? "Full season roster for the selected tournament." : "Полный список игроков текущего сезона и выбранной лиги."}</small>
          </label>
          <label class="wide">
            <span>Players countries XML</span>
            <input name="player_countries_endpoint_template" value="${escapeHtml(value.player_countries_endpoint_template || "players-countries.xml")}" placeholder="players-countries.xml">
            <small>${state.language === "en" ? "Global country directory and player-country links." : "Глобальный справочник стран и привязки игроков к странам."}</small>
          </label>
          <label class="wide">
            <span>Referees XML</span>
            <input name="referees_endpoint_template" value="${escapeHtml(value.referees_endpoint_template || "{tournament_id}/referees-{tournament_id}.xml")}" placeholder="{tournament_id}/referees-{tournament_id}.xml">
          </label>
          <label class="wide">
            <span>Shots JSON</span>
            <input name="shots_endpoint_template" value="${escapeHtml(value.shots_endpoint_template || "shots/shots-{game_id}.json")}" placeholder="shots/shots-{game_id}.json">
            <small>${state.language === "en" ? "Optional resource. It is requested only when available." : "Необязательный ресурс. При отсутствии файла используется отрицательный кэш."}</small>
          </label>
          <label class="check">
            <input name="verify_ssl" type="checkbox" ${value.verify_ssl ? "checked" : ""}>
            <span>${escapeHtml(t("verifySsl"))}</span>
          </label>
          <label class="check">
            <input name="games_auto_discovery" type="checkbox" ${value.games_auto_discovery !== false ? "checked" : ""}>
            <span>${escapeHtml(t("endpointAuto"))}</span>
          </label>
          <p class="wide">${escapeHtml(t("credentialsHint"))}</p>
        </div>
        <footer>
          <button type="button" class="btn" data-test-connection>${escapeHtml(t("test"))}</button>
          <span data-settings-status></span>
          <button type="submit" class="btn btn-accent">${escapeHtml(t("save"))}</button>
        </footer>
      </form>
    `;
    document.body.appendChild(modal);
    state.settingsModal = modal;

    const form = modal.querySelector("form");
    form.elements.auth_mode.value = value.auth_mode || "auto";
    form.elements.ui_language.value = value.ui_language || "ru";
    form.elements.fallback_language.value = value.fallback_language || "ru";

    const close = () => {
      modal.remove();
      state.settingsModal = null;
    };
    modal.querySelector("[data-settings-close]").addEventListener("click", close);
    modal.querySelector(".hockey-settings-backdrop").addEventListener("click", close);

    modal.querySelector("[data-test-connection]").addEventListener("click", async () => {
      const status = modal.querySelector("[data-settings-status]");
      status.textContent = "…";
      try {
        const result = await request("/api/hockey/connection/test", { method: "POST" });
        status.textContent = `${t("connectionOk")} · ${result.items_found}`;
        status.classList.remove("error");
      } catch (error) {
        status.textContent = error.message;
        status.classList.add("error");
      }
    });

    form.addEventListener("submit", async (event) => {
      event.preventDefault();
      const status = modal.querySelector("[data-settings-status]");
      const values = new FormData(form);
      try {
        const savedSettings = await request("/api/hockey/settings", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            base_url: values.get("base_url"),
            auth_mode: values.get("auth_mode"),
            ui_language: values.get("ui_language"),
            fallback_language: values.get("fallback_language"),
            sync_interval_seconds: Number(values.get("sync_interval_seconds") || 300),
            games_cache_seconds: Number(values.get("games_cache_seconds") || 300),
            timer_period_minutes: Number(values.get("timer_period_minutes") || 20),
            timer_regular_overtime_minutes: Number(values.get("timer_regular_overtime_minutes") || 5),
            timer_playoff_overtime_minutes: Number(values.get("timer_playoff_overtime_minutes") || 20),
            timer_reset_on_period_change: Boolean(values.get("timer_reset_on_period_change")),
            strength_regulation_skaters: Number(values.get("strength_regulation_skaters") || 5),
            strength_regular_overtime_skaters: Number(values.get("strength_regular_overtime_skaters") || 3),
            strength_playoff_overtime_skaters: Number(values.get("strength_playoff_overtime_skaters") || 5),
            strength_min_skaters: Number(values.get("strength_min_skaters") || 3),
            games_endpoint_template: values.get("games_endpoint_template"),
            players_endpoint_template: values.get("players_endpoint_template"),
            player_countries_endpoint_template: values.get("player_countries_endpoint_template"),
            referees_endpoint_template: values.get("referees_endpoint_template"),
            shots_endpoint_template: values.get("shots_endpoint_template"),
            games_auto_discovery: Boolean(values.get("games_auto_discovery")),
            verify_ssl: Boolean(values.get("verify_ssl")),
          }),
        });

        state.settings = savedSettings || state.settings || {};
        state.settingsLoadedAt = Date.now();

        const username = String(values.get("username") || "").trim();
        const password = String(values.get("password") || "");
        if (username || password) {
          await request("/api/hockey/credentials", {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              username: username || null,
              password: password || null,
            }),
          });
        }

        const previousLanguage = String(state.language || "ru").toLowerCase();
        const nextLanguage = String(values.get("ui_language") || state.language || "ru").toLowerCase();
        state.language = nextLanguage;
        localStorage.setItem(storage.language, state.language);
        window.dispatchEvent(new CustomEvent("hockey:settings-updated", {
          detail: { settings: savedSettings || {} },
        }));

        // Mapping can use virtual *_RUS/*_ENG columns. Language is a global
        // Hockey setting, while live Agents may belong to other operator accounts.
        // Therefore refresh every active broadcast Agent, not only the admin's own.
        let mappingRefresh = null;
        if (previousLanguage !== nextLanguage) {
          try {
            mappingRefresh = await request("/api/hockey/admin/vmix-mapping/apply-all-active", { method: "POST" });
          } catch (mappingError) {
            status.textContent = `${t("saved")} · vMix: ${mappingError.message}`;
            status.classList.add("error");
          }
        }
        if (!status.classList.contains("error")) {
          if (mappingRefresh && Number(mappingRefresh.total_devices || 0) > 0) {
            status.textContent = `${t("saved")} · vMix ${Number(mappingRefresh.applied_devices || 0)}/${Number(mappingRefresh.total_devices || 0)}`;
          } else {
            status.textContent = t("saved");
          }
          status.classList.remove("error");
        }
        setTimeout(close, 120);
        loadNavigation().catch((error) => console.warn("[Hockey] Navigation refresh after settings save failed:", error));
      } catch (error) {
        status.textContent = error.message;
        status.classList.add("error");
      }
    });
  }

  async function init() {
    await ensureAccountScopedRuntimeState();
    ensureShell();
    renderStaticLabels();
    // Warm the tiny local settings endpoint while navigation is loading, so the
    // gear opens immediately instead of starting its first request on click.
    void loadSettingsCached().catch(() => {});
    await loadNavigation();
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init, { once: true });
  } else {
    init();
  }

  window.HockeyTournamentMenu = {
    open: () => setOpen(true),
    close: () => setOpen(false),
    reload: loadNavigation,
    getSelectedTournamentId: () => state.selectedId,
    getSelectedGameId: () => state.selectedGameId,
    selectTournament: openTournament,
    selectGame: openGame,
    reloadGames: () => loadGames({ autoSync: false }),
  };

  function installGlobalTooltipPortal() {
    if (document.querySelector(".hockey-global-tooltip")) return;
    const tooltip = document.createElement("div");
    tooltip.className = "hockey-global-tooltip";
    document.body.appendChild(tooltip);
    let current = null;
    const hide = () => {
      current = null;
      tooltip.classList.remove("show");
      tooltip.textContent = "";
    };
    const position = () => {
      if (!current) return;
      const rect = current.getBoundingClientRect();
      const tipRect = tooltip.getBoundingClientRect();
      let left = rect.left + rect.width / 2 - tipRect.width / 2;
      left = Math.max(8, Math.min(window.innerWidth - tipRect.width - 8, left));
      let top = rect.top - tipRect.height - 10;
      if (top < 8) top = rect.bottom + 10;
      tooltip.style.left = `${left}px`;
      tooltip.style.top = `${Math.max(8, top)}px`;
    };
    const show = (target) => {
      const text = String(target?.dataset?.tooltip || target?.getAttribute?.("aria-label") || "").trim();
      if (!text) return;
      current = target;
      tooltip.textContent = text;
      tooltip.classList.add("show");
      requestAnimationFrame(position);
    };
    document.addEventListener("pointerover", (event) => {
      const target = event.target.closest?.("[data-tooltip]");
      if (target) show(target);
    });
    document.addEventListener("pointerout", (event) => {
      if (!current) return;
      const next = event.relatedTarget;
      if (next && current.contains(next)) return;
      hide();
    });
    document.addEventListener("focusin", (event) => {
      const target = event.target.closest?.("[data-tooltip]");
      if (target) show(target);
    });
    document.addEventListener("focusout", hide);
    window.addEventListener("scroll", hide, true);
    window.addEventListener("resize", hide);
  }
  installGlobalTooltipPortal();
})();
