(function () {
  const params = new URLSearchParams(location.search);
  const isProgramPage = location.pathname === "/program.html";
  const isCompanionFrame = params.get("companion") === "1";
  const isProgramLive = !isCompanionFrame && (params.get("program") === "1" || params.get("programFrame") === "1");
  const isPreviewFrame = params.get("preview") === "1";

  const routes = {
    blank: "",
    lineup: "/board.html?mode=lineup",
    running: "/board.html?mode=running",
    topbanner: "/top-banner.html",
    sidebanner: "/side-banner.html",
    results: "/board.html?mode=results",
    leaders: "/current-leaders.html",
    top5: "/results.html",
    podium: "/top3-podium.html",
    weather: "/weather.html",
    ticker: "/ticker.html",
    story: "/story-spotlight.html"
  };
  const bannerCompanionGraphics = new Set(["leaders", "story", "weather"]);
  let companionFrame = null;

  function currentGraphic() {
    if (location.pathname === "/top-banner.html") return "topbanner";
    if (location.pathname === "/side-banner.html") return "sidebanner";
    if (location.pathname === "/current-leaders.html") return "leaders";
    if (location.pathname === "/results.html") return "top5";
    if (location.pathname === "/top3-podium.html") return "podium";
    if (location.pathname === "/weather.html") return "weather";
    if (location.pathname === "/ticker.html") return "ticker";
    if (location.pathname === "/story-spotlight.html") return "story";
    if (location.pathname === "/board.html") {
      const mode = new URLSearchParams(location.search).get("mode") || "lineup";
      if (mode === "running") return "running";
      if (mode === "results") return "results";
      return "lineup";
    }
    return "blank";
  }

  function normalizeGraphic(graphic) {
    const key = String(graphic || "blank").trim().toLowerCase();
    if (key === "running") return "lineup";
    if (key === "top3" || key === "top-3") return "podium";
    return routes[key] !== undefined ? key : "blank";
  }

  function programUrl(graphic, version = Date.now(), options = {}) {
    const route = routes[graphic] || "";
    const safeVersion = encodeURIComponent(version || Date.now());
    if (!route) return `/program.html?programVersion=${safeVersion}`;
    const separator = route.includes("?") ? "&" : "?";
    const modeParam = options.companion ? "companion=1" : "program=1";
    return `${route}${separator}${modeParam}&programVersion=${safeVersion}`;
  }

  function stateKey(graphic, version) {
    return `${graphic || "blank"}:${version || ""}`;
  }

  function watchStandaloneTopBanner() {
    const graphic = currentGraphic();
    if (graphic !== "topbanner" && graphic !== "sidebanner") return;
    let fallbackTimer = null;
    let latestVisibility = null;
    let isProgramSelected = false;

    function applyDisplayState() {
      const visibleByBannerControl = !latestVisibility || latestVisibility.visible !== false;
      if (isProgramSelected || visibleByBannerControl) {
        document.body.classList.remove("program-exit");
      } else {
        document.body.classList.add("program-exit");
      }
    }

    function syncVisibility(state) {
      latestVisibility = state || null;
      applyDisplayState();
    }

    function syncProgram(state) {
      isProgramSelected = normalizeGraphic(state && state.graphic) === graphic;
      applyDisplayState();
    }

    function pollVisibility() {
      fetch(`/api/top-banner-output?t=${Date.now()}`, { cache: "no-store" })
        .then(res => res.json())
        .then(syncVisibility)
        .catch(() => {});
    }

    function pollProgram() {
      fetch(`/api/program?t=${Date.now()}`, { cache: "no-store" })
        .then(res => res.json())
        .then(syncProgram)
        .catch(() => {});
    }

    function startFallbackPolling() {
      if (fallbackTimer) return;
      pollVisibility();
      pollProgram();
      fallbackTimer = setInterval(() => {
        pollVisibility();
        pollProgram();
      }, 3000);
    }

    function stopFallbackPolling() {
      if (fallbackTimer) {
        clearInterval(fallbackTimer);
        fallbackTimer = null;
      }
    }

    if (typeof EventSource === "function") {
      try {
        // The main /events stream carries output states, so one connection covers
        // banner visibility and program selection (browsers cap connections per
        // origin at 6, and every extra stream risks starving send requests).
        const stream = typeof lrwSharedEventsStream === "function" ? lrwSharedEventsStream() : new EventSource("/events");
        stream.addEventListener("message", event => {
          try {
            const state = JSON.parse(event.data);
            if (state && state.output) {
              syncVisibility(state.output.topBanner);
              syncProgram(state.output.program);
            }
          } catch (_err) {}
        });
        stream.addEventListener("error", () => startFallbackPolling());
        stream.addEventListener("open", () => stopFallbackPolling());
        return;
      } catch (_err) {}
    }

    startFallbackPolling();
  }

  function watchStandaloneProgramGraphic() {
    const graphic = currentGraphic();
    if (graphic !== "story") return;
    let fallbackTimer = null;

    function syncStoryOutput(state) {
      document.body.classList.toggle("program-exit", state?.visible !== true);
    }

    function pollStoryOutput() {
      fetch(`/api/story-output?t=${Date.now()}`, { cache: "no-store" })
        .then(res => res.json())
        .then(syncStoryOutput)
        .catch(() => {});
    }

    function startFallbackPolling() {
      if (fallbackTimer) return;
      pollStoryOutput();
      fallbackTimer = setInterval(pollStoryOutput, 3000);
    }

    function stopFallbackPolling() {
      if (fallbackTimer) {
        clearInterval(fallbackTimer);
        fallbackTimer = null;
      }
    }

    if (typeof EventSource === "function") {
      try {
        const stream = typeof lrwSharedEventsStream === "function" ? lrwSharedEventsStream() : new EventSource("/events");
        stream.addEventListener("message", event => {
          try {
            const state = JSON.parse(event.data);
            if (state && state.output) syncStoryOutput(state.output.storyOutput);
          } catch (_err) {}
        });
        stream.addEventListener("error", () => startFallbackPolling());
        stream.addEventListener("open", () => stopFallbackPolling());
        return;
      } catch (_err) {}
    }

    startFallbackPolling();
  }

  const programChannel = window.BroadcastChannel ? new BroadcastChannel("raceGraphics.program") : null;
  let lastAppliedKey = stateKey(currentGraphic(), params.get("programVersion"));
  let pendingTarget = "";
  let pendingNavigationTimer = null;

  function currentProgramPath() {
    return `${location.pathname}${location.search}`;
  }

  function clearPendingNavigation() {
    if (pendingNavigationTimer) {
      clearTimeout(pendingNavigationTimer);
      pendingNavigationTimer = null;
    }
    pendingTarget = "";
  }

  function keepProgramVisible() {
    clearPendingNavigation();
    document.body.classList.remove("program-exit");
  }

  function showBannerCompanionGraphic(graphic, version) {
    clearPendingNavigation();
    document.body.classList.remove("program-exit");
    const target = programUrl(graphic, version, { companion: true });
    if (!companionFrame) {
      companionFrame = document.createElement("iframe");
      companionFrame.title = "Temporary program graphic";
      companionFrame.setAttribute("allowtransparency", "true");
      companionFrame.style.position = "fixed";
      companionFrame.style.inset = "0";
      companionFrame.style.width = "100vw";
      companionFrame.style.height = "100vh";
      companionFrame.style.border = "0";
      companionFrame.style.background = "transparent";
      companionFrame.style.pointerEvents = "none";
      companionFrame.style.zIndex = "20";
      companionFrame.style.opacity = "0";
      companionFrame.style.transition = "none";
      document.body.appendChild(companionFrame);
    }
    if (companionFrame.getAttribute("src") !== target) {
      // A navigating iframe paints opaque white until its document renders
      // (allowtransparency does not cover the provisional load), so the frame
      // stays invisible until its content is actually ready.
      const frame = companionFrame;
      frame.style.opacity = "0";
      frame.onload = () => {
        if (companionFrame !== frame) return;
        frame.style.opacity = "1";
      };
      frame.setAttribute("src", target);
    } else {
      companionFrame.style.opacity = "1";
    }
  }

  function hideBannerCompanionGraphic(options = {}) {
    if (!companionFrame) return;
    const frame = companionFrame;
    companionFrame = null;
    if (options.animate) {
      frame.style.transition = "opacity 220ms ease";
      frame.style.opacity = "0";
      setTimeout(() => frame.remove(), 240);
    } else {
      frame.remove();
    }
  }

  function navigateProgramTarget(target) {
    if (currentProgramPath() === target) {
      keepProgramVisible();
      return;
    }
    const current = currentGraphic();
    const shouldAnimateOut = current !== "blank";

    if (!shouldAnimateOut) {
      keepProgramVisible();
      location.replace(target);
      return;
    }

    if (pendingTarget === target) return;
    pendingTarget = target;
    if (pendingNavigationTimer) clearTimeout(pendingNavigationTimer);
    document.body.classList.add("program-exit");
    pendingNavigationTimer = setTimeout(() => {
      pendingNavigationTimer = null;
      pendingTarget = "";
      location.replace(target);
    }, 760);
  }

  function applyProgramState(state) {
    if (!state || typeof state !== "object") return;

    const graphic = normalizeGraphic(state.graphic);
    const version = String(state.version || Date.now());
    const nextKey = stateKey(graphic, version);
    if (nextKey === lastAppliedKey) {
      document.body.classList.remove("program-exit");
      return;
    }
    lastAppliedKey = nextKey;

    if (currentGraphic() === "topbanner" && bannerCompanionGraphics.has(graphic)) {
      showBannerCompanionGraphic(graphic, version);
      return;
    }

    if (currentGraphic() === "topbanner" && graphic === "blank" && companionFrame) {
      hideBannerCompanionGraphic({ animate: true });
      keepProgramVisible();
      return;
    }

    if (currentGraphic() === "topbanner" && graphic === "topbanner" && companionFrame) {
      hideBannerCompanionGraphic({ animate: true });
      keepProgramVisible();
      return;
    }

    hideBannerCompanionGraphic();
    const target = programUrl(graphic, version);
    navigateProgramTarget(target);
  }

  function checkProgram() {
    fetch(`/api/program?t=${Date.now()}`, { cache: "no-store" })
      .then(res => res.json())
      .then(applyProgramState)
      .catch(() => {});
  }

  function watchProgramOutput() {
    document.body.classList.remove("program-exit");
    let fallbackTimer = null;
    checkProgram();

    window.addEventListener("storage", event => {
      if (event.key !== "raceGraphics.programState" || !event.newValue) return;
      try {
        applyProgramState(JSON.parse(event.newValue));
      } catch (_err) {}
    });

    if (programChannel) {
      programChannel.onmessage = event => applyProgramState(event.data);
    }

    function startFallbackPolling() {
      if (fallbackTimer) return;
      checkProgram();
      fallbackTimer = setInterval(checkProgram, 3000);
    }

    function stopFallbackPolling() {
      if (fallbackTimer) {
        clearInterval(fallbackTimer);
        fallbackTimer = null;
      }
    }

    if (typeof EventSource === "function") {
      try {
        const stream = typeof lrwSharedEventsStream === "function" ? lrwSharedEventsStream() : new EventSource("/events");
        stream.addEventListener("message", event => {
          try {
            const state = JSON.parse(event.data);
            if (state && state.output) applyProgramState(state.output.program);
          } catch (_err) {}
        });
        stream.addEventListener("error", () => startFallbackPolling());
        stream.addEventListener("open", () => stopFallbackPolling());
        return;
      } catch (_err) {}
    }

    startFallbackPolling();
  }

  if (!isPreviewFrame && !isProgramPage && !isProgramLive && !isCompanionFrame) {
    watchStandaloneTopBanner();
    watchStandaloneProgramGraphic();
  }

  if (!isPreviewFrame && (isProgramPage || isProgramLive)) {
    watchProgramOutput();
    return;
  }
})();
