<h1>Library</h1>
<div class="pagebar"><span id="libcount"></span></div>
<div class="filterbar" role="group" aria-label="filter by kind">
  <input type="search" id="libsearch" placeholder="search titles, designers, mechanics…"
         aria-label="search library">
  <button type="button" data-kind="" aria-pressed="true">All</button>
  <button type="button" data-kind="boardgame" aria-pressed="false">Board games</button>
  <button type="button" data-kind="rpgitem" aria-pressed="false">RPGs</button>
  <button type="button" data-kind="localgame" aria-pressed="false">Off BGG</button>
</div>
<div class="filterbar">
  <label>plays with
    <input type="number" id="libplayers" min="1" max="20" placeholder="any"
           style="width:5em" aria-label="filter by player count"></label>
  <label>sort
    <select id="libsort" aria-label="sort library">
      <option value="name">name</option>
      <option value="year">year (newest)</option>
      <option value="rank">BGG rank</option>
      <option value="weight">weight (lightest)</option>
      <option value="playtime">playing time</option>
    </select></label>
</div>
<div id="libbody"></div>
<script>
"use strict";
let GAMES = [];
let KIND = "";  // "" = all; "boardgame" also covers expansions

function gameCard(g) {
  const art = g.thumbnail || g.image;
  const lo = g.min_players ?? g.max_players, hi = g.max_players ?? g.min_players;
  const players = lo
    ? (lo === hi ? `${lo}` : `${lo}–${hi}`) + " players"
    : "";
  const time = g.playtime ? `${g.playtime} min` : "";
  const weight = g.weight ? `weight ${g.weight.toFixed(1)}` : "";
  const rank = g.rank ? `BGG rank ${g.rank}` : "";
  return `
  <a class="game" href="/library/game/${encodeURIComponent(g.key)}">
    ${art
      ? `<img src="${esc(art)}" alt="" loading="lazy">`
      : `<div class="noart" aria-hidden="true">${esc((g.name || "?").charAt(0).toUpperCase())}</div>`}
    <div class="info">
      <div class="gname">${esc(g.name)}${g.year ? ` <span class="meta">(${esc(g.year)})</span>` : ""}
        ${g.type === "rpgitem" ? `<span class="chip open">RPG · local only</span>` : ""}
        ${g.type === "localgame" ? `<span class="chip open">not on BGG · local</span>` : ""}</div>
      <div class="gmeta">${[players, time, weight, rank].filter(Boolean).map(esc).join(" · ")}</div>
      ${g.version ? `<div class="gmeta">${esc(g.version.name || "")}</div>` : ""}
    </div>
  </a>`;
}

function matchesKind(g) {
  if (!KIND) return true;
  if (KIND === "boardgame") return !["rpgitem", "localgame"].includes(g.type);
  return g.type === KIND;
}

// null sorts last whichever way the column runs: an unranked game is not
// "rank 0", and a game with no weight is not the lightest
function by(field, dir = 1) {
  return (a, b) => {
    const x = a[field], y = b[field];
    if (x == null && y == null) return 0;
    if (x == null) return 1;
    if (y == null) return -1;
    return (x - y) * dir;
  };
}

const SORTS = {
  name: (a, b) => (a.name || "").localeCompare(b.name || "", undefined, {sensitivity: "base"}),
  year: by("year", -1),
  rank: by("rank"),
  weight: by("weight"),
  playtime: by("playtime"),
};

function render() {
  const q = document.getElementById("libsearch").value.trim().toLowerCase();
  const seats = Number(document.getElementById("libplayers").value) || 0;
  let rows = GAMES.filter(matchesKind);
  if (q) rows = rows.filter(g => [
    g.name,
    (g.designers || []).join(" "),
    (g.mechanics || []).join(" "),
    (g.categories || []).join(" "),
    g.version && g.version.name,
  ].filter(Boolean).join(" ").toLowerCase().includes(q));
  if (seats) rows = rows.filter(g =>
    (g.min_players || g.max_players || 0) <= seats
    && seats <= (g.max_players || g.min_players || 0));
  rows = [...rows].sort(SORTS[document.getElementById("libsort").value] || SORTS.name);
  document.getElementById("libcount").innerHTML =
    `<b>${rows.length}</b> of <b>${GAMES.length}</b> game(s)`
    + (seats ? ` playable with <b>${seats}</b>` : "");
  document.getElementById("libbody").innerHTML = rows.length
    ? `<div class="shelfgrid">${rows.map(gameCard).join("")}</div>`
    : `<p class="empty">${GAMES.length
        ? "No games match that search."
        : `Your library appears here after <b>enrich</b> runs — full metadata for
           every cataloged game: art, player counts, playtime, and more.<br>
           Run the pipeline through <code>enrich</code> to fill these shelves.`}</p>`;
}

const GATE = changeGate();
async function refresh() {
  const games = await fetchJSON("/api/library");
  GATE(games, () => { GAMES = games; render(); });
}

document.getElementById("libsearch").addEventListener("input", render);
document.getElementById("libplayers").addEventListener("input", render);
document.getElementById("libsort").addEventListener("change", render);
document.querySelectorAll("[data-kind]").forEach(b => b.addEventListener("click", () => {
  KIND = b.dataset.kind;
  document.querySelectorAll("[data-kind]").forEach(o =>
    o.setAttribute("aria-pressed", String(o === b)));
  render();
}));
refresh().catch(err => errorBanner(err.message || err));
pollLoop(refresh, 10000, () => showBanner(""));
</script>
