/* Enhanced sections for the Marvin Gaye, What's Going On dossier.
   Each component is conditionally rendered by AlbumPage when the matching
   richer data exists on the essay object. */

const { useState: useWState, useEffect: useWEffect, useMemo: useWMemo } = React;

/* -------------------------------------------------
   1. PressingLedger, dynamic collector's corner
   ------------------------------------------------- */
function PressingLedger({ pressings, affiliates, slug }) {
  const [filter, setFilter] = useWState("all"); // all | buy | avoid
  const [activeId, setActiveId] = useWState(pressings[0].id);
  const [sortBy, setSortBy] = useWState("rank"); // rank | sound | rarity | value

  // Live market values for this album's pressings, on the same valuation path as
  // the collection pages. Empty until the fetch lands, so the first paint shows
  // the editorial figures and nothing reflows if the fetch fails.
  const liveValues = window.useDossierValues ? window.useDossierValues(slug) : { byId: {} };

  /* Verdict handling.
     Two conventions exist in the data: the canon 100 use a keyword
     ("buy" / "avoid" / "specialty"), while the deep dives put a sentence in the
     same field ("The home-label first press and the origin grail"). The old code
     tested `verdict === "buy"` and rendered ANYTHING else as "SKIP", so on those
     albums every pressing — the holy grail included — was labelled SKIP, and the
     "Worth buying" filter matched nothing. Normalise on read so both conventions
     render correctly and an unrecognised value degrades to no badge rather than a
     false negative recommendation. */
  const verdictOf = (p) => {
    const v = String(p.verdict || "").trim().toLowerCase();
    if (v === "buy") return "buy";
    if (v === "skip" || v === "avoid") return "avoid";
    if (v === "specialty" || v === "hold") return "specialty";
    if (!v) return null;
    // A sentence: read its sense rather than assuming the worst.
    if (/\b(avoid|steer clear|wary|beware|skip|don't bother|not worth)\b/.test(v)) return "avoid";
    return "buy";
  };
  const VERDICT_BADGE = { buy: "BUY", avoid: "SKIP", specialty: "SPECIALIST" };

  const filtered = useWMemo(() => {
    let list = [...pressings];
    if (filter !== "all") list = list.filter(p => verdictOf(p) === filter);
    if (sortBy === "sound") list.sort((a, b) => b.sound - a.sound);
    if (sortBy === "rarity") list.sort((a, b) => b.rarity - a.rarity);
    if (sortBy === "value") list.sort((a, b) => b.value - a.value);
    return list;
  }, [filter, sortBy, pressings]);

  const active = pressings.find(p => p.id === activeId) || pressings[0];

  return (
    <div className="ledger">
      <div className="ledger-controls">
        <div className="ledger-filters">
          {[
            { k: "all", n: "All pressings" },
            { k: "buy", n: "Worth buying" },
            { k: "avoid", n: "Skip" }
          ].map(f => (
            <button key={f.k} className={filter === f.k ? "lf active" : "lf"} onClick={() => setFilter(f.k)}>{f.n}</button>
          ))}
        </div>
        <div className="ledger-sort">
          <span>Sort by</span>
          {[
            { k: "rank", n: "Grail order" },
            { k: "sound", n: "Sound" },
            { k: "rarity", n: "Rarity" },
            { k: "value", n: "Value/$" }
          ].map(s => (
            <button key={s.k} className={sortBy === s.k ? "ls active" : "ls"} onClick={() => setSortBy(s.k)}>{s.n}</button>
          ))}
        </div>
      </div>

      <div className="ledger-grid">
        <ul className="ledger-list">
          {filtered.map(p => {
            const isActive = p.id === activeId;
            const v = verdictOf(p);
            return (
              <li key={p.id} className={"lrow " + (isActive ? "active " : "") + (v ? "v-" + v : "")}
                  onClick={() => setActiveId(p.id)}>
                <div className="lrow-top">
                  <span className="lrow-year">{p.year}</span>
                  <span className="lrow-country">{p.country}</span>
                  {v && <span className={"lrow-verdict v-" + v}>{VERDICT_BADGE[v]}</span>}
                  <span className="lrow-rating">{"★".repeat(Math.round(p.rating / 2))}<span className="o">{"★".repeat(5 - Math.round(p.rating / 2))}</span></span>
                </div>
                <div className="lrow-title">{p.title}</div>
                <div className="lrow-rank">{p.rank}</div>
              </li>
            );
          })}
        </ul>

        <div className="ledger-detail">
          <div className="ld-top">
            <div className="ld-rank">{active.rank}</div>
            <h3>{active.title}</h3>
            <div className="ld-meta">
              <span><b>Format</b>{active.format}</span>
              <span><b>Plant</b>{active.plant}</span>
              <span><b>Matrix</b><code>{active.matrix}</code></span>
            </div>
          </div>

          <div className="ld-marks">
            <h4>Identifying marks</h4>
            <ul>{active.marks.map((m, i) => <li key={i}>{m}</li>)}</ul>
          </div>

          <div className="ld-bars">
            <Bar label="Sound quality" pct={active.sound} />
            <Bar label="Rarity" pct={active.rarity} />
            <Bar label="Value for money" pct={active.value} />
          </div>

          {/* Live value where we have one, editorial figure where we don't, and
              nothing at all where we have neither (see components/DossierValues.jsx). */}
          {window.PressingValues
            ? <window.PressingValues pressing={active} live={liveValues.byId[active.id] || null} />
            : (
              <div className="ld-values">
                {Object.entries(active.values || {}).map(([k, v]) => (
                  <div className="ldv" key={k}>
                    <span className="ldv-k">{k === "vgplus" ? "VG+" : k.toUpperCase()}</span>
                    <span className="ldv-v">{typeof v === "number" ? "£" + v.toLocaleString("en-GB") : v}</span>
                  </div>
                ))}
              </div>
            )}

          <p className="ld-note">{active.note}</p>
        </div>
      </div>

      <button
        className="ledger-guide-link"
        onClick={() => window.__nav && window.__nav.goPressingGuide && window.__nav.goPressingGuide()}>
        <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="6" cy="6" r="5"/><path d="M6 5.5v3M6 3.6h.01"/></svg>
        Not sure which pressing you own? Read the identification guide
      </button>
    </div>
  );
}

function Bar({ label, pct }) {
  return (
    <div className="bar-row">
      <span className="bar-label">{label}</span>
      <div className="bar-track"><span style={{ width: pct + "%" }} /></div>
      <span className="bar-pct">{pct}</span>
    </div>
  );
}

/* -------------------------------------------------
   2. ChartsVisual, bar chart + sparkline + timeline
   ------------------------------------------------- */
function ChartsVisual({ peakData, salesMilestones: rawMilestones, globalSales, datapoint, released }) {
  /* Sales milestones are a pure year-keyed timeline, and later data files APPEND
     to the array at load time, so the merged order can end up wrong even when the
     source arrays are sorted. Sorting on render covers every future append.
     Stable, so entries sharing a year keep their authored order.

     This sort is NOT a substitute for correct data. It was added when
     like-a-prayer, ray-of-light and homogenic each ended on an out-of-sequence
     year, and it quietly hid the actual cause: those three were authored as
     `{ year: 2023-24 }`, which JavaScript evaluates as a subtraction — 1999. The
     rows sorted into the 1990s and rendered "1999" beside a 2023 tour. The years
     are quoted now and audit_data.js fails on the unquoted form. A `year` that
     looks wrong here is a data bug to fix at source, not to sort around. */
  const salesMilestones = useWMemo(() => {
    const list = Array.isArray(rawMilestones) ? rawMilestones : [];
    return list
      .map((m, i) => ({ m, i, y: Number(String(m.year ?? "").match(/\d{4}/)?.[0]) }))
      .sort((a, b) => (isFinite(a.y) && isFinite(b.y) ? a.y - b.y || a.i - b.i : a.i - b.i))
      .map((x) => x.m);
  }, [rawMilestones]);

  const maxShown = 80; // visualize up to #80; anything lower clips but labels show truth

  // Pull the primary "Month YYYY" the album was released, from the (variable)
  // released string, e.g. "August 5, 1966 (UK) · ..." -> "August 1966".
  function enteredMonth(str) {
    if (!str) return null;
    const m = str.match(/(January|February|March|April|May|June|July|August|September|October|November|December)\s+(?:\d{1,2},\s*)?(\d{4})/);
    return m ? (m[1] + " " + m[2]) : null;
  }
  const entered = enteredMonth(released);

  return (
    <div className="charts-visual">
      <div className="cv-bars">
        <div className="cv-bar-head">
          <span>Peak position by market</span>
          {entered && <span className="cv-entered">Released {entered}</span>}
        </div>
        {peakData.map((d, i) => {
          const pct = Math.max(4, 100 - (Math.min(d.peak, maxShown) / maxShown) * 100);
          return (
            <div className="cv-bar" key={i}>
              <span className="cv-flag">{d.country}</span>
              <span className="cv-market">{d.market}</span>
              <div className="cv-bar-track">
                <span className="cv-bar-fill" style={{ width: pct + "%" }} />
                <span className="cv-peak">№{d.peak}</span>
              </div>
              <span className="cv-runline">
                {d.weeks ? <span className="cv-weeks">{d.weeks} wks on chart</span> : null}
                {d.cert ? <span className="cv-cert">{d.cert}</span> : null}
              </span>
            </div>
          );
        })}
      </div>

      <div className="cv-milestones">
        <div className="cv-milestones-head">Commercial arc</div>
        <ol className="cv-ml">
          {salesMilestones.map((m, i) => (
            <li key={i}>
              <span className="ml-year">{m.year}</span>
              <span className="ml-dot" />
              <span className="ml-event">{m.events}</span>
            </li>
          ))}
        </ol>
      </div>

      <div className="cv-footer">
        <div>
          <b>Global sales</b>
          <p>{globalSales}</p>
        </div>
        <div>
          <b>One interesting data point</b>
          <p>{datapoint}</p>
        </div>
      </div>
    </div>
  );
}




/* -------------------------------------------------
   3. SceneGrid, making-of illustrated vignettes
   ------------------------------------------------- */
function SceneGrid({ scenes }) {
  return (
    <div className="scenes">
      {scenes.map((s, i) => {
        // Real wired photo > hand-drawn illustration for a known key > nothing.
        // SceneArt returns null for keys without a custom illustration, so an
        // unillustrated scene collapses its art slot rather than rendering an
        // internal "reference pending" placeholder. Called as a plain function
        // (SceneArt is hook-free) so we can test truthiness before wrapping.
        const art = s.imageUrl ? (
          <div className="scene-photo">
            <img
              src={s.imageUrl}
              alt={s.headline || ""}
              loading="lazy"
              style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
            />
            {s.imageCredit && (
              <span
                style={{
                  position: "absolute", left: 0, right: 0, bottom: 0,
                  padding: "4px 7px", fontSize: "9px", lineHeight: 1.3,
                  fontFamily: "var(--mono, monospace)", letterSpacing: "0.02em",
                  color: "rgba(255,255,255,0.9)",
                  background: "linear-gradient(to top, rgba(0,0,0,0.62), rgba(0,0,0,0))",
                  pointerEvents: "none",
                }}
              >{s.imageCredit}</span>
            )}
          </div>
        ) : SceneArt({ kind: s.key });
        return (
          <article className={"scene scene-" + s.key + (art ? "" : " scene-noart")} key={s.key}>
            {art && <div className="scene-art">{art}</div>}
            <div className="scene-body">
              <div className="scene-sub">{s.sub}</div>
              <h3>{s.headline}</h3>
              <p>{s.body}</p>
            </div>
          </article>
        );
      })}
    </div>
  );
}

// CSS/SVG illustrations, editorial-style, no external images
function SceneArt({ kind }) {
  if (kind === "hitsville") {
    return (
      <svg viewBox="0 0 400 260" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="260" fill="#e9d6aa" />
        {/* floor line */}
        <rect x="0" y="200" width="400" height="60" fill="#3a322a" />
        {/* house */}
        <rect x="90" y="80" width="220" height="130" fill="#f3ede1" stroke="#17130f" strokeWidth="2" />
        <polygon points="90,80 200,30 310,80" fill="#7a2424" stroke="#17130f" strokeWidth="2" />
        {/* sign */}
        <rect x="130" y="100" width="140" height="34" fill="#17130f" />
        <text x="200" y="122" fontSize="13" fontFamily="Inter" fontWeight="800" fill="#f3ede1" textAnchor="middle" letterSpacing="2">HITSVILLE U.S.A.</text>
        {/* windows */}
        <rect x="120" y="150" width="36" height="48" fill="#f9e6b4" stroke="#17130f" strokeWidth="1.5" />
        <rect x="244" y="150" width="36" height="48" fill="#f9e6b4" stroke="#17130f" strokeWidth="1.5" />
        {/* door */}
        <rect x="182" y="158" width="36" height="52" fill="#17130f" />
        <circle cx="210" cy="185" r="1.5" fill="#d97757" />
        {/* lamppost */}
        <line x1="340" y1="210" x2="340" y2="130" stroke="#17130f" strokeWidth="2" />
        <circle cx="340" cy="126" r="6" fill="#f9e6b4" stroke="#17130f" strokeWidth="1.5" />
        {/* night glow */}
        <circle cx="340" cy="126" r="24" fill="#f9e6b4" opacity="0.3" />
      </svg>
    );
  }
  if (kind === "triplevocal") {
    return (
      <svg viewBox="0 0 400 260" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="260" fill="#17130f" />
        {/* three stacked waveforms */}
        {[70, 130, 190].map((y, i) => (
          <g key={i} transform={`translate(40, ${y})`}>
            <line x1="0" y1="0" x2="320" y2="0" stroke="#3a322a" strokeWidth="1" />
            <path
              d={Array.from({ length: 60 }).map((_, j) => {
                const x = (j / 59) * 320;
                const h = Math.sin((j + i * 7) * 0.6) * (8 + (i * 3)) + Math.cos(j * 1.3 + i) * 6;
                return `M${x} ${-Math.abs(h)} L${x} ${Math.abs(h)}`;
              }).join(" ")}
              stroke={i === 0 ? "#d97757" : i === 1 ? "#f3ede1" : "#b39c6e"}
              strokeWidth="2"
              opacity={i === 1 ? 1 : 0.85}
            />
            <text x="-6" y="4" fontSize="9" fontFamily="JetBrains Mono" fill="#8a8072" textAnchor="end" letterSpacing="2">TR {3 + i * 2}</text>
          </g>
        ))}
        <text x="40" y="235" fontSize="10" fontFamily="JetBrains Mono" fill="#8a8072" letterSpacing="3">MARVIN × 3 · AMPEX 8-TRACK</text>
      </svg>
    );
  }
  if (kind === "floor-bass") {
    return (
      <svg viewBox="0 0 400 260" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="260" fill="#2d3a4a" />
        {/* floor tiles */}
        <g opacity="0.25">
          {Array.from({ length: 10 }).map((_, i) => (
            <line key={i} x1="0" y1={40 + i * 24} x2="400" y2={40 + i * 24} stroke="#f3ede1" strokeWidth="0.5" />
          ))}
        </g>
        {/* bass guitar laid flat */}
        <g transform="translate(60, 140) rotate(-8)">
          <rect x="0" y="0" width="240" height="30" rx="4" fill="#3a2a1a" stroke="#17130f" strokeWidth="1.5" />
          <rect x="0" y="-4" width="24" height="38" rx="2" fill="#6b4a2a" stroke="#17130f" strokeWidth="1.5" />
          <circle cx="170" cy="15" r="10" fill="#17130f" />
          {/* strings */}
          {[8, 13, 18, 22].map((y, i) => (
            <line key={i} x1="24" y1={y} x2="230" y2={y} stroke="#b39c6e" strokeWidth="0.8" />
          ))}
        </g>
        {/* ghost figure lying */}
        <g transform="translate(100, 100)" opacity="0.6">
          <ellipse cx="80" cy="10" rx="20" ry="14" fill="none" stroke="#f3ede1" strokeWidth="1.5" strokeDasharray="3 3" />
          <path d="M60 10 L40 60 L0 80" fill="none" stroke="#f3ede1" strokeWidth="1.5" strokeDasharray="3 3" />
          <path d="M100 10 L130 50" fill="none" stroke="#f3ede1" strokeWidth="1.5" strokeDasharray="3 3" />
        </g>
        <text x="200" y="245" fontSize="10" fontFamily="JetBrains Mono" fill="#f3ede1" opacity="0.6" textAnchor="middle" letterSpacing="3">TAKE 1 · ONE TAKE ONLY</text>
      </svg>
    );
  }
  if (kind === "gordy-no") {
    return (
      <svg viewBox="0 0 400 260" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="260" fill="#f3ede1" />
        {/* phone cord */}
        <path d="M80 40 Q120 90 90 130 T110 200" stroke="#17130f" strokeWidth="1.5" fill="none" />
        {/* receiver top */}
        <g transform="translate(50, 30) rotate(-15)">
          <rect x="0" y="0" width="60" height="28" rx="10" fill="#17130f" />
          <circle cx="12" cy="14" r="4" fill="#d97757" />
          <circle cx="48" cy="14" r="4" fill="#d97757" />
        </g>
        {/* speech bubble */}
        <g transform="translate(150, 60)">
          <path d="M0 20 Q0 0 20 0 L210 0 Q230 0 230 20 L230 100 Q230 120 210 120 L60 120 L30 155 L40 120 L20 120 Q0 120 0 100 Z"
                fill="#17130f" />
          <text x="115" y="44" fontSize="15" fontFamily="DM Serif Display" fontStyle="italic" fill="#f3ede1" textAnchor="middle">&ldquo;The worst record</text>
          <text x="115" y="66" fontSize="15" fontFamily="DM Serif Display" fontStyle="italic" fill="#f3ede1" textAnchor="middle">I've ever heard</text>
          <text x="115" y="88" fontSize="15" fontFamily="DM Serif Display" fontStyle="italic" fill="#f3ede1" textAnchor="middle">in my life.&rdquo;</text>
          <text x="115" y="108" fontSize="9" fontFamily="JetBrains Mono" fill="#d97757" textAnchor="middle" letterSpacing="3">, BERRY GORDY</text>
        </g>
      </svg>
    );
  }
  // No custom illustration for this scene key and no real image wired: render
  // nothing so the slot collapses cleanly. We never surface an internal
  // "reference pending" placeholder on a production page (canon or deep dive) —
  // an absent slot is the correct, deliberate state.
  return null;
}
function CommentsSection({ slug }) {
  const [comments, setComments] = useWState([]);
  const [votes, setVotes] = useWState({});       // { comment_id: netScore }
  const [myVotes, setMyVotes] = useWState({});   // { comment_id: 1 | -1 }
  const [loading, setLoading] = useWState(true);
  const [me, setMe] = useWState(null);
  const [draftBody, setDraftBody] = useWState("");
  const [draftRating, setDraftRating] = useWState(5);
  const [posting, setPosting] = useWState(false);
  const [replyTo, setReplyTo] = useWState(null);   // comment id being replied to
  const [replyBody, setReplyBody] = useWState("");
  const cfBodyRef = React.useRef(null);

  useWEffect(() => {
    let active = true;
    if (window.BBR_auth && window.BBR_auth.current) {
      window.BBR_auth.current().then((u) => { if (active) setMe(u || null); });
    }
    const off = window.BBR_auth && window.BBR_auth.onChange
      ? window.BBR_auth.onChange((u) => setMe(u || null))
      : null;
    return () => { active = false; if (typeof off === "function") off(); };
  }, []);

  async function loadAll() {
    if (!window.BBR_supabase) { setLoading(false); return; }
    try {
      const { data: cData, error: cErr } = await window.BBR_supabase
        .from("comments").select("*").eq("slug", slug)
        .order("created_at", { ascending: true });
      if (cErr) throw cErr;
      const list = cData || [];
      setComments(list);

      // Votes for these comments
      const ids = list.map(c => c.id);
      const tally = {};
      const mine = {};
      if (ids.length) {
        const { data: vData } = await window.BBR_supabase
          .from("comment_votes").select("comment_id, value, user_id").in("comment_id", ids);
        (vData || []).forEach(v => {
          tally[v.comment_id] = (tally[v.comment_id] || 0) + v.value;
          if (me && v.user_id === me.id) mine[v.comment_id] = v.value;
        });
      }
      setVotes(tally);
      setMyVotes(mine);
    } catch (e) {
      console.error("[BBR] load comments failed", e);
    } finally {
      setLoading(false);
    }
  }
  useWEffect(() => { loadAll(); }, [slug, me && me.id]);

  useWEffect(() => {
    const h = (ev) => {
      const t = ev && ev.detail && ev.detail.text;
      if (!t) return;
      setDraftBody(prev => (prev && prev.trim()) ? prev : ("On the question, “" + t + "”\n\n"));
      setTimeout(() => { if (cfBodyRef.current) cfBodyRef.current.focus(); }, 80);
    };
    window.addEventListener("bbr-prefill-comment", h);
    return () => window.removeEventListener("bbr-prefill-comment", h);
  }, []);

  // Ratings only count top-level comments (replies don't carry a rating).
  const topLevel = comments.filter(c => !c.parent_id);
  const repliesByParent = {};
  comments.filter(c => c.parent_id).forEach(r => {
    (repliesByParent[r.parent_id] = repliesByParent[r.parent_id] || []).push(r);
  });

  const allRatings = topLevel.map(c => c.rating).filter(r => typeof r === "number" && r > 0);
  const avg = allRatings.length > 0 ? (allRatings.reduce((a, b) => a + b, 0) / allRatings.length) : 0;
  const distribution = [5, 4, 3, 2, 1].map(n => ({ stars: n, count: allRatings.filter(r => r === n).length }));
  const total = allRatings.length;

  function requireMe() {
    if (!me) { if (window.__bbrRequireAuth) window.__bbrRequireAuth("comment"); return false; }
    return true;
  }

  // Jump straight into the composer — used by the empty-state and header CTAs so
  // "be the first to comment" is one tap, not a scroll-hunt for the form.
  function startComment() {
    const el = document.getElementById("comment-form");
    if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
    if (!me) { if (window.__bbrRequireAuth) window.__bbrRequireAuth("comment"); return; }
    setTimeout(() => { if (cfBodyRef.current) cfBodyRef.current.focus(); }, 320);
  }

  async function submitComment() {
    const body = draftBody.trim();
    if (!body) return;
    if (!requireMe()) return;
    setPosting(true);
    try {
      const row = { slug, user_id: me.id, name: me.name || "Member", body, rating: draftRating, parent_id: null };
      const { data, error } = await window.BBR_supabase.from("comments").insert(row).select().single();
      if (error) throw error;
      setComments(prev => [...prev, data]);
      setDraftBody(""); setDraftRating(5);
    } catch (e) { console.error("[BBR] post failed", e); }
    finally { setPosting(false); }
  }

  async function submitReply(parentId) {
    const body = replyBody.trim();
    if (!body) return;
    if (!requireMe()) return;
    setPosting(true);
    try {
      const row = { slug, user_id: me.id, name: me.name || "Member", body, rating: null, parent_id: parentId };
      const { data, error } = await window.BBR_supabase.from("comments").insert(row).select().single();
      if (error) throw error;
      setComments(prev => [...prev, data]);
      setReplyBody(""); setReplyTo(null);
    } catch (e) { console.error("[BBR] reply failed", e); }
    finally { setPosting(false); }
  }

  async function vote(commentId, value) {
    if (!requireMe()) return;
    const existing = myVotes[commentId] || 0;
    const next = existing === value ? 0 : value; // tapping the same arrow clears it
    // optimistic update
    setMyVotes(prev => ({ ...prev, [commentId]: next }));
    setVotes(prev => ({ ...prev, [commentId]: (prev[commentId] || 0) - existing + next }));
    try {
      if (next === 0) {
        await window.BBR_supabase.from("comment_votes")
          .delete().eq("comment_id", commentId).eq("user_id", me.id);
      } else {
        await window.BBR_supabase.from("comment_votes")
          .upsert({ comment_id: commentId, user_id: me.id, value: next }, { onConflict: "comment_id,user_id" });
      }
    } catch (e) {
      console.error("[BBR] vote failed", e);
      loadAll(); // resync on failure
    }
  }

  function whenLabel(ts) {
    if (!ts) return "just now";
    const d = new Date(ts);
    const diff = (Date.now() - d.getTime()) / 1000;
    if (diff < 60) return "just now";
    if (diff < 3600) return Math.floor(diff / 60) + "m ago";
    if (diff < 86400) return Math.floor(diff / 3600) + "h ago";
    if (diff < 604800) return Math.floor(diff / 86400) + "d ago";
    return d.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" });
  }

  function VoteBar({ c }) {
    const score = votes[c.id] || 0;
    const mv = myVotes[c.id] || 0;
    return (
      <span className="c-votes">
        <button className={"c-vote up" + (mv === 1 ? " on" : "")} onClick={() => vote(c.id, 1)} aria-label="Thumbs up">
          <svg width="14" height="14" viewBox="0 0 24 24" fill={mv === 1 ? "currentColor" : "none"} stroke="currentColor" strokeWidth="2"><path d="M7 10v11H4a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h3zm0 0l5-7a2 2 0 0 1 2 2v3h5a2 2 0 0 1 2 2.4l-1.5 7A2 2 0 0 1 16.5 21H7"/></svg>
        </button>
        <span className={"c-score" + (score > 0 ? " pos" : score < 0 ? " neg" : "")}>{score}</span>
        <button className={"c-vote down" + (mv === -1 ? " on" : "")} onClick={() => vote(c.id, -1)} aria-label="Thumbs down">
          <svg width="14" height="14" viewBox="0 0 24 24" fill={mv === -1 ? "currentColor" : "none"} stroke="currentColor" strokeWidth="2"><path d="M17 14V3h3a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-3zm0 0l-5 7a2 2 0 0 1-2-2v-3H5a2 2 0 0 1-2-2.4l1.5-7A2 2 0 0 1 7.5 3H17"/></svg>
        </button>
      </span>
    );
  }

  function CommentItem({ c, isReply }) {
    return (
      <li className={isReply ? "c c-reply-item" : "c"}>
        <div className="c-avatar" style={{ background: avatarColor(c.name || "Member") }}>{initials(c.name || "Member")}</div>
        <div className="c-body">
          <div className="c-top">
            <span className="c-name">{c.name || "Member"}</span>
            {typeof c.rating === "number" && c.rating > 0 && (
              <span className="c-stars">{"★".repeat(c.rating)}<span className="o">{"★".repeat(5 - c.rating)}</span></span>
            )}
            <span className="c-when">{whenLabel(c.created_at)}</span>
          </div>
          <p>{c.body}</p>
          <div className="c-actions">
            <VoteBar c={c} />
            {!isReply && (
              <button className="c-replybtn" onClick={() => { if (!requireMe()) return; setReplyTo(replyTo === c.id ? null : c.id); setReplyBody(""); }}>
                Reply
              </button>
            )}
          </div>
          {!isReply && replyTo === c.id && (
            <div className="c-replybox">
              <textarea placeholder={"Reply to " + (c.name || "Member") + "…"} value={replyBody}
                        onChange={(e) => setReplyBody(e.target.value)} rows="3" />
              <div className="c-replybox-actions">
                <button className="c-replycancel" onClick={() => { setReplyTo(null); setReplyBody(""); }}>Cancel</button>
                <button className="cf-submit" onClick={() => submitReply(c.id)} disabled={!replyBody.trim() || posting}>
                  {posting ? "Posting…" : "Post reply"}
                </button>
              </div>
            </div>
          )}
          {!isReply && (repliesByParent[c.id] || []).length > 0 && (
            <ul className="c-replies">
              {repliesByParent[c.id].map(r => <CommentItem key={r.id} c={r} isReply={true} />)}
            </ul>
          )}
        </div>
      </li>
    );
  }

  // Newest top-level first
  const ordered = [...topLevel].reverse();

  return (
    <div className="comments">
      <div className="comments-top">
        <div className="rating-aggregate">
          <div className="ra-big">{total ? avg.toFixed(1) : "—"}</div>
          <div className="ra-side">
            <div className="ra-stars">
              {[1,2,3,4,5].map(n => (
                <span key={n} className={avg >= n - 0.25 ? "ra-star on" : (avg >= n - 0.75 ? "ra-star half" : "ra-star")}>★</span>
              ))}
            </div>
            <div className="ra-meta">Based on {total} reader rating{total === 1 ? "" : "s"}</div>
          </div>
        </div>
        <div className="rating-dist">
          {distribution.map(d => {
            const pct = total ? (d.count / total) * 100 : 0;
            return (
              <div className="rd-row" key={d.stars}>
                <span className="rd-stars">{d.stars}★</span>
                <div className="rd-bar"><span style={{ width: pct + "%" }} /></div>
                <span className="rd-count">{d.count}</span>
              </div>
            );
          })}
        </div>
      </div>

      <div className="comments-head" id="comments-list-head">
        <h4>Reader discussion</h4>
        <span>{loading ? "loading…" : (topLevel.length + " comment" + (topLevel.length === 1 ? "" : "s") + " · newest first")}</span>
      </div>

      {!loading && topLevel.length === 0 && (
        <div className="comments-empty">
          <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4" aria-hidden="true"><path d="M21 11.5a8.38 8.38 0 0 1-8.5 8.5 8.5 8.5 0 0 1-3.8-.9L3 21l1.9-5.7a8.5 8.5 0 0 1-.9-3.8A8.38 8.38 0 0 1 12.5 3 8.38 8.38 0 0 1 21 11.5z"/></svg>
          <p>No comments yet — be the first to make the case. Does it belong this high? What did we get wrong?</p>
          <button className="comments-empty-cta" onClick={startComment}>
            Start the discussion
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" aria-hidden="true"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
          </button>
        </div>
      )}

      <ul className="comments-list">
        {ordered.map((c) => <CommentItem key={c.id} c={c} isReply={false} />)}
      </ul>

      <div className="comment-form" id="comment-form">
        <h5>Add to the conversation</h5>
        {me ? (
          <>
            <div className="cf-asyou">Posting as <b>{me.name}</b></div>
            <div className="cf-rating">
              <span>Your rating:</span>
              {[1,2,3,4,5].map(n => (
                <button key={n} onClick={() => setDraftRating(n)} className={n <= draftRating ? "cf-star on" : "cf-star"}>★</button>
              ))}
            </div>
            <textarea placeholder="Say something thoughtful. Be specific, cite timestamps, disagree well."
                      ref={cfBodyRef} value={draftBody} onChange={(e) => setDraftBody(e.target.value)}
                      className="cf-body" rows="4" />
            <div className="cf-actions">
              <span className="cf-note">Be specific. Cite timestamps. Disagree well.</span>
              <button className="cf-submit" onClick={submitComment} disabled={!draftBody.trim() || posting}>
                {posting ? "Posting…" : "Post comment"}
              </button>
            </div>
          </>
        ) : (
          <div className="cf-signin">
            <p>Sign in to join the discussion. Comments are tied to your account.</p>
            <button className="cf-submit" onClick={() => window.__bbrRequireAuth && window.__bbrRequireAuth("comment")}>
              Sign in to comment
            </button>
          </div>
        )}
      </div>
    </div>
  );
}


function avatarColor(name) {
  const colors = ["#d97757", "#2d5d3a", "#7a2424", "#2d3a4a", "#a56300", "#3a322a"];
  let h = 0;
  for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
  return colors[h % colors.length];
}
function initials(name) {
  return name.split(/\s+/).slice(0, 2).map(s => s[0] || "").join("").toUpperCase();
}

// Export to window
Object.assign(window, {
  PressingLedger, ChartsVisual, SceneGrid, CommentsSection
});
