// ===========================================================
// FrontTen (C1, roadmap F6, 28 Sep 2026): the ten records that lead a shelf.
//
// A collector picks them (shelf_picks, set through store.saveShelfPicks).
// Until they do, the front row is their rarest, then most valuable, records, so no shelf
// ever opens on an empty row and nothing needs setting up to look good. The
// same ten lead the shelf's preview image (ShelfShare), and later the Shelf
// card, Collector of the month's embed and Year in Vinyl.
//
// Tiers (F7, pressing_tiers.sql): each record may carry `tier`; the sleeve
// wears it and the automatic order is highest tier first, then value.
//
//   <FrontTen records picks mode name onEdit />   the band
//   <FrontTenPicker records picks onClose />      the owner's chooser
//   frontTenOf(records, picks) -> { list, chosen }
//
// records: [{ id, title, artist, year, value, grade, album }] where `album` is
// what <Sleeve> draws. Globals in: React, Sleeve, bbrOverlayPortal, BBR_store.
// ===========================================================
const { useState: useFtState, useEffect: useFtEffect, useRef: useFtRef, useMemo: useFtMemo } = React;

const FT_MAX = 10;

function ftGbp(n) { return "£" + Math.round(Number(n) || 0).toLocaleString("en-GB"); }
function ftNo(i) { return "№" + String(i + 1).padStart(2, "0"); }
function ftByValue(a, b) { return (Number(b.value) || 0) - (Number(a.value) || 0); }
// The automatic order the roadmap asked for: highest tier first, then value.
function ftByTierThenValue(a, b) {
  const tr = window.tierRank ? window.tierRank(b.tier) - window.tierRank(a.tier) : 0;
  return tr || ftByValue(a, b);
}

// One album, however many copies: two copies of the same record side by side
// read as a bug (the first real preview drew My Beautiful Dark Twisted Fantasy
// twice). The canon slug when there is one, else artist + title, loosely.
function ftAlbumKey(r) {
  const slug = r && ((r.album && r.album.slug) || r.slug);
  if (slug) return "s:" + slug;
  // Letters and digits in any script: an ASCII-only key made every Cyrillic or
  // CJK title the same record, and Ed Sheeran's +, ÷ and = one album.
  const n = (x) => String(x || "").toLowerCase().replace(/\(.*?\)|\[.*?\]/g, "").replace(/[^\p{L}\p{N}]+/gu, " ").trim();
  const t = n(r && r.title);
  if (!t) return "i:" + (r && r.id);
  return "t:" + n(r && r.artist) + "|" + t;
}
function ftDistinct(list, max) {
  const seen = new Set(), out = [];
  for (const r of list) {
    const k = ftAlbumKey(r);
    if (seen.has(k)) continue;
    seen.add(k); out.push(r);
    if (out.length >= max) break;
  }
  return out;
}

// The chosen ten in their order, skipping any since removed from the shelf;
// otherwise the ten most valuable distinct albums. `chosen` says which, so the
// heading can say. A collector's own picks are kept as chosen, duplicates and all.
function frontTenOf(records, picks) {
  const recs = records || [];
  const byId = new Map(recs.map((r) => [String(r.id), r]));
  const picked = (picks || []).map((id) => byId.get(String(id))).filter(Boolean);
  if (picked.length) return { list: picked.slice(0, FT_MAX), chosen: true };
  return { list: ftDistinct(recs.slice().sort(ftByTierThenValue), FT_MAX), chosen: false };
}

function FrontTenCard({ r, i }) {
  return (
    <li className={"ft-card" + (i === 0 ? " ft-lead" : "")}>
      <div className={"ft-sleeve" + (window.tierClass ? window.tierClass(r.tier) : "")}>{window.Sleeve ? <Sleeve album={r.album} size={i === 0 ? 220 : 148} /> : null}</div>
      <span className="ft-no">{ftNo(i)}{window.TierChip ? <TierChip tier={r.tier} className="ft-tier" /> : null}</span>
      <span className="ft-title">{r.title}</span>
      <span className="ft-artist">{r.artist}</span>
      <span className="ft-meta">
        {r.value > 0 ? <b>{ftGbp(r.value)}</b> : <span className="ft-pending">Not yet valued</span>}
        {r.year ? <span>{r.year}</span> : null}
        {r.grade ? <span>{r.grade}</span> : null}
      </span>
    </li>
  );
}

// mode: "owner" | "visitor". name: how the shelf is labelled ("Maya Brennan",
// "Anonymous Petty").
function FrontTen({ records, picks, mode, name, onEdit }) {
  const { list, chosen } = useFtMemo(() => frontTenOf(records, picks), [records, picks]);
  if (!list.length) return null;
  const owner = mode === "owner";
  const title = owner ? "Your front ten" : chosen ? (name || "Their") + "’s ten" : "The front of the shelf";
  const sub = owner
    ? (chosen
        ? "Chosen by you. They lead your shelf, and the preview when you share it."
        : "Your rarest records first, then your most valuable, until you pick your own ten. They lead your shelf, and the preview when you share it.")
    : (chosen
        ? "Chosen by " + (name || "this collector") + "."
        : "The rarest records here, then the most valuable. " + (name || "This collector") + " hasn’t picked their own ten yet.");
  return (
    <section className="ft" id="front-ten" aria-labelledby="ft-h">
      <div className="ft-head">
        <div>
          <h2 className="ft-h" id="ft-h">{title}</h2>
          <p className="ft-sub">{sub}</p>
        </div>
        {owner && onEdit && (
          <button type="button" className="mc-invite ft-edit" onClick={onEdit}>{chosen ? "Edit your ten" : "Pick your ten"}</button>
        )}
      </div>
      <ol className="ft-row" tabIndex={0} aria-label={title}>
        {list.map((r, i) => <FrontTenCard key={r.id} r={r} i={i} />)}
      </ol>
    </section>
  );
}

// ---- the chooser -------------------------------------------------------------
const FT_ERR = {
  not_yours: "One of those records is no longer on your shelf. Refresh and try again.",
  too_many: "Ten at most.",
  duplicate: "A record can only be in your ten once.",
};

function FrontTenPicker({ records, picks, onClose }) {
  const store = window.BBR_store;
  const start = useFtMemo(() => frontTenOf(records, picks), []); // eslint-disable-line
  // The automatic ten (rarest, then most valuable), as ids. The picker opens on
  // whatever leads the shelf now, so a collector who let us choose sees our ten
  // and can swap one out; it used to open empty, which read as the choice
  // having been lost. "Choose for me" puts this list back.
  const auto = useFtMemo(() => frontTenOf(records, []).list.map((r) => String(r.id)), [records]);
  const [sel, setSel] = useFtState(start.chosen ? start.list.map((r) => String(r.id)) : auto);
  // Whether this is OUR ten (saved as no picks, so it keeps up as the shelf
  // grows) or the collector's. A flag, not "does the list equal auto": auto is
  // recomputed as values and tiers arrive, so an untouched list could stop
  // matching it while the picker was open and an untouched Save froze it; and
  // a collector's own picks that happen to match our order are still theirs.
  // Set by opening on our ten or by "Choose for me"; any edit clears it.
  const [automatic, setAutomatic] = useFtState(!start.chosen);
  const sameAsAuto = sel.length === auto.length && sel.every((id, i) => id === auto[i]);
  const [q, setQ] = useFtState("");
  const [saving, setSaving] = useFtState(false);
  const [err, setErr] = useFtState("");
  const onCloseRef = useFtRef(onClose); onCloseRef.current = onClose;
  const byId = useFtMemo(() => new Map((records || []).map((r) => [String(r.id), r])), [records]);

  // Body lock, back gesture, Escape: as InviteFriend.
  useFtEffect(() => {
    const y = window.scrollY || 0, b = document.body;
    const prev = { position: b.style.position, top: b.style.top, left: b.style.left, right: b.style.right, width: b.style.width, overflow: b.style.overflow };
    Object.assign(b.style, { position: "fixed", top: "-" + y + "px", left: "0", right: "0", width: "100%", overflow: "hidden" });
    return () => { Object.assign(b.style, prev); window.scrollTo(0, y); };
  }, []);
  useFtEffect(() => {
    history.pushState({ bbrFrontTen: true }, "", location.pathname + location.search + location.hash);
    let popped = false;
    const onPop = () => { popped = true; onCloseRef.current(); };
    window.addEventListener("popstate", onPop);
    return () => { window.removeEventListener("popstate", onPop); if (!popped) history.back(); };
  }, []);
  useFtEffect(() => {
    const k = (e) => { if (e.key === "Escape" && !e.defaultPrevented) onCloseRef.current(); };
    window.addEventListener("keydown", k);
    return () => window.removeEventListener("keydown", k);
  }, []);

  const chosen = sel.map((id) => byId.get(id)).filter(Boolean);
  const full = chosen.length >= FT_MAX;
  // Big shelves run to a thousand records: the list shows the first hundred
  // matches, and search reaches the rest.
  const matches = useFtMemo(() => {
    const t = q.trim().toLowerCase();
    const all = (records || []).slice().sort(ftByValue);
    const hit = t ? all.filter((r) => ((r.title || "") + " " + (r.artist || "")).toLowerCase().includes(t)) : all;
    return { shown: hit.slice(0, 100), total: hit.length };
  }, [records, q]);

  function toggle(id) {
    setErr(""); setAutomatic(false);
    setSel((s) => s.includes(id) ? s.filter((x) => x !== id) : (s.length >= FT_MAX ? s : s.concat(id)));
  }
  function move(i, d) {
    setAutomatic(false);
    setSel((s) => {
      const j = i + d;
      if (j < 0 || j >= s.length) return s;
      const n = s.slice(); const t = n[i]; n[i] = n[j]; n[j] = t; return n;
    });
  }
  async function save(ids) {
    if (!store || !store.saveShelfPicks) return;
    // Our ten, untouched, saves as no picks at all, so the row keeps itself
    // current as the shelf grows instead of freezing today's ten.
    if (automatic) ids = [];
    setSaving(true); setErr("");
    const out = await store.saveShelfPicks(ids);
    setSaving(false);
    if (out && out.ok) {
      try { window.BBR_ref && window.BBR_ref.track("front_ten_saved", { count: ids.length }); } catch (e) {}
      onClose();
    } else setErr(FT_ERR[(out && out.error) || ""] || "That didn’t save. Try again.");
  }

  return bbrOverlayPortal(
    <div className="av2-backdrop" onClick={onClose}>
      <div className="av2-modal ft-modal" onClick={(e) => e.stopPropagation()}>
        <div className="av2-head">
          <div className="av2-head-left">
            <button className="av2-back-arrow" aria-label="Back" onClick={onClose}>
              <svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M12 4l-5 6 5 6" /></svg>
            </button>
            <span className="av2-head-title">Pick your ten</span>
          </div>
          <button className="av2-x" aria-label="Close" onClick={onClose}>×</button>
        </div>
        <div className="av2-body">
          <div className="ft-pane">
            <p className="ft-lede">The ten records that lead your shelf and its preview. Choose up to ten, in the order you want them seen.</p>

            <div className="ft-label">{automatic ? "Chosen for you" : "Your ten"} <span>{chosen.length} of {FT_MAX}</span></div>
            {automatic && chosen.length > 0 && (
              <p className="ft-auto-note">
                {start.chosen
                  ? "Save to hand the front of your shelf back to us: your rarest records, then your most valuable, kept up to date as your shelf grows."
                  : "Your rarest records, then your most valuable. They update as your shelf grows. Remove one with × and pick another below, or reorder them, to make the ten your own."}
              </p>
            )}
            {chosen.length ? (
              <ol className="ft-chosen">
                {chosen.map((r, i) => (
                  <li key={r.id}>
                    <span className="ft-no">{ftNo(i)}</span>
                    <span className="ft-thumb">{window.Sleeve ? <Sleeve album={r.album} size={40} /> : null}</span>
                    <span className="ft-row-text"><b>{r.title}</b><span>{r.artist}</span></span>
                    <span className="ft-row-btns">
                      <button type="button" aria-label={"Move " + r.title + " up"} disabled={i === 0} onClick={() => move(i, -1)}>↑</button>
                      <button type="button" aria-label={"Move " + r.title + " down"} disabled={i === chosen.length - 1} onClick={() => move(i, 1)}>↓</button>
                      <button type="button" aria-label={"Remove " + r.title} onClick={() => toggle(String(r.id))}>×</button>
                    </span>
                  </li>
                ))}
              </ol>
            ) : (
              <p className="ft-empty">None chosen, so your shelf leads with your rarest records, then your most valuable. Tap a record below to start, or let us choose.</p>
            )}

            <div className="ft-label ft-label-all">Your shelf</div>
            {/* The picker now opens on a full ten, so every record below starts
                disabled; without this a tap on one did nothing and said nothing. */}
            {full && <p className="ft-full-hint" role="note">Your ten is full. Tap × on one above to make room for another.</p>}
            <input className="inv-copy-url ft-search" type="search" placeholder={"Search " + (records || []).length.toLocaleString("en-GB") + " records"}
                   value={q} onChange={(e) => setQ(e.target.value)} aria-label="Search your records"
                   onKeyDown={(e) => { if (e.key === "Escape" && q) { e.preventDefault(); setQ(""); } }} />
            <ul className="ft-all">
              {matches.shown.map((r) => {
                const id = String(r.id), on = sel.includes(id);
                return (
                  <li key={id}>
                    <button type="button" className={"ft-pick" + (on ? " on" : "")} aria-pressed={on} disabled={!on && full} onClick={() => toggle(id)}>
                      <span className="ft-thumb">{window.Sleeve ? <Sleeve album={r.album} size={40} /> : null}</span>
                      <span className="ft-row-text"><b>{r.title}</b><span>{r.artist}{r.value > 0 ? " · " + ftGbp(r.value) : ""}</span></span>
                      <span className="ft-tick" aria-hidden="true">{on ? "✓" : "+"}</span>
                    </button>
                  </li>
                );
              })}
            </ul>
            {matches.total > matches.shown.length && (
              <p className="ft-more">Showing {matches.shown.length} of {matches.total.toLocaleString("en-GB")}. Search to find the rest.</p>
            )}
          </div>
        </div>
        <div className="ft-foot">
          {err && <div className="sh-err" role="alert">{err}</div>}
          <div className="ft-foot-btns">
            <button type="button" className="sh-text-btn" disabled={saving || !auto.length || (automatic && sameAsAuto)} onClick={() => { setErr(""); setSel(auto); setAutomatic(true); }}>Choose for me</button>
            <button type="button" className="inv-copy-btn" disabled={saving} onClick={() => save(sel)}>{saving ? "Saving" : "Save"}</button>
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { FrontTen, FrontTenPicker, frontTenOf, ftAlbumKey });
