// Composite Bracket Checker — the React island on /commander-bracket-checker.
// The page's SEO content is static HTML in bracketcheck.html; this island is
// ONLY the tool: input surface, composite verdict, spread chart, grader rows,
// signal breakdown. Verdict-first (DISPLAY LAW), spread ships as a chart
// (RATIO LAW), the grade is the reward for the click (LANDER LAW: no wall).

const { useState, useEffect, useRef, useMemo } = React;

const T = {
  accent: "var(--cm-accent)", accentFg: "var(--cm-accent-fg)", accentSoft: "var(--cm-accent-soft)",
  bg: "var(--bg)", bg2: "var(--bg-2)", bg3: "var(--bg-3)",
  line: "var(--line)", ink: "var(--ink)", ink2: "var(--ink-2)", ink3: "var(--ink-3)",
  good: "var(--good)",
  sans: "var(--cm-sans)", mono: "var(--cm-mono)",
};

const BRACKET_LABELS = { 1: "Exhibition", 2: "Core", 3: "Upgraded", 4: "Optimized", 5: "cEDH" };
const MONOGRAMS = { spellstash: "SS", deckflow: "DF", "mtg-analyzer": "MA", commanderbracket: "CB", arcmind: "AM", moxfield: "MX" };

const card = { background: T.bg2, border: "1px solid " + T.line, borderRadius: 14, padding: 16 };

function useDebounced(value, ms) {
  const [v, setV] = useState(value);
  useEffect(() => { const t = setTimeout(() => setV(value), ms); return () => clearTimeout(t); }, [value, ms]);
  return v;
}

// ── Commander typeahead (optional helper, public autocomplete) ──────────────
function CommanderField({ value, onChange }) {
  const [open, setOpen] = useState(false);
  const [hits, setHits] = useState([]);
  const q = useDebounced(value, 220);
  const picked = useRef(false);

  useEffect(() => {
    if (picked.current) { picked.current = false; return; }
    let dead = false;
    if (!q || q.length < 3) { setHits([]); setOpen(false); return; }
    (async () => {
      try {
        const r = await window.ssAuth.authedFetch("/api/composite/commanders?q=" + encodeURIComponent(q.slice(0, 60)));
        if (!r.ok) return;
        const j = await r.json();
        if (!dead) { setHits((j.results || []).slice(0, 6)); setOpen(true); }
      } catch { /* typeahead is a helper, never an error state */ }
    })();
    return () => { dead = true; };
  }, [q]);

  return (
    <div style={{ position: "relative" }}>
      <input
        value={value}
        onChange={(e) => onChange(e.target.value)}
        onBlur={() => setTimeout(() => setOpen(false), 150)}
        placeholder="Commander"
        aria-label="Commander name"
        style={{ width: "100%", height: 44, padding: "0 12px", font: "500 15px " + T.sans, color: T.ink, background: T.bg, border: "1px solid " + T.line, borderRadius: 10, boxSizing: "border-box" }}
      />
      {open && hits.length > 0 && (
        <div style={{ position: "absolute", top: 46, left: 0, right: 0, zIndex: 30, background: T.bg2, border: "1px solid " + T.line, borderRadius: 10, overflow: "hidden", boxShadow: "0 8px 24px rgba(0,0,0,0.12)" }}>
          {hits.map((h) => (
            <button key={h.name} type="button"
              onMouseDown={() => { picked.current = true; onChange(h.name); setOpen(false); }}
              style={{ display: "flex", alignItems: "center", gap: 8, width: "100%", padding: "8px 12px", font: "500 14px " + T.sans, color: T.ink, background: "transparent", border: "none", cursor: "pointer", textAlign: "left" }}>
              {h.image_small ? <img src={h.image_small} alt="" style={{ width: 24, height: 33, borderRadius: 3, objectFit: "cover" }} /> : <span style={{ width: 24 }} />}
              {h.name}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ── The spread chart: dots on a 1-5 axis, composite diamond on top ──────────
function SpreadChart({ result }) {
  // Every grader gets a dot (Dwayne 8-25). Partner dots render dashed: their
  // call shows on the axis, but the composite math stays local-only and the
  // note below says so.
  const graders = result.graders.filter((g) => g.status === "ok");
  const byBracket = {};
  for (const g of graders) (byBracket[g.bracket] = byBracket[g.bracket] || []).push(g);
  const comp = result.composite.bracket;
  const pct = (b) => ((b - 1) / 4) * 100;

  return (
    <div style={{ ...card, marginTop: 12 }} aria-label={"Grader spread from Bracket " + result.composite.span[0] + " to Bracket " + result.composite.span[1]}>
      <div style={{ font: "600 11px " + T.mono, letterSpacing: "0.08em", color: T.ink3 }}>THE SPREAD</div>
      <div style={{ position: "relative", height: 210, margin: "0 14px" }}>
        {/* composite diamond above everything on its bracket */}
        <div style={{ position: "absolute", left: pct(comp) + "%", top: 0, transform: "translateX(-50%)", textAlign: "center", zIndex: 2 }}>
          <div style={{ width: 16, height: 16, background: T.accent, transform: "rotate(45deg)", margin: "0 auto", borderRadius: 3 }} />
          <div style={{ font: "700 9px " + T.mono, color: T.accent, letterSpacing: "0.06em", marginTop: 4 }}>COMPOSITE</div>
        </div>
        {/* the track */}
        <div style={{ position: "absolute", left: 0, right: 0, top: 170, height: 2, background: T.line }} />
        {[1, 2, 3, 4, 5].map((b) => (
          <div key={b} style={{ position: "absolute", left: pct(b) + "%", top: 0, bottom: 0, transform: "translateX(-50%)" }}>
            {/* dot tower anchored just above the track, growing upward */}
            <div style={{ position: "absolute", bottom: 44, left: "50%", transform: "translateX(-50%)", display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
              {(byBracket[b] || []).map((g) => (
                <div key={g.key} title={g.label + ": Bracket " + g.bracket} aria-label={g.label + ": Bracket " + g.bracket}
                  style={{ width: 22, height: 22, borderRadius: "50%", background: T.bg3, border: (g.kind === "remote" ? "2px dashed " : "2px solid ") + T.ink2, display: "flex", alignItems: "center", justifyContent: "center", font: "700 9px " + T.mono, color: T.ink }}>
                  {MONOGRAMS[g.key] || g.key.slice(0, 2).toUpperCase()}
                </div>
              ))}
            </div>
            {/* tick + labels, same height on every stop */}
            <div style={{ position: "absolute", top: 166, left: "50%", transform: "translateX(-50%)", textAlign: "center" }}>
              <div style={{ width: 2, height: 10, background: T.line, margin: "0 auto" }} />
              <div style={{ font: "600 12px " + T.mono, color: b === comp ? T.ink : T.ink3, marginTop: 3 }}>{b}</div>
              <div style={{ font: "500 9px " + T.mono, color: T.ink3 }}>{BRACKET_LABELS[b].slice(0, 6)}</div>
            </div>
          </div>
        ))}
      </div>
      {graders.some((g) => g.kind === "remote") && (
        <div style={{ font: "500 10.5px " + T.mono, color: T.ink3, marginTop: 8 }}>
          Dashed dots are partner graders. Their call shows here and never feeds the composite.
        </div>
      )}
    </div>
  );
}

// ── Game Changer count vs bracket allowance strip ───────────────────────────
function GcStrip({ count }) {
  const fits = (b) => {
    if (b <= 2) return count === 0;
    if (b === 3) return count <= 3;
    return true;
  };
  return (
    <div style={{ display: "flex", gap: 4, margin: "6px 0" }}>
      {[1, 2, 3, 4, 5].map((b) => (
        <div key={b} style={{ flex: 1, textAlign: "center", padding: "5px 0", borderRadius: 6, font: "600 11px " + T.mono, background: fits(b) ? T.accentSoft : T.bg3, color: fits(b) ? T.ink : T.ink3, textDecoration: fits(b) ? "none" : "line-through" }}>
          B{b}
        </div>
      ))}
    </div>
  );
}

function SignalBreakdown({ signals }) {
  const [open, setOpen] = useState(false);
  const c = signals.counts;
  const chip = (label, n) => label + " " + n;
  const chips = [chip("GC", c.gc), chip("Combos", signals.combos_checked ? c.two_card_combos : "?"), chip("MLD", c.mld), chip("Turns", c.extra_turns), chip("Tutors", c.tutors)].join(" · ");

  const list = (title, items, note) => (
    <div style={{ marginTop: 14 }}>
      <div style={{ font: "700 13px " + T.sans, color: T.ink, marginBottom: 4 }}>{title}</div>
      {items.length === 0
        ? <div style={{ font: "400 13px " + T.sans, color: T.ink3 }}>{note || "None found."}</div>
        : <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            {items.map((x) => (
              <span key={x.name} style={{ font: "500 12.5px " + T.sans, color: T.ink2, background: T.bg3, border: "1px solid " + T.line, borderRadius: 999, padding: "4px 10px" }}>{x.name}</span>
            ))}
          </div>}
    </div>
  );

  return (
    <div style={{ ...card, marginTop: 12 }}>
      <button type="button" onClick={() => setOpen(!open)} aria-expanded={open}
        style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: "transparent", border: "none", cursor: "pointer", padding: 0, minHeight: 32 }}>
        <span style={{ font: "700 14.5px " + T.sans, color: T.ink }}>What the graders saw</span>
        <span style={{ font: "500 12px " + T.mono, color: T.ink3 }}>{chips} {open ? "▴" : "▾"}</span>
      </button>
      {open && (
        <div>
          <div style={{ marginTop: 12 }}>
            <div style={{ font: "700 13px " + T.sans, color: T.ink, marginBottom: 2 }}>Game Changers: {c.gc} found</div>
            <GcStrip count={c.gc} />
            <div style={{ font: "400 12px " + T.sans, color: T.ink3 }}>{c.gc === 0 ? "None found. Every bracket allows that." : c.gc <= 3 ? "Bracket 3 allows up to three." : "Four or more is the Bracket 4 floor."}</div>
            {signals.game_changers.length > 0 && (
              <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 6 }}>
                {signals.game_changers.map((x) => (
                  <span key={x.name} style={{ font: "500 12.5px " + T.sans, color: T.ink2, background: T.bg3, border: "1px solid " + T.line, borderRadius: 999, padding: "4px 10px" }}>{x.name}</span>
                ))}
              </div>
            )}
          </div>
          {signals.combos_checked
            ? (signals.two_card_combos.length > 0
              ? <div style={{ marginTop: 14 }}>
                  <div style={{ font: "700 13px " + T.sans, color: T.ink, marginBottom: 4 }}>Two-card combos: {c.two_card_combos}</div>
                  {signals.two_card_combos.map((v, i) => (
                    <div key={i} style={{ font: "400 13px " + T.sans, color: T.ink2, padding: "5px 0", borderTop: i === 0 ? "none" : "1px solid " + T.line, display: "flex", justifyContent: "space-between", gap: 8 }}>
                      <span>{v.cards.join(" + ")}{typeof v.mana_value_needed === "number" ? (v.mana_value_needed <= 4 ? " · early" : " · late") : ""}</span>
                      {v.spellbook_url && <a href={v.spellbook_url} target="_blank" rel="noopener" style={{ color: T.accent, font: "500 12px " + T.sans, flexShrink: 0 }}>Spellbook</a>}
                    </div>
                  ))}
                </div>
              : list("Two-card combos", [], "None found by Commander Spellbook."))
            : list("Two-card combos", [], "Combo check was not available for this run. Graders scored without it.")}
          {list("Mass land denial", signals.mass_land_denial)}
          {list("Extra turns", signals.extra_turns)}
          {list("Tutors", signals.tutors)}
        </div>
      )}
    </div>
  );
}

function GraderRows({ result }) {
  return (
    <div style={{ ...card, marginTop: 12, padding: "4px 16px" }}>
      {result.graders.map((g, i) => (
        <div key={g.key} style={{ display: "flex", gap: 12, alignItems: "flex-start", padding: "12px 0", borderTop: i === 0 ? "none" : "1px solid " + T.line }}>
          <div style={{ width: 40, height: 40, borderRadius: 10, background: T.bg3, border: "1px solid " + T.line, display: "flex", alignItems: "center", justifyContent: "center", font: "800 15px " + T.mono, color: T.ink, flexShrink: 0 }}>
            {g.status === "ok" ? "B" + g.bracket : "–"}
          </div>
          <div style={{ minWidth: 0 }}>
            <div style={{ font: "700 14.5px " + T.sans, color: T.ink }}>
              {g.detail_url ? <a href={g.detail_url} target="_blank" rel="noopener" style={{ color: T.ink, textDecoration: "none" }}>{g.label} ↗</a> : g.label}
            </div>
            <div style={{ font: "400 13.5px " + T.sans, color: T.ink2, marginTop: 2 }}>
              {g.status === "ok" ? (g.reason || (g.bracket_name ? "Bracket " + g.bracket + " · " + g.bracket_name : "Bracket " + g.bracket)) : "Grader unavailable right now."}
            </div>
            {g.status === "ok" && (g.win_turn || g.power_level != null) && (
              <div style={{ display: "flex", gap: 6, marginTop: 5, flexWrap: "wrap" }}>
                {g.win_turn && <span style={{ font: "600 11px " + T.mono, color: T.ink2, background: T.bg3, border: "1px solid " + T.line, borderRadius: 999, padding: "3px 9px" }}>Est. win turn {g.win_turn}</span>}
                {g.power_level != null && <span style={{ font: "600 11px " + T.mono, color: T.ink2, background: T.bg3, border: "1px solid " + T.line, borderRadius: 999, padding: "3px 9px" }}>Power {g.power_level}</span>}
              </div>
            )}
            {g.attribution && (
              <a href={g.attribution.url} target="_blank" rel="noopener" style={{ font: "500 10.5px " + T.mono, color: T.ink3, textDecoration: "none" }}>{g.attribution.name}</a>
            )}
          </div>
        </div>
      ))}
      <div style={{ padding: "12px 0", borderTop: "1px solid " + T.line, font: "400 13px " + T.sans, color: T.ink3 }}>
        {!result.graders.some((g) => g.key === "moxfield") && "Deck live on Moxfield? Paste its link above and Moxfield's own bracket joins the list. "}
        More graders are coming. Each one appears here the day its column goes live.
      </div>
    </div>
  );
}

function VerdictCard({ result, innerRef, onShare, shareState }) {
  const comp = result.composite;
  const cmdr = (result.commanders && result.commanders[0]) || null;
  const title = result.deck_name || (cmdr ? cmdr.name : "Pasted list");
  const cmdrLine = result.commanders && result.commanders.length > 0
    ? "Commander: " + result.commanders.map((c) => c.name).join(" + ")
    : null;
  return (
    <div ref={innerRef} style={{ ...card, marginTop: 20 }}>
      {/* deck header: art + deck name + commander, ScryCheck-style context */}
      <div style={{ display: "flex", alignItems: "center", gap: 12, paddingBottom: 12, marginBottom: 14, borderBottom: "1px solid " + T.line }}>
        {cmdr && cmdr.image_normal
          ? <img src={cmdr.image_normal} alt="" style={{ width: 46, height: 64, borderRadius: 5, objectFit: "cover", objectPosition: "top", background: T.bg3, flexShrink: 0 }} />
          : <div style={{ width: 46, height: 64, borderRadius: 5, background: T.bg3, flexShrink: 0 }} />}
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ font: "800 17px Archivo, " + T.sans, color: T.ink, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{title}</div>
          {cmdrLine && <div style={{ font: "500 11.5px " + T.mono, color: T.ink3, marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{cmdrLine}</div>}
        </div>
        {onShare && (
          <button type="button" onClick={onShare} disabled={shareState === "busy"}
            style={{ flexShrink: 0, height: 34, padding: "0 12px", borderRadius: 8, border: "1px solid " + (shareState === "copied" ? T.accent : T.line), background: shareState === "copied" ? T.accentSoft : "transparent", color: T.ink, font: "600 12px " + T.mono, cursor: "pointer" }}>
            {shareState === "copied" ? "Link copied" : shareState === "busy" ? "…" : shareState === "failed" ? "Share failed" : "Share"}
          </button>
        )}
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "118px 1fr", gap: 14, alignItems: "center" }}>
        <div style={{ font: "900 92px/1 Archivo, " + T.sans, color: T.accent, textAlign: "center" }} aria-label={"Composite bracket " + comp.bracket}>{comp.bracket}</div>
        <div style={{ minWidth: 0 }}>
          <div style={{ font: "600 11px " + T.mono, letterSpacing: "0.08em", color: T.ink3 }}>COMPOSITE VERDICT</div>
          <div style={{ font: "800 23px Archivo, " + T.sans, color: T.ink, margin: "2px 0" }}>Bracket {comp.bracket} · {comp.label}</div>
          <div style={{ font: "400 14.5px " + T.sans, color: T.ink2 }}>{comp.reason}</div>
          <div style={{ font: "500 11px " + T.mono, color: T.ink3, marginTop: 6 }}>
            {comp.method_line}{comp.shared_scan_note ? " " + comp.shared_scan_note : ""}
          </div>
        </div>
      </div>
    </div>
  );
}

function CtaCard({ session, mode }) {
  return (
    <div style={{ background: T.accentSoft, border: "1px solid " + T.accent, borderRadius: 14, padding: 16, marginTop: 12 }}>
      {session
        ? (mode === "deck"
          ? <>
              <div style={{ font: "800 17px Archivo, " + T.sans, color: T.ink }}>Want the full read on this deck?</div>
              <p style={{ font: "400 14px " + T.sans, color: T.ink2, margin: "4px 0 10px" }}>The Bracketizer diffs it against the bracket averages, card by card.</p>
              <a href="/bracketizer" style={{ display: "inline-block", padding: "11px 18px", background: T.accent, color: T.accentFg, borderRadius: 10, font: "700 14px " + T.sans, textDecoration: "none" }}>Open the Bracketizer</a>
            </>
          : <>
              <div style={{ font: "800 17px Archivo, " + T.sans, color: T.ink }}>Keep this list</div>
              <p style={{ font: "400 14px " + T.sans, color: T.ink2, margin: "4px 0 10px" }}>Save it as a deck in your stash and track every physical copy you own.</p>
              <a href="/collection" style={{ display: "inline-block", padding: "11px 18px", background: T.accent, color: T.accentFg, borderRadius: 10, font: "700 14px " + T.sans, textDecoration: "none" }}>Open your stash</a>
            </>)
        : <>
            <div style={{ font: "800 17px Archivo, " + T.sans, color: T.ink }}>Track this deck in Spellstash</div>
            <p style={{ font: "400 14px " + T.sans, color: T.ink2, margin: "4px 0 10px" }}>Know where every physical copy lives, and re-check the bracket in one tap. Free.</p>
            <a href="/login?next=/commander-bracket-checker" style={{ display: "inline-block", padding: "11px 18px", background: T.accent, color: T.accentFg, borderRadius: 10, font: "700 14px " + T.sans, textDecoration: "none" }}>Start free</a>
          </>}
    </div>
  );
}

function BracketCheckApp() {
  const [session, sessionLoading] = window.ssAuth.useSession();
  const [mode, setMode] = useState("paste"); // paste | deck
  const [commander, setCommander] = useState("");
  const [pasteText, setPasteText] = useState("");
  const [decks, setDecks] = useState(null);
  const [pickedDeck, setPickedDeck] = useState(null);
  const [deckCommanders, setDeckCommanders] = useState([]);
  const [busy, setBusy] = useState(false);
  const [stage, setStage] = useState("");
  const [error, setError] = useState(null);
  const [result, setResult] = useState(null);
  const [shareState, setShareState] = useState("idle");
  const [sharedView, setSharedView] = useState(false);
  const verdictRef = useRef(null);

  // ?s=<token> renders a shared verdict read-only (the input card stays live
  // above it, so the recipient can grade their own list next).
  useEffect(() => {
    const token = new URLSearchParams(window.location.search).get("s");
    if (!token) return;
    (async () => {
      try {
        const r = await window.ssAuth.authedFetch("/api/composite/share/" + encodeURIComponent(token));
        if (!r.ok) return;
        const j = await r.json();
        if (j && j.payload && j.payload.composite) { setResult(j.payload); setSharedView(true); }
      } catch { /* a dead share link just leaves the blank tool */ }
    })();
  }, []);

  const share = async () => {
    if (!result || shareState === "busy") return;
    setShareState("busy");
    try {
      const r = await window.ssAuth.authedFetch("/api/composite/share", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ result }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok || !j.share_url) { setShareState("failed"); return; }
      await navigator.clipboard.writeText(j.share_url);
      setShareState("copied");
      setTimeout(() => setShareState("idle"), 2500);
    } catch { setShareState("failed"); }
  };

  useEffect(() => {
    if (!session || decks !== null) return;
    (async () => {
      try {
        const r = await window.ssAuth.authedFetch("/api/containers");
        if (!r.ok) return;
        const j = await r.json();
        // 100-card floor (Dwayne 8-25): a Commander deck is 100 cards, and a
        // half-built list grades misleadingly low. Under-100 decks stay out
        // of the picker; paste mode still grades anything.
        setDecks((Array.isArray(j) ? j : []).filter((c) => c.kind === "deck" && (c.card_count || 0) >= 100));
      } catch { setDecks([]); }
    })();
  }, [session, decks]);

  useEffect(() => {
    if (result && verdictRef.current) verdictRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
  }, [result]);

  // Picking a deck surfaces ITS eligible commanders (legendary creatures in
  // the mainboard) as a dropdown, defaulted to the first (Dwayne 8-25).
  useEffect(() => {
    if (!pickedDeck) { setDeckCommanders([]); return; }
    let dead = false;
    (async () => {
      try {
        const r = await window.ssAuth.authedFetch("/api/containers/" + pickedDeck);
        if (!r.ok) return;
        const j = await r.json();
        const names = [];
        for (const inst of (j.instances || [])) {
          if (inst.is_sideboard) continue;
          const c = inst.card || inst.cards || inst;
          const tl = c.type_line || "";
          if (/Legendary/.test(tl) && /Creature/.test(tl) && c.name && !names.includes(c.name)) names.push(c.name);
        }
        if (!dead) {
          setDeckCommanders(names);
          if (names.length > 0) setCommander(names[0]);
        }
      } catch { /* dropdown is a helper; the server still infers */ }
    })();
    return () => { dead = true; };
  }, [pickedDeck]);

  const canRun = mode === "deck" ? pickedDeck != null : pasteText.trim().length > 0;

  const run = async () => {
    if (!canRun || busy) return;
    setBusy(true); setError(null); setResult(null);
    setStage("Reading the list…");
    const slowTimer = setTimeout(() => setStage("Checking combos with Commander Spellbook. A cold deck can take up to a minute."), 4000);
    try {
      const body = mode === "deck"
        ? { container_id: pickedDeck, commander: commander.trim() || undefined }
        : { decklist_text: pasteText, commander: commander.trim() || undefined };
      const r = await window.ssAuth.authedFetch("/api/composite/check", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) { setError(j.error || "bracket check failed. Try again in a minute."); return; }
      setShareState("idle");
      setSharedView(false);
      if (mode === "paste" && !commander.trim() && j.commanders && j.commanders[0]) {
        setCommander(j.commanders[0].name); // show who the deck was graded as
      }
      setResult(j);
    } catch {
      setError("bracket check failed. Try again in a minute.");
    } finally {
      clearTimeout(slowTimer);
      setStage("");
      setBusy(false);
    }
  };

  const seg = (key, label) => (
    <button type="button" onClick={() => setMode(key)}
      style={{ flex: 1, height: 36, borderRadius: 8, border: "1px solid " + (mode === key ? T.accent : T.line), background: mode === key ? T.accentSoft : "transparent", color: T.ink, font: "600 12.5px " + T.mono, cursor: "pointer" }}>
      {label}
    </button>
  );

  return (
    <window.ssShell.AppShell active="bracketcheck" session={session}>
      <main className="cm-radar" style={{ maxWidth: 860, margin: "0 auto", padding: "0 16px" }}>
        <div style={{ ...card, marginTop: 16 }}>
          {session && !sessionLoading && (
            <div style={{ display: "flex", gap: 8, marginBottom: 12 }}>
              {seg("paste", "Paste a list")}
              {seg("deck", "Your decks")}
            </div>
          )}
          <div style={{ marginBottom: 10 }}>
            {mode === "deck" && deckCommanders.length > 0 ? (
              <select value={commander} onChange={(e) => setCommander(e.target.value)} aria-label="Commander"
                style={{ width: "100%", height: 44, padding: "0 12px", font: "500 15px " + T.sans, color: T.ink, background: T.bg, border: "1px solid " + T.line, borderRadius: 10, boxSizing: "border-box" }}>
                {deckCommanders.map((n) => <option key={n} value={n}>{n}</option>)}
              </select>
            ) : (
              <CommanderField value={commander} onChange={setCommander} />
            )}
          </div>
          {mode === "paste" ? (
            <>
              <textarea
                value={pasteText}
                onChange={(e) => setPasteText(e.target.value)}
                placeholder={"1 Sol Ring\n1 Arcane Signet\n1 Swords to Plowshares\n…one card per line"}
                aria-label="Decklist, one card per line"
                style={{ width: "100%", minHeight: 176, padding: 12, font: "400 13px/1.5 " + T.mono, color: T.ink, background: T.bg, border: "1px solid " + T.line, borderRadius: 10, boxSizing: "border-box", resize: "vertical" }}
              />
              <div style={{ font: "400 11.5px " + T.mono, color: T.ink3, margin: "6px 0 10px" }}>
                Moxfield, Archidekt, ManaBox, or plain text exports all paste clean. A Moxfield deck link works too.
              </div>
            </>
          ) : (
            <div style={{ margin: "0 0 10px", maxHeight: 280, overflowY: "auto", border: "1px solid " + T.line, borderRadius: 10 }}>
              {decks === null && <div style={{ padding: 14, font: "400 13.5px " + T.sans, color: T.ink3 }}>Loading your decks…</div>}
              {decks !== null && decks.length === 0 && <div style={{ padding: 14, font: "400 13.5px " + T.sans, color: T.ink3 }}>No decks yet. Paste a list instead.</div>}
              {(decks || []).map((d) => (
                <label key={d.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 12px", borderTop: "1px solid " + T.line, cursor: "pointer" }}>
                  <input type="radio" name="bc-deck" checked={pickedDeck === d.id} onChange={() => setPickedDeck(d.id)} />
                  <span style={{ font: "600 14.5px " + T.sans, color: T.ink, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{d.name}</span>
                  <span style={{ font: "500 11.5px " + T.mono, color: T.ink3, flexShrink: 0 }}>{d.card_count} cards</span>
                </label>
              ))}
            </div>
          )}
          <button type="button" onClick={run} disabled={!canRun || busy}
            style={{ width: "100%", height: 46, borderRadius: 10, border: "none", background: canRun && !busy ? T.accent : T.bg3, color: canRun && !busy ? T.accentFg : T.ink3, font: "700 15px " + T.sans, cursor: canRun && !busy ? "pointer" : "default" }}>
            {busy ? "Grading…" : "Run the graders"}
          </button>
          {busy && stage && <div style={{ font: "500 12px " + T.mono, color: T.ink3, marginTop: 8, textAlign: "center" }}>{stage}</div>}
          {!session && !sessionLoading && (
            <div style={{ font: "400 12.5px " + T.sans, color: T.ink3, marginTop: 8, textAlign: "center" }}>
              Signed in? <a href="/login?next=/commander-bracket-checker" style={{ color: T.accent }}>Grade a saved Spellstash deck in one tap.</a>
            </div>
          )}
          {error && <div style={{ font: "500 13.5px " + T.sans, color: "#b3402a", marginTop: 10 }}>{error}</div>}
        </div>

        {result && (
          <>
            {result.deck.unresolved.length > 0 && (
              <div style={{ font: "500 12.5px " + T.sans, color: T.ink2, background: T.bg3, border: "1px solid " + T.line, borderRadius: 10, padding: "8px 12px", marginTop: 12 }}>
                {result.deck.unresolved.length} name{result.deck.unresolved.length === 1 ? "" : "s"} did not resolve: {result.deck.unresolved.slice(0, 5).join(", ")}{result.deck.unresolved.length > 5 ? "…" : ""}. Graded on the rest.
              </div>
            )}
            {sharedView && (
              <div style={{ font: "500 12.5px " + T.sans, color: T.ink2, background: T.bg3, border: "1px solid " + T.line, borderRadius: 10, padding: "8px 12px", marginTop: 12 }}>
                A shared verdict. Paste your own list above to grade yours.
              </div>
            )}
            <VerdictCard result={result} innerRef={verdictRef} onShare={share} shareState={shareState} />
            <SpreadChart result={result} />
            <GraderRows result={result} />
            <CtaCard session={session} mode={mode} />
            <SignalBreakdown signals={result.signals} />
            {result.commanders.some((c) => c.inferred) && (
              <div style={{ font: "400 12px " + T.sans, color: T.ink3, marginTop: 10 }}>
                Commander read as {result.commanders.find((c) => c.inferred).name} for the combo check. Set it above if that is wrong.
              </div>
            )}
          </>
        )}
      </main>
    </window.ssShell.AppShell>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<BracketCheckApp />);
