// Word Wall for WakeUpYourSoul.
//
// A visual frequency cloud of the most common content-bearing words across
// the library, sized by sqrt(count) so the long tail stays readable.
//
// Lives as its own top-level view, reachable via the Library category's
// sub-row (Books | Word Wall). When the view becomes active we re-render
// the cloud — this lets the user switch source books or language and see
// the cloud update without leaving the view.

(function () {
  function L() { return (window.__langData && window.__langData[(window.i18n && window.i18n.getLang()) || "en"]) || {}; }
  function t(key, fallback) { return L()[key] != null ? L()[key] : (fallback != null ? fallback : key); }

  function escapeHTML(s) {
    return String(s).replace(/[&<>"']/g, c => ({"&": "&amp;","<": "&lt;",">": "&gt;","\"": "&quot;","'": "&#39;"}[c]));
  }

  function currentLang() { return (window.i18n && window.i18n.getLang()) || "en"; }

  const SORT_STORAGE_KEY = "wuys-wwall-sort";
  const REVERSE_STORAGE_KEY = "wuys-wwall-reverse";

  function currentSortMode() {
    const sel = document.getElementById("wwall-sort");
    return (sel && sel.value) || "freq";
  }

  function currentReverse() {
    try { return localStorage.getItem(REVERSE_STORAGE_KEY) === "1"; } catch (e) { return false; }
  }

  function persistReverse(rev) {
    try { localStorage.setItem(REVERSE_STORAGE_KEY, rev ? "1" : "0"); } catch (e) {}
  }

  function restoreSortMode() {
    const sel = document.getElementById("wwall-sort");
    if (!sel) return;
    try {
      const saved = localStorage.getItem(SORT_STORAGE_KEY);
      if (saved && Array.from(sel.options).some(o => o.value === saved)) {
        sel.value = saved;
      }
    } catch (e) {}
  }

  function persistSortMode(mode) {
    try { localStorage.setItem(SORT_STORAGE_KEY, mode); } catch (e) {}
  }

  // Refresh the action button's icon, reversed-state class, and aria/title.
  function updateActionButton() {
    const btn = document.getElementById("wwall-action");
    const icon = document.getElementById("wwall-action-icon");
    if (!btn || !icon) return;
    const mode = currentSortMode();
    if (mode === "shuffle") {
      icon.textContent = "↻";
      btn.classList.remove("reversed");
      btn.setAttribute("aria-label", t("page.library.ww_reshuffle", "Re-shuffle"));
      btn.setAttribute("title", t("page.library.ww_reshuffle", "Re-shuffle"));
    } else {
      icon.textContent = "↓";
      const rev = currentReverse();
      btn.classList.toggle("reversed", rev);
      btn.setAttribute("aria-label", t("page.library.ww_reverse", "Reverse order"));
      btn.setAttribute("title", t("page.library.ww_reverse", "Reverse order"));
    }
  }

  // Data comes from window.__wordData.<lang>.<source>, populated by
  // words/<lang>.js script tags in index.html. Synchronous lookup — no
  // fetch — so the page works on file:// without a server.
  function loadWords(lang, source) {
    const bag = (window.__wordData && window.__wordData[lang]) || null;
    if (!bag) {
      console.warn("wordwall: no data loaded for lang", lang);
      return null;
    }
    const pairs = bag[source];
    if (!pairs) {
      console.warn("wordwall: no entry for source", source, "in", lang);
      return null;
    }
    return pairs;
  }

  function listAvailableBooks() {
    const cur = L();
    const ids = new Set();
    Object.keys(cur).forEach(k => {
      const m = k.match(/^book\.([^.]+)\.title$/);
      if (m) ids.add(m[1]);
    });
    const books = [];
    ids.forEach(id => {
      const title = cur["book." + id + ".title"];
      if (title) books.push({ id, title });
    });
    books.sort((a, b) => a.title.localeCompare(b.title));
    return books;
  }

  // sqrt scaling: biggest stands out without crushing the tail.
  function makeSizer(pairs) {
    if (!pairs || pairs.length === 0) return () => 14;
    const counts = pairs.map(p => p[1]);
    const min = Math.min.apply(null, counts);
    const max = Math.max.apply(null, counts);
    const MIN_PX = 12;
    const MAX_PX = 72;
    if (max === min) return () => (MIN_PX + MAX_PX) / 2;
    return function (c) {
      const t = (Math.sqrt(c) - Math.sqrt(min)) / (Math.sqrt(max) - Math.sqrt(min));
      return MIN_PX + t * (MAX_PX - MIN_PX);
    };
  }

  function tintFor(i) {
    const tones = ["wwall-t1", "wwall-t2", "wwall-t3", "wwall-t4", "wwall-t5"];
    return tones[i % tones.length];
  }

  // Sort modes — the visual order of words in the cloud.
  // Size (font scale) is ALWAYS driven by frequency; only the layout order changes.
  // The `reverse` flag inverts the natural direction of freq/alpha/length sorts.
  function applySort(pairs, mode, lang, reverse) {
    const arr = pairs.slice();
    switch (mode) {
      case "alpha": {
        const collator = new Intl.Collator(lang || "en", { sensitivity: "base" });
        arr.sort((a, b) => collator.compare(a[0], b[0]));
        if (reverse) arr.reverse();
        return arr;
      }
      case "length":
        arr.sort((a, b) => b[0].length - a[0].length || b[1] - a[1]);
        if (reverse) arr.reverse();
        return arr;
      case "shuffle": {
        for (let i = arr.length - 1; i > 0; i--) {
          const j = Math.floor(Math.random() * (i + 1));
          const tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;
        }
        return arr;
      }
      case "freq":
      default:
        // Pairs come sorted by frequency desc. Reverse → rare-first.
        if (reverse) arr.reverse();
        return arr;
    }
  }

  function renderCloud(host, pairs) {
    if (!pairs || pairs.length === 0) {
      host.innerHTML = '<p class="wwall-empty">' + escapeHTML(t("page.library.ww_empty", "No words available for this selection.")) + '</p>';
      return;
    }
    const sizer = makeSizer(pairs);
    const mode = currentSortMode();
    const reverse = mode !== "shuffle" && currentReverse();
    const visualOrder = applySort(pairs, mode, currentLang(), reverse);

    // Stagger fade-in across ~1.6s so the cloud unfolds rather than pops.
    // Each word also gets a random pulse duration (3–6.5s) AND random phase
    // — so the wall scatter-pulses instead of breathing in sync.
    const STAGGER_MS = 18;
    const STAGGER_CAP = 1600;
    host.innerHTML = visualOrder.map(([word, count], i) => {
      const size = sizer(count);
      const tint = tintFor(i);
      const bloomDelay = Math.min(i * STAGGER_MS, STAGGER_CAP);
      const pulseDur = (3 + Math.random() * 3.5).toFixed(2);    // 3.00 – 6.50 s
      const pulsePhase = -(Math.random() * parseFloat(pulseDur)).toFixed(2);
      const style = 'font-size:' + size.toFixed(1) + 'px;'
        + '--bloom-delay:' + bloomDelay + 'ms;'
        + '--pulse-dur:' + pulseDur + 's;'
        + '--pulse-delay:' + pulsePhase + 's';
      // role=button + tabindex=0 so words are keyboard-reachable.
      return '<span class="wwall-word ' + tint + '" '
        + 'role="button" tabindex="0"'
        + ' data-word="' + escapeHTML(word) + '"'
        + ' data-tint="' + tint + '"'
        + ' style="' + style + '"'
        + ' title="' + escapeHTML(word) + ' — ' + count + '">'
        + escapeHTML(word) + '</span>';
    }).join(" ");
  }

  // ===== Popover ===========================================================

  // Per-tint accent colors (kept in sync with styles.css tints).
  const TINT_ACCENTS = {
    "wwall-t1": { neon: "#ff2db5", glow: "rgba(255, 45, 181, 0.55)" },
    "wwall-t2": { neon: "#00f0ff", glow: "rgba(0, 240, 255, 0.55)" },
    "wwall-t3": { neon: "#b8ff3a", glow: "rgba(184, 255, 58, 0.50)" },
    "wwall-t4": { neon: "#c060ff", glow: "rgba(192, 96, 255, 0.55)" },
    "wwall-t5": { neon: "#ffa831", glow: "rgba(255, 168, 49, 0.50)" }
  };

  function lookupDefinition(word, lang) {
    // Try exact slug match first (dictionary keys are lowercase slugs).
    const slug = word.toLowerCase();
    const term = (window.__langData && window.__langData[lang] && window.__langData[lang]["dict.term." + slug]);
    const def = (window.__langData && window.__langData[lang] && window.__langData[lang]["dict.def." + slug]);
    if (term && def) return { term, def };
    return null;
  }

  function bookTitle(bookId, lang) {
    const cur = (window.__langData && window.__langData[lang]) || {};
    return cur["book." + bookId + ".title"] || bookId;
  }

  function lookupUsages(word, lang) {
    const bag = (window.__wordUsages && window.__wordUsages[lang]) || null;
    if (!bag) return null;
    return bag[word] || null;
  }

  function renderPopoverBody(word, lang) {
    const def = lookupDefinition(word, lang);
    const usages = lookupUsages(word, lang);
    const parts = [];

    if (def) {
      parts.push(
        '<div class="wwp-section">'
        + '<span class="wwp-section-label">' + escapeHTML(t("page.library.ww_definition", "Definition")) + '</span>'
        + '<div class="wwp-def">' + def.def + '</div>'
        + '</div>'
      );
    }

    if (usages && usages.sentences && usages.sentences.length) {
      const sentsHTML = usages.sentences.map(s => {
        const title = bookTitle(s.book, lang);
        // chXX for the human-facing "Chapter N" label (1-based)
        const chapLabel = t("page.library.stats_chapters", "Chapters").replace(/s$/, "");
        return '<button type="button" class="wwp-quote" '
          + 'data-action="open-quote" data-book="' + escapeHTML(s.book) + '" '
          + 'data-chapter="' + s.chapter + '">'
          + '<span class="wwp-quote-text">«' + escapeHTML(s.text) + '»</span>'
          + '<span class="wwp-quote-source">' + escapeHTML(title) + ' · '
          + escapeHTML(chapLabel) + ' ' + (s.chapter + 1) + '</span>'
          + '</button>';
      }).join("");
      parts.push(
        '<div class="wwp-section">'
        + '<span class="wwp-section-label">' + escapeHTML(t("page.library.ww_in_books", "In the books")) + '</span>'
        + sentsHTML
        + '</div>'
      );
    }

    if (usages && usages.books && usages.books.length) {
      const chipsHTML = usages.books.map(b => {
        const title = bookTitle(b.id, lang);
        return '<button type="button" class="wwp-chip" '
          + 'data-action="open-book" data-book="' + escapeHTML(b.id) + '">'
          + escapeHTML(title)
          + '<span class="wwp-chip-count">×' + b.count + '</span>'
          + '</button>';
      }).join("");
      parts.push(
        '<div class="wwp-section">'
        + '<span class="wwp-section-label">' + escapeHTML(t("page.library.ww_top_books", "Most used in")) + '</span>'
        + '<div class="wwp-books">' + chipsHTML + '</div>'
        + '</div>'
      );
    }

    if (parts.length === 0) {
      parts.push('<p class="wwp-empty">' + escapeHTML(t("page.library.ww_no_data", "No deeper data for this word yet.")) + '</p>');
    }
    return parts.join("");
  }

  function openPopover(word, tint) {
    const pop = document.getElementById("wwall-popover");
    const title = document.getElementById("wwall-popover-title");
    const body = document.getElementById("wwall-popover-body");
    if (!pop || !title || !body) return;
    const lang = currentLang();

    // Apply the clicked word's neon accent to the card framing.
    const accent = TINT_ACCENTS[tint] || TINT_ACCENTS["wwall-t4"];
    pop.style.setProperty("--accent-neon", accent.neon);
    pop.style.setProperty("--accent-neon-glow", accent.glow);

    title.textContent = word;
    body.innerHTML = renderPopoverBody(word, lang);
    pop.hidden = false;
    document.body.style.overflow = "hidden";
  }

  function closePopover() {
    const pop = document.getElementById("wwall-popover");
    if (!pop) return;
    pop.hidden = true;
    document.body.style.overflow = "";
  }

  function bindWordClicks() {
    document.body.addEventListener("click", (ev) => {
      // Open: clicking any word in the cloud.
      const w = ev.target.closest(".wwall-word");
      if (w) {
        openPopover(w.dataset.word, w.dataset.tint || "wwall-t1");
        return;
      }
      // Close: backdrop, ✕ button, or any element marked data-wwall-close.
      if (ev.target.closest("[data-wwall-close]")) {
        closePopover();
        return;
      }
      // Inside popover — quote/chip click → open the reader.
      const action = ev.target.closest("[data-action]");
      if (!action) return;
      const a = action.dataset.action;
      if (a === "open-quote" || a === "open-book") {
        const bookId = action.dataset.book;
        const chapter = action.dataset.chapter != null ? parseInt(action.dataset.chapter, 10) : 0;
        closePopover();
        if (typeof window.__openBookAt === "function") {
          window.__openBookAt(bookId, chapter);
        }
      }
    });
    document.body.addEventListener("keydown", (ev) => {
      // Esc closes the popover.
      if (ev.key === "Escape") {
        const pop = document.getElementById("wwall-popover");
        if (pop && !pop.hidden) closePopover();
      }
      // Enter / Space on a focused word triggers open.
      if (ev.key === "Enter" || ev.key === " ") {
        const w = document.activeElement && document.activeElement.closest(".wwall-word");
        if (w) {
          ev.preventDefault();
          openPopover(w.dataset.word, w.dataset.tint || "wwall-t1");
        }
      }
    });
  }

  function populateBookSelect(sel) {
    const cur = sel.value;
    const allOpt = '<option value="_all">' + escapeHTML(t("page.library.ww_all", "Whole library")) + '</option>';
    const books = listAvailableBooks().map(b =>
      '<option value="' + escapeHTML(b.id) + '">' + escapeHTML(b.title) + '</option>'
    ).join("");
    sel.innerHTML = allOpt + books;
    if (cur) sel.value = cur;
  }

  function refreshCloud() {
    const sel = document.getElementById("wwall-source");
    const host = document.getElementById("wwall-cloud");
    if (!sel || !host) return;
    const lang = currentLang();
    const source = sel.value || "_all";
    const pairs = loadWords(lang, source);
    renderCloud(host, pairs);
  }

  function bindSelectChange() {
    document.body.addEventListener("change", (ev) => {
      const id = ev.target && ev.target.id;
      if (id === "wwall-source") refreshCloud();
      if (id === "wwall-sort") {
        persistSortMode(ev.target.value);
        updateActionButton();
        refreshCloud();
      }
    });
  }

  // The action button slot does double duty:
  //   • Shuffle mode → spins, reshuffles
  //   • Freq / Alpha / Length → toggles `reverse`, icon flips 180°
  function bindAction() {
    document.body.addEventListener("click", (ev) => {
      const btn = ev.target.closest("#wwall-action");
      if (!btn) return;
      const mode = currentSortMode();
      if (mode === "shuffle") {
        btn.classList.remove("spinning");
        void btn.offsetWidth; // reflow so class re-add re-triggers rotation
        btn.classList.add("spinning");
        refreshCloud();
      } else {
        const rev = !currentReverse();
        persistReverse(rev);
        updateActionButton();
        refreshCloud();
      }
    });
  }

  function isWordWallActive() {
    const v = document.querySelector('[data-view="wordwall"]');
    return v && v.classList.contains("active");
  }

  // Refresh whenever the user navigates into the Word Wall view.
  // app.js updates location.hash on view changes, so hashchange is reliable.
  function watchNav() {
    window.addEventListener("hashchange", () => {
      if (isWordWallActive()) {
        // small delay so view-active class is fully applied
        setTimeout(refreshCloud, 0);
      }
    });
    // Also handle direct clicks on the Word Wall nav-link (in case the hash
    // is already #wordwall and clicking doesn't fire hashchange).
    document.body.addEventListener("click", (ev) => {
      const link = ev.target.closest('.nav-link[data-view="wordwall"]');
      if (link) setTimeout(refreshCloud, 0);
    });
  }

  function onLangChange() {
    const sel = document.getElementById("wwall-source");
    if (sel) populateBookSelect(sel);
    updateActionButton(); // button label is translated
    if (isWordWallActive()) refreshCloud();
  }

  function init() {
    const sel = document.getElementById("wwall-source");
    if (sel) populateBookSelect(sel);
    restoreSortMode();
    updateActionButton();
    bindSelectChange();
    bindAction();
    bindWordClicks();
    watchNav();
    window.addEventListener("langchange", onLangChange);
    // If the page loaded directly on #wordwall, render now.
    if (isWordWallActive()) refreshCloud();
  }

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