/*
 * language-gate.js — root language vestibule, local preference only.
 *
 * The vestibule at / is the modal-shell language-choice page. The page
 * is server-rendered visible and works with no JavaScript: the two
 * choices are ordinary <a href="/en-au/"> / <a href="/fr/"> links, and
 * the display language is fixed pre-paint by the inline head script
 * (html[data-preferred-lang]).
 *
 * This script is pure progressive enhancement:
 *   · on a fresh load with localStorage["tp-lang"] already set, skips
 *     the gate entirely and routes to the stored edition;
 *   · remembers the chosen edition in localStorage["tp-lang"] on click,
 *     so the next visit routes straight through;
 *   · dismissal (× button, Esc, scrim click) routes to the stored
 *     edition, or /en-au/ if none is stored — the visitor never lands
 *     on a blocked screen;
 *   · traps focus within the modal;
 *   · moves focus to the first choice on load.
 *
 * localStorage is used only to improve presentation. No cookies, no
 * network request, no analytics, no dependencies. Fails silently if
 * storage is unavailable.
 */
(function () {
  "use strict";

  var STORAGE_KEY = "tp-lang";
  var SKIP_ANIM_KEY = "tp-skip-hero-anim";
  var ROUTES = { en: "/en-au/", fr: "/fr/" };
  var DEFAULT_ROUTE = "/en-au/";

  function readStoredLang() {
    try {
      var v = localStorage.getItem(STORAGE_KEY);
      return (v === "en" || v === "fr") ? v : null;
    } catch (_) { return null; }
  }

  // Hero-static handoff — the hero already animates behind the
  // scrim on the gate page, so when the visitor leaves the gate
  // (either by picking a language or by dismissing) the
  // destination should paint the hero AT REST instead of
  // re-playing the reveal. We mark it once in sessionStorage; the
  // destination's bootstrap inline script reads the flag pre-paint
  // and adds html.hero-static, then clears it so the next direct
  // visit gets the full animation back.
  function markSkipHeroAnim() {
    try { sessionStorage.setItem(SKIP_ANIM_KEY, "1"); } catch (_) {}
  }

  // The vestibule never auto-routes. The visitor always sees the
  // gate and must take an action — click an edition, press Esc,
  // click outside the modal, or use the × close. Earlier we
  // skipped the gate on return visits when localStorage carried a
  // language; that felt too abrupt for a first paint, so the
  // stored value is now read only as a DEFAULT for the dismiss
  // handlers (see dismissAndRoute below).
  var overlay = document.getElementById("lang-gate");
  if (!overlay) return;

  // Shared dismissal — × button, Esc, scrim click. Routes to the stored
  // edition, or /en-au/ if none is stored. The visitor never lands on a
  // blocked screen.
  function dismissAndRoute() {
    var s = readStoredLang();
    var target = s === "fr" ? ROUTES.fr : (s === "en" ? ROUTES.en : DEFAULT_ROUTE);
    markSkipHeroAnim();
    window.location.replace(target);
  }

  // × close button.
  var closeBtn = document.getElementById("lang-close");
  if (closeBtn) {
    closeBtn.addEventListener("click", function () { dismissAndRoute(); });
  }

  // Esc key — global so the listener catches it from any focus position.
  document.addEventListener("keydown", function (event) {
    if (event.key === "Escape" || event.key === "Esc") {
      dismissAndRoute();
    }
  });

  // Scrim click — only when the target IS the scrim itself, not a
  // descendant of the shell.
  var scrim = document.getElementById("lang-gate-scrim");
  if (scrim) {
    scrim.addEventListener("click", function (event) {
      if (event.target === scrim) dismissAndRoute();
    });
  }

  var choices = overlay.querySelectorAll("a[data-lang-choice]");

  // Remember the chosen edition, then let the link navigate. When motion
  // is allowed, the scrim and the card dissolve over ~280 ms before the
  // route changes — the homepage underneath sharpens into view. With
  // prefers-reduced-motion the link navigates instantly. No-JS visitors
  // also navigate instantly: the click handler isn't installed and the
  // <a href> resolves normally.
  Array.prototype.forEach.call(choices, function (link) {
    link.addEventListener("click", function (event) {
      var lang = link.getAttribute("data-lang-choice");
      if (lang === "en" || lang === "fr") {
        try { localStorage.setItem("tp-lang", lang); } catch (_) {}
      }
      // Mark the hero-static handoff for the destination page —
      // set in BOTH the reduced-motion branch and the animated
      // branch so any path through this handler hands off.
      markSkipHeroAnim();
      var mq = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)");
      if (mq && mq.matches) return;
      event.preventDefault();
      document.documentElement.classList.add("is-settling");
      // fail-safe: navigate after the transition window regardless of
      // whether transitionend fires (some browsers + offscreen elements).
      setTimeout(function () { window.location.assign(link.href); }, 320);
    });
  });

  // Focus trap — Tab cycles inside the modal; Shift+Tab wraps. Escape
  // is bound separately above (dismissAndRoute).
  var focusable = overlay.querySelectorAll(
    "a[href], button, [tabindex]:not([tabindex='-1'])"
  );
  if (!focusable.length) return;
  var first = focusable[0];
  var last = focusable[focusable.length - 1];

  overlay.addEventListener("keydown", function (event) {
    if (event.key !== "Tab") return;
    if (event.shiftKey && document.activeElement === first) {
      event.preventDefault();
      last.focus();
    } else if (!event.shiftKey && document.activeElement === last) {
      event.preventDefault();
      first.focus();
    }
  });

  // Move focus into the modal once the document is interactive.
  window.requestAnimationFrame(function () {
    try { first.focus({ preventScroll: true }); } catch (_) { first.focus(); }
  });
})();
