// ===========================================================
// Tiers (C1, roadmap F7, 28 Sep 2026): Common, Scarce, Rare, Grail.
//
// A tier belongs to the PRESSING, not the copy (supabase/migrations/
// pressing_tiers.sql), so every copy of a pressing wears the same one and a
// regrade never changes it. The rule, in the words the site uses:
//   Scarce  its Near Mint price is £60 or more
//   Rare    £150 or more, and it's scarce: few owners and few copies for sale,
//           or even the cheapest copy fetches a large share of that price
//   Grail   £400 or more, and scarce in the same way
// A pressing the market guard is still checking stops at Scarce.
//
// The look (the Tiers mockup): Common plain, Scarce an ink outline, Rare a
// vermilion ring, Grail the only foil on the site. Only Scarce and up carry a
// chip on a card; Common is shown in the shelf's counts, never as a badge.
//
//   BBR_tiers.forShelf(userId) -> Promise<{ items: {itemId: tier}, counts }>
//   <TierChip tier />  <TierCounts counts />  tierClass(tier)  tierRank(tier)
//
// Globals in: React, BBR_supabase. Exposes window.BBR_tiers, TierChip,
// TierCounts, tierClass, tierRank.
// ===========================================================
const TIER_LABEL = { common: "Common", scarce: "Scarce", rare: "Rare", grail: "Grail" };
const TIER_RANK = { grail: 3, rare: 2, scarce: 1, common: 0 };
// Tooltips say "about": a tier holds until the price falls 10% below its line,
// so a Rare can sit at £140 (pressing_tiers.sql, basis 'held').
const TIER_NOTE = {
  scarce: "Scarce: this pressing's Near Mint price is about £60 or more.",
  rare: "Rare: this pressing's Near Mint price is about £150 or more, and it's scarce.",
  grail: "Grail: this pressing's Near Mint price is about £400 or more, and it's scarce. The records people hunt for years.",
};

function tierRank(t) { return TIER_RANK[t] || 0; }
function tierClass(t) { return t && t !== "common" ? " tier-" + t : ""; }

// One shelf_tiers() call per shelf, cached for the page's life. The RPC answers
// in one row, so the 1,000-row cap on set-returning calls never applies.
// `fresh` skips the cache: the owner's page asks again when their collection
// changes, so a removed Grail stops being counted and a new record gets its tier
// as soon as the hourly refresh has one.
const tierCache = new Map();
function tiersForShelf(userId, fresh) {
  if (!userId || !window.BBR_supabase) return Promise.resolve({ items: {}, counts: null, loaded: true });
  const k = String(userId);
  if (!fresh && tierCache.has(k)) return tierCache.get(k);
  const p = Promise.resolve(window.BBR_supabase.rpc("shelf_tiers", { p_user: k }))
    .then((out) => (out && !out.error && out.data ? { items: out.data.items || {}, counts: out.data.counts || null, loaded: true } : { items: {}, counts: null, loaded: true }),
          () => ({ items: {}, counts: null, loaded: true }));
  tierCache.set(k, p);
  // A failed read should not stick for the page's life.
  p.then((v) => { if (!v.counts) tierCache.delete(k); });
  return p;
}

function TierChip({ tier, className }) {
  if (!tier || tier === "common") return null;
  return <span className={"tier-chip tier-chip-" + tier + (className ? " " + className : "")} title={TIER_NOTE[tier]}>{TIER_LABEL[tier]}</span>;
}

// "1 Grail · 4 Rare · 11 Scarce · 198 Common", and how the tiers work. Nothing
// at all while no record is Scarce or above: a line of "0 Grail · 0 Rare" says
// nothing a visitor wants to read.
function TierCounts({ counts, owner }) {
  if (!counts) return null;
  const top = (counts.grail || 0) + (counts.rare || 0) + (counts.scarce || 0);
  if (!top) return null;
  const parts = ["grail", "rare", "scarce", "common"].filter((t) => counts[t] > 0)
    .map((t) => <span key={t} className={"tier-count tier-count-" + t}><b>{Number(counts[t]).toLocaleString("en-GB")}</b> {TIER_LABEL[t]}</span>);
  return (
    <div className="tier-counts">
      <span className="tier-count-list">{parts}</span>
      {/* A ladder, not a paragraph (3 Oct 2026). The rule was one 120-word
          block in the counts row; what people want first is "what does it
          take", so each tier is one line with its price, and the fine print
          sits under it as three short notes. Same rule as before, and as
          refresh_pressing_tiers (live body checked 3 Oct 2026: 60/150/400,
          have < 5,000 and for_sale < 20, or lowest >= 0.4 x NM, 0.9 hysteresis).
          Change the numbers here if those constants move. */}
      <details className="tier-how">
        <summary>How tiers work</summary>
        <div className="tier-how-body">
          <ul className="tier-ladder">
            <li><TierChip tier="grail" /><b>£400+</b><span>and hard to find</span></li>
            <li><TierChip tier="rare" /><b>£150+</b><span>and hard to find</span></li>
            <li><TierChip tier="scarce" /><b>£60+</b></li>
            <li><span className="tier-chip tier-chip-common">Common</span><b>Under £60</b></li>
          </ul>
          <p className="tier-ladder-key">Prices are for a Near Mint copy of the pressing.</p>
          <ul className="tier-notes">
            <li><b>Hard to find:</b> fewer than 5,000 owners and fewer than 20 copies for sale, or the cheapest copy still costs 40% of the price.</li>
            <li><b>It&rsquo;s the pressing, not {owner ? "your" : "the"} copy,</b> so regrading never changes a tier.</li>
            <li><b>Updated hourly.</b> A tier holds until the price drops 10% below its line.</li>
            <li><b>Still checking?</b> A pressing whose price we&rsquo;re still checking stops at Scarce.</li>
          </ul>
        </div>
      </details>
    </div>
  );
}

Object.assign(window, {
  BBR_tiers: { forShelf: tiersForShelf, label: TIER_LABEL, note: TIER_NOTE },
  TierChip, TierCounts, tierClass, tierRank,
});
