(function () {
  // View routing + two-tier nav
  const views = document.querySelectorAll(".view");
  const navLinks = document.querySelectorAll(".nav-link");
  const navCats = document.querySelectorAll(".nav-cat");
  const subRows = document.querySelectorAll(".sub-row");
  const navSub = document.querySelector(".nav-sub");
  const brand = document.querySelector(".brand");

  const VIEW_TO_CAT = {
    home: "home",
    emotions: "inner", self: "inner", ego: "inner",
    sovereignty: "inner", truth: "inner", discernment: "inner",
    shadow: "inner", "inner-child": "inner",
    heart: "inner", meditation: "inner", silence: "inner",
    witness: "inner", now: "inner", time: "inner", beauty: "inner",
    friendship: "bonds", relationships: "bonds", children: "bonds",
    voice: "bonds", boundaries: "bonds",
    partnership: "bonds", kin: "bonds", elders: "bonds",
    "the-wound": "bonds", belonging: "bonds", conflict: "bonds", trust: "bonds",
    manipulation: "world", power: "world", money: "world", history: "world", places: "world", cosmology: "world", science: "world", media: "world", temples: "world", family: "world", community: "world", calendar: "world", inversion: "world", war: "world",
    body: "body", sex: "body", healing: "body", nature: "body",
    mind: "body", food: "body", plants: "body", medicine: "body",
    neurodivergence: "body", veil: "body", frequency: "body", parasites: "body",
    monad: "source", sin: "source", religion: "source",
    "heaven-hell": "source", "false-teachers": "source",
    christ: "source", magic: "source", love: "source",
    purpose: "path", awakening: "path", karma: "path",
    forgiveness: "path", suffering: "path", surrender: "path",
    service: "path", death: "path",
    music: "music",
    library: "library",
    wordwall: "library",
    reader: "library",
    dictionary: "dictionary",
    dictwall: "dictionary",
    quiz: "quiz",
    geo: "quiz"
  };

  const LEAF_CATS = new Set(["home", "music"]);

  // Categories that have a sub-row but should ALSO jump to a default view
  // when the top-level category is clicked. (e.g., clicking "Library" lands
  // on the Books page; the sub-row Word Wall is opt-in.)
  const CAT_DEFAULT_VIEW = {
    library: "library",
    dictionary: "dictionary",
    quiz: "quiz",
  };

  const validViews = Object.keys(VIEW_TO_CAT);

  function showCategory(cat) {
    navCats.forEach((b) => b.classList.toggle("active", b.dataset.cat === cat));
    subRows.forEach((r) => r.classList.toggle("active", r.dataset.cat === cat));
    const hasSubRow = Array.from(subRows).some((r) => r.dataset.cat === cat);
    if (navSub) navSub.classList.toggle("collapsed", !hasSubRow);
  }

  // Views may embed a subpath after a `/` to encode page-specific state
  // (e.g. geo.js writes `#geo/view=globe,lon=12.34,lat=-5.67,z=4.2` to
  // restore the map view). The view *name* is everything before the first `/`.
  function getHashViewName() {
    const raw = location.hash.replace(/^#/, "");
    const slash = raw.indexOf("/");
    return slash >= 0 ? raw.slice(0, slash) : raw;
  }

  function setView(name) {
    if (!validViews.includes(name)) name = "home";
    views.forEach((v) => v.classList.toggle("active", v.dataset.view === name));
    navLinks.forEach((l) => l.classList.toggle("active", l.dataset.view === name));
    document.body.dataset.view = name;
    showCategory(VIEW_TO_CAT[name]);
    // Only rewrite the hash if the current view-name PREFIX doesn't match.
    // Preserves any subpath state the view itself owns (geo coords, etc.).
    if (getHashViewName() !== name) {
      history.replaceState(null, "", "#" + name);
    }
    window.scrollTo(0, 0);
  }

  navLinks.forEach((link) => {
    link.addEventListener("click", () => setView(link.dataset.view));
  });

  navCats.forEach((btn) => {
    btn.addEventListener("click", () => {
      const cat = btn.dataset.cat;
      if (LEAF_CATS.has(cat)) {
        setView(cat);
      } else if (CAT_DEFAULT_VIEW[cat]) {
        setView(CAT_DEFAULT_VIEW[cat]);
      } else {
        showCategory(cat);
      }
    });
  });

  brand.addEventListener("click", (e) => {
    e.preventDefault();
    setView("home");
  });

  // Landing room cards: click anywhere outside a subject link → go to default view.
  document.querySelectorAll(".room-card[data-default-view]").forEach((card) => {
    card.addEventListener("click", (e) => {
      if (e.target.closest(".room-page-link")) return;
      const v = card.dataset.defaultView;
      if (v) {
        if (location.hash === "#" + v) setView(v);
        else location.hash = "#" + v;
      }
    });
  });

  window.addEventListener("hashchange", () => {
    setView(getHashViewName() || "home");
  });

  const initialView = getHashViewName() || "home";
  setView(initialView);

  // Emotions view
  const negativeContainer = document.getElementById("negative-cards");
  const positiveContainer = document.getElementById("positive-cards");
  const searchInput = document.getElementById("search");
  const filterButtons = document.querySelectorAll(".filter-btn");
  const negativeColumn = document.querySelector('.column[data-group="negative"]');
  const positiveColumn = document.querySelector('.column[data-group="positive"]');
  const countNeg = document.getElementById("count-negative");
  const countPos = document.getElementById("count-positive");

  let activeFilter = "all";
  let searchTerm = "";
  let sortMode = "hz-asc";

  const DEFAULT_ORDER = EMOTIONS.map((e, i) => [e.name, i]).reduce((m, [n, i]) => {
    m[n] = i;
    return m;
  }, {});

  function sortEmotions(list) {
    const copy = list.slice();
    const nameOf = (e) => (window.i18n && window.i18n.t)
      ? window.i18n.t(`emotion.${e.id}.name`, e.name)
      : e.name;
    switch (sortMode) {
      case "hz-asc":
        return copy.sort((a, b) => a.frequency - b.frequency || nameOf(a).localeCompare(nameOf(b)));
      case "hz-desc":
        return copy.sort((a, b) => b.frequency - a.frequency || nameOf(a).localeCompare(nameOf(b)));
      case "alpha":
        return copy.sort((a, b) => nameOf(a).localeCompare(nameOf(b)));
      default:
        return copy.sort((a, b) => DEFAULT_ORDER[a.name] - DEFAULT_ORDER[b.name]);
    }
  }

  function tt(key, fallback) {
    return (window.i18n && window.i18n.t) ? window.i18n.t(key, fallback) : fallback;
  }

  function flipLabel(group) {
    return group === "negative"
      ? tt("page.emotions.flip_label.negative", "The hidden gift")
      : tt("page.emotions.flip_label.positive", "The hidden shadow");
  }

  function pairDirectionLabel(group) {
    return group === "negative"
      ? tt("page.emotions.pair_dir.negative", "Light side")
      : tt("page.emotions.pair_dir.positive", "Shadow side");
  }

  const EMOTIONS_BY_NAME = EMOTIONS.reduce((m, e) => { m[e.name] = e; return m; }, {});

  function emotionDisplayName(e) {
    return tt(`emotion.${e.id}.name`, e.name);
  }
  function emotionDisplayDef(e) {
    return tt(`emotion.${e.id}.def`, e.definition);
  }
  function emotionDisplayFlip(e) {
    return tt(`emotion.${e.id}.flip`, e.flip);
  }
  function pairDisplayName(pairName) {
    const p = EMOTIONS_BY_NAME[pairName];
    return p ? emotionDisplayName(p) : pairName;
  }

  function freqBand(hz) {
    if (hz <= 30) return "deep-low";
    if (hz <= 100) return "low";
    if (hz < 200) return "mid-low";
    if (hz === 200) return "threshold";
    if (hz <= 400) return "mid-high";
    if (hz < 540) return "high";
    if (hz < 600) return "very-high";
    return "peace";
  }

  function makeCard(emotion) {
    const card = document.createElement("div");
    card.className = "card";
    card.dataset.name = emotion.name.toLowerCase();
    card.dataset.group = emotion.group;
    card.innerHTML = `
      <div class="card-header">
        <span class="card-name"></span>
        <span class="card-meta">
          <span class="hz-badge"></span>
          <span class="card-toggle">▾</span>
        </span>
      </div>
      <div class="card-body">
        <p class="definition"></p>
        <span class="flip-label"></span>
        <p class="flip-text"></p>
        <button class="pair-link" type="button">
          <span class="pair-direction"></span>
          <span class="pair-name"></span>
          <span class="pair-arrow">→</span>
        </button>
      </div>
    `;
    card.querySelector(".card-name").textContent = emotionDisplayName(emotion);
    card.querySelector(".definition").textContent = emotionDisplayDef(emotion);
    card.querySelector(".flip-label").textContent = flipLabel(emotion.group);
    card.querySelector(".flip-text").textContent = emotionDisplayFlip(emotion);
    card.querySelector(".pair-direction").textContent = pairDirectionLabel(emotion.group);
    card.querySelector(".pair-name").textContent = pairDisplayName(emotion.pair);

    const badge = card.querySelector(".hz-badge");
    badge.textContent = emotion.frequency + " Hz";
    badge.dataset.band = freqBand(emotion.frequency);
    badge.title =
      emotion.frequency < 200
        ? tt("page.emotions.hz.contractive", "Contractive — drains life-force from the body")
        : emotion.frequency === 200
        ? tt("page.emotions.hz.threshold", "Courage — the threshold between contraction and expansion")
        : tt("page.emotions.hz.expansive", "Expansive — amplifies life-force in the body");

    card.querySelector(".card-header").addEventListener("click", () => {
      card.classList.toggle("open");
    });

    card.querySelector(".pair-link").addEventListener("click", (e) => {
      e.stopPropagation();
      navigateToPair(emotion.pair);
    });

    return card;
  }

  function navigateToPair(name) {
    if (activeFilter !== "all") {
      activeFilter = "all";
      filterButtons.forEach((b) => {
        b.classList.toggle("active", b.dataset.filter === "all");
      });
      render();
    }

    requestAnimationFrame(() => {
      const target = document.querySelector(`.card[data-name="${name.toLowerCase()}"]`);
      if (!target) return;

      target.classList.add("open");
      target.scrollIntoView({ behavior: "smooth", block: "center" });

      target.classList.remove("pulse");
      void target.offsetWidth;
      target.classList.add("pulse");
      setTimeout(() => target.classList.remove("pulse"), 1500);
    });
  }

  function render() {
    negativeContainer.innerHTML = "";
    positiveContainer.innerHTML = "";

    let negVisible = 0;
    let posVisible = 0;

    sortEmotions(EMOTIONS).forEach((e) => {
      const dispName = emotionDisplayName(e).toLowerCase();
      const dispDef = emotionDisplayDef(e).toLowerCase();
      const dispFlip = emotionDisplayFlip(e).toLowerCase();
      const dispPair = pairDisplayName(e.pair).toLowerCase();
      const matchesSearch =
        !searchTerm ||
        dispName.includes(searchTerm) ||
        dispDef.includes(searchTerm) ||
        dispFlip.includes(searchTerm) ||
        dispPair.includes(searchTerm) ||
        e.name.toLowerCase().includes(searchTerm) ||
        e.pair.toLowerCase().includes(searchTerm);

      const matchesFilter = activeFilter === "all" || activeFilter === e.group;

      if (matchesSearch && matchesFilter) {
        const card = makeCard(e);
        if (e.group === "negative") {
          negativeContainer.appendChild(card);
          negVisible++;
        } else {
          positiveContainer.appendChild(card);
          posVisible++;
        }
      }
    });

    countNeg.textContent = `${negVisible} ${tt("page.emotions.count.negative", "negative")}`;
    countPos.textContent = `${posVisible} ${tt("page.emotions.count.positive", "positive")}`;

    negativeColumn.classList.toggle("hidden", activeFilter === "positive");
    positiveColumn.classList.toggle("hidden", activeFilter === "negative");

    negativeContainer.classList.toggle("empty", negVisible === 0 && activeFilter !== "positive");
    positiveContainer.classList.toggle("empty", posVisible === 0 && activeFilter !== "negative");
  }

  searchInput.addEventListener("input", (e) => {
    searchTerm = e.target.value.trim().toLowerCase();
    render();
  });

  filterButtons.forEach((btn) => {
    btn.addEventListener("click", () => {
      filterButtons.forEach((b) => b.classList.remove("active"));
      btn.classList.add("active");
      activeFilter = btn.dataset.filter;
      render();
    });
  });

  const sortSelect = document.getElementById("sort");
  sortSelect.addEventListener("change", (e) => {
    sortMode = e.target.value;
    render();
  });

  render();

  window.addEventListener("langchange", () => { render(); });

  // ===== Dictionary: A–Z jump bar + search =====
  const dictGroups = document.querySelectorAll(".dict-group");
  const dictJump = document.getElementById("dict-jump");
  const dictSearch = document.getElementById("dict-search");
  const dictEmpty = document.getElementById("dict-empty");

  if (dictJump && dictGroups.length) {
    const letters = [];
    dictGroups.forEach((g) => {
      const letter = g.querySelector(".dict-letter")?.textContent?.trim();
      if (letter) letters.push(letter);
    });
    dictJump.innerHTML = letters
      .map((l) => `<button data-letter="${l}">${l}</button>`)
      .join("");
    dictJump.addEventListener("click", (e) => {
      const btn = e.target.closest("button[data-letter]");
      if (!btn) return;
      const target = document.getElementById("dict-" + btn.dataset.letter);
      if (target) target.scrollIntoView({ behavior: "smooth", block: "start" });
    });

    // Scroll-spy: highlight whichever letter section is currently below the
    // sticky bars. Picks the topmost dict-group whose heading is below the
    // (nav + jump-bar) zone — that's the section the reader is actually in.
    const jumpButtons = dictJump.querySelectorAll("button[data-letter]");
    const byLetter = {};
    jumpButtons.forEach((b) => { byLetter[b.dataset.letter] = b; });

    function activeOffset() {
      // Distance from viewport top down to "just below the dict-jump bar".
      const navH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--nav-h")) || 96;
      const jumpH = dictJump.offsetHeight || 60;
      return navH + jumpH + 8;
    }

    let raf = null;
    function updateActiveLetter() {
      raf = null;
      const dictView = document.getElementById("dictionary");
      // Only run when the dictionary view is actually showing
      if (dictView && !dictView.classList.contains("active")) return;
      const top = activeOffset();
      let current = null;
      // Pick the last group whose top is at or above the active line.
      dictGroups.forEach((g) => {
        if (g.style.display === "none") return;
        const rect = g.getBoundingClientRect();
        if (rect.top <= top + 1) current = g;
      });
      // Fallback: if nothing is above the line yet (we're above all groups),
      // highlight the first visible group instead so the bar isn't empty.
      if (!current) {
        for (const g of dictGroups) {
          if (g.style.display !== "none") { current = g; break; }
        }
      }
      const letter = current?.querySelector(".dict-letter")?.textContent?.trim();
      jumpButtons.forEach((b) => b.classList.toggle("active", b.dataset.letter === letter));
    }
    function scheduleUpdate() {
      if (raf) return;
      raf = requestAnimationFrame(updateActiveLetter);
    }
    window.addEventListener("scroll", scheduleUpdate, { passive: true });
    window.addEventListener("resize", scheduleUpdate);
    // Recompute when the user toggles search filters (some groups hide).
    if (dictSearch) dictSearch.addEventListener("input", scheduleUpdate);
    // First paint
    scheduleUpdate();
    // Recompute when nav-h changes (orientation flip, sub-row toggle, etc.)
    if (window.ResizeObserver) new ResizeObserver(scheduleUpdate).observe(document.querySelector(".site-nav"));
  }

  if (dictSearch) {
    dictSearch.addEventListener("input", (e) => {
      const q = e.target.value.trim().toLowerCase();
      let totalVisible = 0;
      dictGroups.forEach((group) => {
        let groupVisible = 0;
        group.querySelectorAll(".dict-entry").forEach((entry) => {
          const text = entry.textContent.toLowerCase();
          const match = !q || text.includes(q);
          entry.style.display = match ? "" : "none";
          if (match) groupVisible++;
        });
        group.style.display = groupVisible === 0 ? "none" : "";
        totalVisible += groupVisible;
      });
      if (dictEmpty) dictEmpty.hidden = totalVisible > 0;
    });
  }
})();
