// ===========================================================
// ShelfStandings (C1, roadmap F8, 28 Sep 2026): "Where Maya stands".
//
// The badges that used to live only on leaderboard rows (Genre leader, 250+
// records) and the collector's best standings as chips
// ("2nd by total value", "1st in Soul / R&B"), on their shelf, where a visitor
// sees them. The strategy's rule: a badge nobody can see doesn't get
// screenshotted.
//
// Every figure comes from shelf_standings() (supabase/migrations/
// shelf_standings.sql), which ranks exactly as the boards do, and every chip
// links to the board that shows it, so a claim on a shelf is always one tap
// from being checked. Only boards that exist are cited. Streaks stay private.
// F10 (29 Sep 2026) adds the segment boards: "3rd in the 1960s", "2nd this
// month", "1st for Queen" (never "most": a 1st can be a count tie broken by value). At most one chip each, so a shelf with forty
// artists doesn't wear forty chips.
//
// Badge rules are the leaderboard row's (Leaderboards_v2 BADGE SCARCITY):
// Genre leader = first in any genre; 250+ records. ("Half the canon", at 50 of
// the Hundred, went with the Hundred on 3 Oct 2026: the deep-dive archive grows
// every week, so "half" has no fixed meaning.) The tier name is shown in the
// stats line already.
// FOUNDERS: membership (B1) will add a Founders badge; it goes first in
// `badges` below, from the profile, when that exists.
//
// Globals in: React, BBR_supabase, BBR_ref, __nav. Exposes window.ShelfStandings.
// ===========================================================
const { useState: useSsState, useEffect: useSsEffect } = React;

const SS_TOP = 10;        // a standing below 10th is not worth a chip
const SS_MIN_FIELD = 3;   // "1st in Folk" out of one collector is not a standing
const SS_MAX_CHIPS = 4;
// Catch-all genres are real boards but not something to wear on a shelf.
const SS_SKIP_GENRES = new Set(["non-music", "other", "unknown"]);

function ssOrdinal(n) {
  const s = ["th", "st", "nd", "rd"], v = n % 100;
  return n + (s[(v - 20) % 10] || s[v] || s[0]);
}

// Turn shelf_standings() into badges and chips. Exported for the harness.
function ssChips(st) {
  if (!st) return { badges: [], chips: [] };
  const genres = Array.isArray(st.genres) ? st.genres : [];
  const badges = [];
  // Genre leader, named for the biggest field they lead.
  const worn = genres.filter((g) => !SS_SKIP_GENRES.has(String(g.genre || "").toLowerCase()));
  // A lead over nobody is not a lead: the badge needs the same field as a chip.
  // (Stricter than the leaderboard row's badge, which still counts a field of
  // one; the shelf never claims more than the board shows.)
  const led = worn.filter((g) => g.rank === 1 && g.of >= SS_MIN_FIELD).sort((a, b) => b.of - a.of);
  if (led.length) badges.push({ key: "leader", label: "Genre leader · " + led[0].genre, board: "genre", genre: led[0].genre,
                                title: "First of " + led[0].of + " collectors in " + led[0].genre + (led.length > 1 ? ", and leads " + (led.length - 1) + " more genre" + (led.length > 2 ? "s" : "") : "") });
  if ((st.count || 0) >= 250) badges.push({ key: "r250", label: "250+ records", board: "value", title: Number(st.count).toLocaleString("en-GB") + " records" });

  const cand = [];
  // Top ten AND top half: in a field of nine, 9th is last, not a standing.
  const worthIt = (rank, of) => rank <= SS_TOP && rank <= Math.ceil(of / 2);
  if (st.value && worthIt(st.value.rank, st.value.of)) cand.push({ key: "value", rank: st.value.rank, of: st.value.of, label: ssOrdinal(st.value.rank) + " by total value", board: "value" });
  if (st.canon && (st.canon_owned || 0) > 0 && worthIt(st.canon.rank, st.canon.of)) cand.push({ key: "canon", rank: st.canon.rank, of: st.canon.of, label: ssOrdinal(st.canon.rank) + " on deep dives owned", board: "canon" });
  worn.forEach((g) => {
    if (g.of < SS_MIN_FIELD || !worthIt(g.rank, g.of)) return;
    if (led.length && g.genre === led[0].genre) return;   // the badge already says it
    cand.push({ key: "g:" + g.genre, rank: g.rank, of: g.of, label: ssOrdinal(g.rank) + " in " + g.genre, board: "genre", genre: g.genre });
  });
  // Segment boards (F10): the best of each kind only. shelf_standings() lists
  // decades and artists best first; an artist needs two albums to be worn.
  const firstWorth = (list, ok) => (Array.isArray(list) ? list : []).find((x) => x.of >= SS_MIN_FIELD && worthIt(x.rank, x.of) && (!ok || ok(x)));
  const m = st.month;
  if (m && m.of >= SS_MIN_FIELD && worthIt(m.rank, m.of)) cand.push({ key: "month", rank: m.rank, of: m.of, label: ssOrdinal(m.rank) + " this month", board: "month" });
  const d = firstWorth(st.decades);
  if (d) cand.push({ key: "d:" + d.decade, rank: d.rank, of: d.of, label: ssOrdinal(d.rank) + " in the " + d.decade + "s", board: "decade", decade: d.decade });
  const a = firstWorth(st.artists, (x) => (x.n || 0) >= 2);
  if (a) cand.push({ key: "a:" + a.key, rank: a.rank, of: a.of, label: ssOrdinal(a.rank) + " for " + a.artist, board: "artist", artist: a.key });
  // Hardest-won first: place relative to the field, so 2nd of 39 by total value
  // outranks 1st of 5 in a small genre. Ties go to the bigger field.
  cand.sort((a, b) => (a.rank / a.of - b.rank / b.of) || (b.of - a.of));
  const chips = cand.slice(0, SS_MAX_CHIPS).map((c) => Object.assign(c, { title: ssOrdinal(c.rank) + " of " + c.of + " collectors" }));
  return { badges, chips };
}

function ssHref(c) {
  const q = new URLSearchParams({ board: c.board });
  if (c.genre) q.set("genre", c.genre);
  if (c.decade) q.set("decade", String(c.decade));
  if (c.artist) q.set("artist", c.artist);
  return "/leaderboards?" + q.toString();
}

// Open the board in the SPA, on the right tab. The href still works on its
// own (a new tab, a copied link): Leaderboards_v2 reads ?board=, ?genre=,
// ?decade= and ?artist=.
function ssGo(e, c, mode) {
  try { window.BBR_ref && window.BBR_ref.track("shelf_to_board", { board: c.board, from: mode }); } catch (x) {}
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
  if (!window.__nav || !window.__nav.goLeaderboards) return;
  e.preventDefault();
  window.BBR_LB_INTENT = { board: c.board, genre: c.genre || null, decade: c.decade != null ? String(c.decade) : null, artist: c.artist || null };
  window.__nav.goLeaderboards();
}

// mode: "owner" | "visitor"; name: the shelf's label ("Anonymous Petty").
function ShelfStandings({ userId, name, mode }) {
  const [st, setSt] = useSsState(null);
  useSsEffect(() => {
    if (!userId || !window.BBR_supabase) return;
    let live = true;
    setSt(null);
    Promise.resolve(window.BBR_supabase.rpc("shelf_standings", { p_user: userId }))
      .then((out) => { if (live && out && !out.error && out.data) setSt(out.data); }, () => {});
    return () => { live = false; };
  }, [userId]);
  const { badges, chips } = ssChips(st);
  if (!badges.length && !chips.length) return null;
  const label = mode === "owner" ? "Where you stand" : "Where " + (name || "this collector") + " stands";
  return (
    <div className="ss" aria-label={label}>
      <div className="ss-label">{label}</div>
      <ul className="ss-chips">
        {badges.map((b) => (
          <li key={b.key}><a className="ss-chip ss-badge" href={ssHref(b)} title={b.title} onClick={(e) => ssGo(e, b, mode)}>{b.label}</a></li>
        ))}
        {chips.map((c) => (
          <li key={c.key}><a className="ss-chip" href={ssHref(c)} title={c.title} onClick={(e) => ssGo(e, c, mode)}>{c.label}</a></li>
        ))}
      </ul>
    </div>
  );
}

Object.assign(window, { ShelfStandings, ssChips });
