// ValuationsAdmin — /valuations-admin, the editor queue for values (F1, 1 Oct 2026).
//
// AUTH IS THE /partner-admin PATTERN, unchanged: paste the shared WEBHOOK_SECRET,
// it lives in React state only, and every request carries it as x-webhook-secret
// for api/reprice.js to check. Unlinked from the nav, noindex, no-store.
//
// WHAT COMES HERE. Every value somebody doubts, grouped by PRESSING, because that
// is what a ruling acts on:
//   * Flagged    a collector pressed "This looks wrong" on a copy of it
//   * Review     the market check held the guide at low confidence (an older
//                pressing whose guide sits far above its cheapest copy)
//   * Corrected  the market check left the guide out by itself; a ruling can
//                confirm it, or put the guide back when the check got it wrong
//   * Rulings    pressings with a ruling in force, and the way to withdraw one
//   * Resolved   the last 30 flags answered, and whether their owner was thanked
//
// THE ONE LEVER is a ruling on the pressing's price guide, never a typed-in value:
// "Guide is right" or "Guide is wrong". It applies to every copy at once and on
// every reprice after. A flag a ruling can't answer (wrong pressing, wrong
// condition, something else) is closed by hand, as fixed or not, with a note.
// Every note here is shown to the collectors whose flags it resolves.
(function () {
  const { useState, useCallback } = React;

  async function call(secret, body) {
    const r = await fetch("/api/reprice", {
      method: "POST",
      headers: { "Content-Type": "application/json", "x-webhook-secret": secret },
      body: JSON.stringify(Object.assign({ kind: "editor" }, body)),
    });
    let json = null;
    try { json = await r.json(); } catch (e) { /* non-JSON */ }
    if (!r.ok) {
      const err = new Error((json && json.error) || "failed");
      err.status = r.status;
      throw err;
    }
    return json;
  }

  const gbp = (n) => n == null ? "—" : "£" + Number(n).toLocaleString("en-GB", { minimumFractionDigits: Number(n) % 1 ? 2 : 0, maximumFractionDigits: 2 });
  const day = (iso) => {
    if (!iso) return "—";
    const d = new Date(iso);
    return isNaN(d) ? "—" : d.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
  };
  const grade = (g) => g ? String(g).replace(/_PLUS$/i, "+").replace(/_MINUS$/i, "-") : "not set";
  const REASON = {
    too_high: "Too high", too_low: "Too low", wrong_pressing: "Wrong pressing",
    wrong_condition: "Wrong condition", other: "Something else",
  };
  // The reasons a ruling on the guide can answer by itself (api/reprice.js edBorneOut).
  const RULED_REASONS = { too_high: 1, too_low: 1 };
  const VERDICT = { ok: "agrees", correct: "guide left out", review: "held for review" };
  const RESOLUTION = { corrected: "Borne out", kept: "Value stands", dismissed: "Closed" };
  const THANKED = { sent: "thanked by email", suppressed: "not emailed: suppressed", skipped: "not emailed: no address", failed: "thank-you failed" };

  function MarketStrip({ m }) {
    if (!m) return <p className="pt-quiet va-market">No market check on record for this pressing.</p>;
    return (
      <div className="va-market">
        <span><b>{gbp(m.lowest)}</b> cheapest of {m.for_sale == null ? "?" : m.for_sale} for sale</span>
        <span>{m.have == null ? "?" : m.have.toLocaleString("en-GB")} own it</span>
        {m.ratio != null && <span>guide is <b>{m.ratio}&times;</b> the cheapest</span>}
        <span>check: {VERDICT[m.verdict] || m.verdict || "—"}{m.observed && m.observed !== m.verdict ? ` (latest reads ${VERDICT[m.observed] || m.observed})` : ""}</span>
        <span className="pt-dim">{m.stale ? "carried from " : "read "}{day(m.checked_on)}{m.pending_verdict ? `, ${VERDICT[m.pending_verdict] || m.pending_verdict} pending` : ""}</span>
      </div>
    );
  }

  function Copies({ copies }) {
    if (!copies.length) return <p className="pt-quiet">No copies on any shelf now.</p>;
    return (
      <div className="pta-tablewrap">
        <table className="pta-table va-copies">
          <thead><tr><th>Owner</th><th>Grade</th><th>Guide at grade</th><th>Value now</th><th>Confidence</th></tr></thead>
          <tbody>
            {copies.map((c) => (
              <tr key={c.id}>
                <td>{c.owner}</td>
                <td>{grade(c.grade)}</td>
                <td className="mono">{c.guide ? <>{gbp(c.guide.value)}{c.guide.held ? <span className="va-held"> left out</span> : null}</> : "—"}</td>
                <td className="mono">{gbp(c.value)}</td>
                <td>{c.confidence || "—"}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }

  // One open flag. A ruling answers a too-high or too-low flag by itself; any flag
  // can also be closed by hand, which is the only way to close the other reasons.
  // `ownNote` gives the flag its own note box (a flag with no pressing card).
  function FlagLine({ f, busy, onClose, ruled, ownNote }) {
    const [armed, setArmed] = useState(null);   // "fixed" | "kept" | null
    const [note, setNote] = useState("");
    return (
      <li className="va-flag">
        <div className="va-flag-h">
          <span className="pta-pill placed">{REASON[f.reason] || f.reason}</span>
          <span>{f.owner}, {day(f.created_at)}, at {gbp(f.value_at_flag)}</span>
          {f.market_verdict && <span className="pt-dim">(check then: {VERDICT[f.market_verdict] || f.market_verdict})</span>}
        </div>
        {f.note && <blockquote className="va-flag-note">{f.note}</blockquote>}
        {ownNote && (
          <label className="va-note">
            <span>Note, shown to this collector</span>
            <textarea rows={2} maxLength={1000} value={note} onChange={(e) => setNote(e.target.value)} />
          </label>
        )}
        {onClose && (
          <div className="pta-rowacts va-flag-acts">
            <span className="pt-dim">{RULED_REASONS[f.reason] && !ruled ? "A ruling answers this one, or close it by hand:" : "Close it by hand:"}</span>
            {armed
              ? <>
                  <button className="pt-link" disabled={busy} onClick={() => { setArmed(null); onClose(f, armed === "fixed", ownNote ? note : undefined); }}>
                    Confirm: close as {armed === "fixed" ? "fixed, and thank them" : "no change"}
                  </button>
                  <button className="pt-link va-quiet" disabled={busy} onClick={() => setArmed(null)}>cancel</button>
                </>
              : <>
                  <button className="pt-link" disabled={busy} onClick={() => setArmed("fixed")}>Close as fixed</button>
                  <button className="pt-link" disabled={busy} onClick={() => setArmed("kept")}>Close, no change</button>
                </>}
          </div>
        )}
      </li>
    );
  }

  function PressingCard({ p, busy, onDecide, onUndo, onClose, result }) {
    const [note, setNote] = useState(p.ruling && p.ruling.note || "");
    const [armed, setArmed] = useState(null);   // "guide_ok" | "guide_wrong" | "undo" | null
    const name = [p.artist, p.title].filter(Boolean).join(" — ") || "Untitled";
    const n = p.copies.length;
    const now = p.ruling && p.ruling.decision;
    const label = { guide_ok: now ? "Change to: guide is right" : "Guide is right",
                    guide_wrong: now ? "Change to: guide is wrong" : "Guide is wrong", undo: "Withdraw the ruling" };
    const go = () => {
      const a = armed; setArmed(null);
      if (a === "undo") onUndo(p); else onDecide(p, a, note);
    };
    return (
      <article className={"va-card va-" + p.state}>
        <header className="va-card-h">
          <h2 className="va-card-t">{name}{p.year ? <span className="pt-dim"> ({p.year})</span> : null}</h2>
          <span className="va-links">
            <a href={`https://www.discogs.com/release/${p.release_id}`} target="_blank" rel="noopener noreferrer">release {p.release_id}</a>
            {" · "}
            <a href={`https://www.discogs.com/sell/release/${p.release_id}`} target="_blank" rel="noopener noreferrer">for sale</a>
          </span>
        </header>
        <MarketStrip m={p.market} />
        {/* A pressing in a deep dive's Collector's Corner (dossier_market_checks).
            The page prices it as a shelf would, so this ruling moves the page too,
            on its next daily refresh. */}
        {p.dossier && (
          <p className="va-dossier">
            In Collector&rsquo;s Corner: <a href={"/deep-dive/" + p.dossier.slug} target="_blank" rel="noopener noreferrer">{p.dossier.pressing || p.dossier.pressing_id}</a>
            {n ? null : ". Nobody owns it yet."}
          </p>
        )}
        {p.ruling && (
          <p className="va-ruling">
            <b>{p.ruling.decision === "guide_ok" ? "Ruled: the guide is right" : "Ruled: the guide is wrong"}</b>, {day(p.ruling.decided_at)}.
            {p.ruling.note ? <> &ldquo;{p.ruling.note}&rdquo;</> : null}
          </p>
        )}
        <Copies copies={p.copies} />
        {p.flags.length > 0 && (
          <ul className="va-flags">
            {p.flags.map((f) => <FlagLine key={f.id} f={f} busy={busy} ruled={!!now} onClose={(ff, fixed) => onClose(ff, fixed, note)} />)}
          </ul>
        )}
        <label className="va-note">
          <span>Note, shown to the collectors whose flags this resolves</span>
          <textarea rows={2} maxLength={1000} value={note} onChange={(e) => setNote(e.target.value)}
            placeholder="What you checked: sold prices, the matrix, the pressing's own listings" />
        </label>
        <div className="pta-rowacts va-acts">
          {armed
            ? <>
                <button className="pt-cta" disabled={busy} onClick={go}>
                  {armed === "undo" ? "Confirm: withdraw, and reprice" : `Confirm: ${label[armed].replace(/^Change to: /, "").toLowerCase()}, and reprice`} {n} {n === 1 ? "copy" : "copies"}
                </button>
                <button className="pt-cta pt-ghost" disabled={busy} onClick={() => setArmed(null)}>Cancel</button>
              </>
            : <>
                {now !== "guide_ok" && <button className="pt-cta" disabled={busy} onClick={() => setArmed("guide_ok")}>{label.guide_ok}</button>}
                {now !== "guide_wrong" && <button className="pt-cta" disabled={busy} onClick={() => setArmed("guide_wrong")}>{label.guide_wrong}</button>}
                {now && <button className="pt-cta pt-ghost" disabled={busy} onClick={() => setArmed("undo")}>{label.undo}</button>}
              </>}
        </div>
        {result && <Result r={result} />}
      </article>
    );
  }

  // What an action did, in the editor's words: every copy's before and after, the
  // tier, and each flag it answered.
  function Result({ r }) {
    if (r.error) return <p className="pt-err">{r.error}</p>;
    const vals = r.values || [];
    return (
      <div className="va-result" role="status">
        <p>
          {r.undone ? "Ruling withdrawn. " : r.decision ? (r.decision === "guide_ok" ? "Ruled: the guide is right. " : "Ruled: the guide is wrong. ") : ""}
          {r.copies != null ? <>{r.copies} {r.copies === 1 ? "copy" : "copies"} repriced{r.decision ? <>, {r.ruled} under the ruling</> : null}{r.refused ? <>, {r.refused} held by the step guard</> : null}.</> : null}
          {r.tier ? <> Tier now <b>{r.tier.tier}</b>.</> : null}
          {r.rateLimited ? <> Discogs rate-limited this run: check again in a minute.</> : null}
        </p>
        {vals.length > 0 && (
          <ul className="va-moves">
            {vals.map((v) => <li key={v.id} className="mono">{gbp(v.before)} &rarr; {gbp(v.after)}{v.confidence ? ` (${v.confidence})` : ""}</li>)}
          </ul>
        )}
        {(r.flags || []).map((f) => (
          <p key={f.id} className="pt-dim">
            Flag {f.id} ({REASON[f.reason] || f.reason}): {f.resolution ? RESOLUTION[f.resolution] || f.resolution : "still open: close it by hand"}
            {f.thanked ? `, ${THANKED[f.thanked] || f.thanked}` : ""}.
          </p>
        ))}
        {r.flag && <p>Flag {r.flag.id} closed: {RESOLUTION[r.flag.resolution] || r.flag.resolution}{r.thanked ? `, ${THANKED[r.thanked] || r.thanked}` : ""}.</p>}
        {r.thanked && !r.flags && !r.flag && <p className="pt-dim">{THANKED[r.thanked] || r.thanked}.</p>}
      </div>
    );
  }

  function Resolved({ rows, busy, onThank, results }) {
    if (!rows.length) return <p className="pt-quiet">No flags resolved yet.</p>;
    return (
      <div className="pta-tablewrap">
        <table className="pta-table">
          <thead><tr><th>Resolved</th><th>Record</th><th>Flag</th><th>Outcome</th><th>Note</th><th>Owner told</th></tr></thead>
          <tbody>
            {rows.map((f) => {
              const res = results["thank:" + f.id];
              return (
                <tr key={f.id}>
                  <td className="mono">{day(f.resolved_at)}</td>
                  <td>{[f.artist, f.title].filter(Boolean).join(" — ") || "—"}<div className="pt-dim">{f.owner}</div></td>
                  <td>{REASON[f.reason] || f.reason}<div className="pt-dim mono">{gbp(f.value_at_flag)}</div></td>
                  <td>{RESOLUTION[f.resolution] || f.resolution || f.status}</td>
                  <td className="va-cell-note">{f.editor_note || <span className="pt-dim">—</span>}</td>
                  <td>
                    {f.notified_at
                      ? <>thanked {day(f.notified_at)}</>
                      : f.resolution === "corrected"
                        ? (res ? (THANKED[res.thanked] || res.error || "—")
                               : <button className="pt-link" disabled={busy} onClick={() => onThank(f)}>Send the thank-you</button>)
                        : <span className="pt-dim">on their record page</span>}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    );
  }

  function ValuationsAdmin({ onHome }) {
    const [secret, setSecret] = useState("");
    const [authed, setAuthed] = useState(false);
    const [busy, setBusy] = useState(false);
    const [msg, setMsg] = useState("");
    const [q, setQ] = useState(null);
    const [tab, setTab] = useState(null);
    const [results, setResults] = useState({});   // "rid:<id>" | "flag:<id>" | "thank:<id>" -> result
    const [last, setLast] = useState(null);       // { name, out }: an action moves its card to another tab, so say it up top

    const refresh = useCallback(async (sec) => {
      const out = await call(sec || secret, { action: "queue" });
      setQ(out);
      setAuthed(true);
      return out;
    }, [secret]);

    const run = async (fn) => {
      setBusy(true); setMsg("");
      try { await fn(); }
      catch (e) { setMsg(e.status === 401 ? "That secret isn't right." : (e.message || "Failed.")); }
      finally { setBusy(false); }
    };
    const act = (key, name, body) => run(async () => {
      let out;
      try { out = await call(secret, body); }
      catch (e) { out = { error: e.status === 401 ? "That secret isn't right." : (e.message || "Failed.") }; }
      setResults((r) => Object.assign({}, r, { [key]: out }));
      setLast({ name, out });
      if (!out.error) await refresh();
    });
    const nameOf = (x) => [x.artist, x.title].filter(Boolean).join(" — ") || "Untitled";

    if (!authed) {
      return (
        <div className="pta-wrap" style={{ maxWidth: 460, margin: "14vh auto" }}>
          <h1 className="pt-h1" style={{ fontSize: 26 }}>Valuations</h1>
          <p className="pt-quiet">Enter the shared secret to see the values collectors and the market check have doubted, and rule on them.</p>
          <input type="password" placeholder="WEBHOOK_SECRET" value={secret}
                 onChange={(e) => setSecret(e.target.value)}
                 onKeyDown={(e) => { if (e.key === "Enter") run(() => refresh(secret)); }}
                 style={{ width: "100%", padding: "10px 12px", marginBottom: 12, font: "inherit" }} />
          <button className="pt-cta" disabled={busy || !secret} onClick={() => run(() => refresh(secret))}>
            {busy ? "Checking…" : "Unlock"}
          </button>
          {msg && <p className="pt-err">{msg}</p>}
        </div>
      );
    }

    const all = (q && q.pressings) || [];
    const by = (s) => all.filter((p) => p.state === s);
    const groups = [
      ["flagged", "Flagged", by("flagged"), "A collector pressed “This looks wrong” on a copy. A ruling answers a too-high or too-low flag by itself: borne out if the value moves 10% their way, and they’re thanked by email."],
      ["review", "Held for review", by("review"), "The guide sits far above the cheapest copy of an older pressing, so it’s kept at low confidence until someone looks. Right: it stands at full confidence. Wrong: it’s priced from its own market."],
      ["correct", "Corrected", by("correct"), "The market check left the guide out by itself. Confirm it, or put the guide back where the check got it wrong."],
      ["ruled", "Rulings", by("ruled"), "In force on every copy, on every reprice, until withdrawn. Withdrawing hands the pressing back to the market check, which starts afresh."],
    ];
    const unmatched = (q && q.unmatched) || [];
    const resolved = (q && q.resolved) || [];
    const current = tab || (groups.find((g) => g[2].length || (g[0] === "flagged" && unmatched.length)) || groups[1])[0];

    return (
      <div className="pta-wrap va-wrap">
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", flexWrap: "wrap", gap: 12 }}>
          <h1 className="pt-h1" style={{ fontSize: 26, marginBottom: 4 }}>Valuations</h1>
          <span className="pta-rowacts">
            <button className="pt-link" disabled={busy} onClick={() => run(() => refresh())}>{busy ? "Working…" : "Refresh"}</button>
            <button className="pt-link" onClick={onHome}>← site</button>
          </span>
        </div>
        {q && q.truncated && <p className="pt-err">A list hit the 1,000-row read limit, so this queue is incomplete.</p>}
        {msg && <p className="pt-err">{msg}</p>}
        {last && (
          <div className="va-last">
            <span className="va-last-k">Last action: {last.name}</span>
            <Result r={last.out} />
            <button className="pt-link va-quiet" onClick={() => setLast(null)}>dismiss</button>
          </div>
        )}
        <div className="pta-tabs" role="tablist">
          {groups.map(([key, name, list]) => (
            <button key={key} role="tab" aria-selected={current === key} className={"pta-tab" + (current === key ? " on" : "")} onClick={() => setTab(key)}>
              {name} <span className="va-count">{list.length + (key === "flagged" ? unmatched.length : 0)}</span>
            </button>
          ))}
          <button role="tab" aria-selected={current === "resolved"} className={"pta-tab" + (current === "resolved" ? " on" : "")} onClick={() => setTab("resolved")}>
            Resolved <span className="va-count">{resolved.length}</span>
          </button>
        </div>
        {groups.filter((g) => g[0] === current).map(([key, , list, blurb]) => (
          <section key={key}>
            <p className="pt-quiet">{blurb}</p>
            {!list.length && !(key === "flagged" && unmatched.length) && <p className="pt-quiet"><b>Nothing here.</b></p>}
            {list.map((p) => (
              <PressingCard key={p.release_id + ":" + p.state} p={p} busy={busy} result={results["rid:" + p.release_id]}
                onDecide={(pp, decision, note) => act("rid:" + pp.release_id, nameOf(pp), { action: "decide", release_id: pp.release_id, decision, note })}
                onUndo={(pp) => act("rid:" + pp.release_id, nameOf(pp), { action: "undo", release_id: pp.release_id })}
                onClose={(f, fixed, note) => act("rid:" + p.release_id, nameOf(p), { action: "close", flag_id: f.id, fixed, note })} />
            ))}
            {key === "flagged" && unmatched.length > 0 && (
              <article className="va-card va-flagged">
                <header className="va-card-h"><h2 className="va-card-t">Not matched to a pressing</h2></header>
                <p className="pt-quiet">No ruling can reach these: the record has no pressing (or no longer exists). Close each by hand.</p>
                <ul className="va-flags">
                  {unmatched.map((f) => (
                    <React.Fragment key={f.id}>
                      <li className="va-flag va-flag-rec">{[f.artist, f.title].filter(Boolean).join(" — ") || "Untitled"}</li>
                      <FlagLine f={f} busy={busy} ruled ownNote
                        onClose={(ff, fixed, note) => act("flag:" + f.id, nameOf(f), { action: "close", flag_id: f.id, fixed, note })} />
                      {results["flag:" + f.id] && <Result r={results["flag:" + f.id]} />}
                    </React.Fragment>
                  ))}
                </ul>
              </article>
            )}
          </section>
        ))}
        {current === "resolved" && (
          <section>
            <p className="pt-quiet">The last 30 flags answered. A borne-out flag&rsquo;s owner is thanked once by email; everyone sees the outcome on their record page.</p>
            <Resolved rows={resolved} busy={busy} results={results}
              onThank={(f) => act("thank:" + f.id, nameOf(f), { action: "thank", flag_id: f.id })} />
          </section>
        )}
      </div>
    );
  }

  window.ValuationsAdmin = ValuationsAdmin;
})();
