// VS Mode — head-to-head comparison. Shared-donors-first treatment.

function VSMode({ aId, bId, ids, onNavigate }) {
  // Accept either ids[] (new) or aId/bId (legacy).
  const initial = ids && ids.length >= 2
    ? ids
    : [aId || "fetterman", bId || "cruz"];
  const [lineup, setLineup] = React.useState(initial);
  const [pickerOpen, setPickerOpen] = React.useState(false);

  // Sync if route changes
  React.useEffect(() => {
    if (ids && ids.length >= 2) setLineup(ids);
    else if (aId && bId) setLineup([aId, bId]);
  }, [aId, bId, (ids || []).join(",")]);

  const pols = lineup
    .map(id => window.CV_DATA.POLITICIANS[id])
    .filter(Boolean);

  if (pols.length < 2) {
    return <div style={{ padding: 40 }}>Pick at least 2 politicians to compare.</div>;
  }

  const ISSUES = window.CV_DATA.ISSUES || [];
  const [activeIssues, setActiveIssues] = React.useState(new Set(ISSUES));
  const allOn = activeIssues.size === ISSUES.length;
  const matchesIssues = (tags) => allOn || (tags && tags.some(t => activeIssues.has(t)));

  const toggleIssue = (issue) => setActiveIssues(prev => {
    const next = new Set(prev);
    if (next.has(issue)) next.delete(issue); else next.add(issue);
    return next;
  });
  const setAll  = () => setActiveIssues(new Set(ISSUES));
  const setNone = () => setActiveIssues(new Set());

  const goBill = () => onNavigate && onNavigate("bill", { id: "hr7024" });
  const goPicker = () => setPickerOpen(true);

  const isMulti = pols.length >= 3;
  const label = pols.map(p => p.short || p.name).join(" · ");

  return (
    <div className="cv-vs" data-screen-label={`VS · ${label}`}>
      <VSLineup
        pols={pols}
        onAdd={goPicker}
        onRemove={(id) => setLineup(lineup.filter(x => x !== id))}
        onSwap={() => goPicker()}
      />
      <IssueFilter
        issues={ISSUES}
        active={activeIssues}
        toggle={toggleIssue}
        all={setAll}
        none={setNone}
        allOn={allOn}
      />
      {isMulti ? (
        <VSMulti
          pols={pols}
          activeIssues={activeIssues}
          allOn={allOn}
          onPickBill={goBill}
          onOpenCard={(id) => onNavigate("politician", { id })}
        />
      ) : (
        <VSDual
          A={pols[0]} B={pols[1]}
          activeIssues={activeIssues}
          allOn={allOn}
          onPickBill={goBill}
          matchesIssues={matchesIssues}
        />
      )}

      <ComparePicker
        open={pickerOpen}
        anchor={pols[0]?.id}
        lineup={lineup.map(id => ({ id }))}
        onChange={setLineup}
        onClose={() => setPickerOpen(false)}
        onCompare={(newIds) => { setLineup(newIds); setPickerOpen(false); }}
      />
    </div>
  );
}

// ─── Lineup bar ─────────────────────────────────────────────────────────
function VSLineup({ pols, onAdd, onRemove, onSwap }) {
  return (
    <div className="cv-vs-lineup">
      <div className="cv-vs-lineup-l">
        <div className="cv-vs-lineup-eyebrow">Comparison lineup</div>
        <div className="cv-vs-lineup-tally">
          {pols.length} politician{pols.length === 1 ? "" : "s"}
          {pols.length === 2 && " · side-by-side"}
          {pols.length >= 3 && ` · spreadsheet view`}
        </div>
      </div>
      <div className="cv-vs-lineup-chips">
        {pols.map((p) => (
          <div key={p.id} className="cv-vs-lineup-chip">
            <Avatar photo={p.photo} name={p.name} size={28} shape="circle" party={p.party}/>
            <span className="cv-vs-lineup-chip-name">{p.short || p.name}</span>
            <PartyTag party={p.party}/>
            {pols.length > 2 && (
              <button
                className="cv-vs-lineup-chip-x"
                onClick={() => onRemove(p.id)}
                aria-label={`Remove ${p.name}`}
              >×</button>
            )}
          </div>
        ))}
        <button className="cv-vs-lineup-add" onClick={onAdd}>
          <span aria-hidden="true">+</span> Add another
        </button>
        {pols.length === 2 && (
          <button className="cv-vs-lineup-swap" onClick={onSwap}>
            Change…
          </button>
        )}
      </div>
    </div>
  );
}

// ─── VSDual — original side-by-side layout for exactly 2 politicians ────
function VSDual({ A, B, activeIssues, allOn, onPickBill, matchesIssues }) {
  const sv = window.CV_DATA.SHARED_VOTES;
  const sd = window.CV_DATA.SHARED_DONORS;
  const sr = window.CV_DATA.SHARED_RATINGS;
  const [mobileSide, setMobileSide] = React.useState("vs");
  const filteredDisagreements = sv.disagreements.filter(d => matchesIssues(d.issues));
  const filteredRatings = sr.filter(r => matchesIssues(r.issues));
  const filteredDonors = sd.filter(d => matchesIssues(d.issues));

  return (
    <>
      <div className="cv-vs-mobile-tabs">
        <button className={mobileSide === "a" ? "is-on" : ""} onClick={() => setMobileSide("a")}>{A.short}</button>
        <button className={mobileSide === "vs" ? "is-on" : ""} onClick={() => setMobileSide("vs")}>VS</button>
        <button className={mobileSide === "b" ? "is-on" : ""} onClick={() => setMobileSide("b")}>{B.short}</button>
      </div>
      <VSHeader A={A} B={B} mobileSide={mobileSide} />
      <VSQuickStats A={A} B={B} />
      <VSVotingAgreement A={A} B={B} sv={sv} disagreements={filteredDisagreements} allOn={allOn} goBill={onPickBill} />
      <VSPromises A={A} B={B} activeIssues={activeIssues} allOn={allOn} onPickBill={onPickBill} />
      <VSSharedDonors A={A} B={B} sd={filteredDonors} totalDonors={sd.length} allOn={allOn} />
      <VSMoney A={A} B={B} />
      <VSRatings A={A} B={B} sr={filteredRatings} totalRatings={sr.length} allOn={allOn} />
      <VSShareCard A={A} B={B} sv={sv} sd={sd} />
    </>
  );
}

// ─── Issue filter chip bar ──────────────────────────────────────────────
function IssueFilter({ issues, active, toggle, all, none, allOn }) {
  const activeCount = active.size;
  return (
    <section className="cv-vs-filter">
      <div className="cv-vs-filter-hd">
        <div className="cv-vs-filter-l">
          <span className="cv-vs-filter-eyebrow">Filter by issue</span>
          <span className="cv-vs-filter-sub">
            {allOn
              ? "Showing everything. Tap to narrow."
              : activeCount === 0
                ? "Nothing selected — pick an issue."
                : `Showing data for ${activeCount} issue${activeCount === 1 ? "" : "s"}.`}
          </span>
        </div>
        <div className="cv-vs-filter-r">
          {!allOn && <button className="cv-vs-filter-act" onClick={all}>Select all</button>}
          {activeCount > 0 && <button className="cv-vs-filter-act cv-vs-filter-act-clear" onClick={none}>Clear</button>}
        </div>
      </div>
      <div className="cv-vs-filter-chips">
        {issues.map((iss) => {
          const on = active.has(iss);
          return (
            <button
              key={iss}
              type="button"
              className={`cv-vs-chip ${on ? "is-on" : ""}`}
              onClick={() => toggle(iss)}
              aria-pressed={on}
            >
              <span className="cv-vs-chip-mark" aria-hidden="true">{on ? "✓" : "+"}</span>
              {iss}
            </button>
          );
        })}
      </div>
    </section>
  );
}

// ─── Header ─────────────────────────────────────────────────────────────
function VSHeader({ A, B, mobileSide, onNavigate }) {
  return (
    <header className="cv-vs-hd">
      <div className={`cv-vs-hd-side cv-vs-hd-a ${mobileSide === "b" ? "cv-mobile-hide" : ""}`}>
        <Avatar photo={A.photo} name={A.name} size={88} shape="rounded" party={A.party} />
        <div className="cv-vs-hd-meta">
          <PartyTag party={A.party} />
          <h2 className="cv-vs-hd-name">{A.name}</h2>
          <div className="cv-vs-hd-office">{A.office} · {A.state}{A.district ? `-${A.district}` : ""}</div>
        </div>
      </div>
      <div className="cv-vs-divider" aria-hidden="true">
        <span className="cv-vs-divider-vs">VS</span>
        <span className="cv-vs-divider-sub">head-to-head</span>
      </div>
      <div className={`cv-vs-hd-side cv-vs-hd-b ${mobileSide === "a" ? "cv-mobile-hide" : ""}`}>
        <Avatar photo={B.photo} name={B.name} size={88} shape="rounded" party={B.party} />
        <div className="cv-vs-hd-meta">
          <PartyTag party={B.party} />
          <h2 className="cv-vs-hd-name">{B.name}</h2>
          <div className="cv-vs-hd-office">{B.office} · {B.state}{B.district ? `-${B.district}` : ""}</div>
        </div>
      </div>
    </header>
  );
}

// ─── VSMulti — spreadsheet view for 3+ politicians ──────────────────────
function VSMulti({ pols, activeIssues, allOn, onPickBill, onOpenCard }) {
  const matches = (tags) => allOn || (tags && tags.some(t => activeIssues.has(t)));

  // Build the stat-row data
  const rows = [
    { kind: "stat", label: <Term name="tenure">Tenure</Term>,         get: (p) => p.years, fmt: (v) => v == null ? "—" : `${v}y`, max: Math.max(...pols.map(p => p.years || 0), 1), better: "higher" },
    { kind: "stat", label: <Term name="party-loyalty">Party loyalty</Term>, get: (p) => p.loyalty, fmt: (v) => v == null ? "—" : `${v}%`, max: 100, better: "higher" },
    { kind: "stat", label: <Term name="missed-votes">Missed votes</Term>, get: (p) => p.missed, fmt: (v) => v == null ? "—" : `${v}%`, max: Math.max(...pols.map(p => p.missed || 0), 5), better: "lower" },
    { kind: "stat", label: <Term name="fec">Total raised</Term>, get: (p) => parseFloat(p.raised || "0"), fmt: (v, p) => p.raised ? `$${p.raised}` : "—", max: Math.max(...pols.map(p => parseFloat(p.raised || "0") || 0), 1), better: "higher" },
    { kind: "stat", label: <Term name="small-dollar">Small-dollar share</Term>, get: (p) => p.smallDollar, fmt: (v) => v == null ? "—" : `${v}%`, max: 100, better: "higher" },
    { kind: "stat", label: <Term name="pac-share">PAC share</Term>, get: (p) => p.pacShare, fmt: (v) => v == null ? "—" : `${v}%`, max: Math.max(...pols.map(p => p.pacShare || 0), 30), better: "lower" },
  ];

  const promiseRows = pols.some(p => (p.promises || []).length > 0) ? [
    {
      kind: "promise-kept",
      label: "Promises kept",
      get: (p) => {
        const promises = (p.promises || []).filter(pr => matches(pr.issues));
        if (promises.length === 0) return null;
        return Math.round((promises.filter(pr => pr.status === "kept").length / promises.length) * 100);
      },
      fmt: (v) => v == null ? "—" : `${v}%`,
      max: 100, better: "higher",
    },
    {
      kind: "promise-reversed",
      label: "Promises reversed",
      get: (p) => (p.promises || []).filter(pr => matches(pr.issues) && pr.status === "reversed").length,
      fmt: (v) => v === 0 ? "0" : <><span>{v}</span> {"🚩".repeat(Math.min(v, 3))}</>,
      max: Math.max(1, ...pols.map(p => (p.promises || []).filter(pr => pr.status === "reversed").length)), better: "lower",
    },
  ] : [];

  // Collect union of rating groups
  const allGroups = Array.from(new Set(
    pols.flatMap(p => (p.ratings || []).filter(r => matches(r.issues)).map(r => r.group))
  ));
  const ratingRows = allGroups.map(group => ({
    kind: "rating",
    label: group,
    group,
    get: (p) => {
      const r = (p.ratings || []).find(x => x.group === group);
      return r ? r.score : null;
    },
    fmt: (v) => v == null ? "—" : v,
  }));

  return (
    <>
      <section className="cv-vs-section cv-vs-multi-wrap">
        <SectionHead
          title="All metrics, side by side"
          sub="Every candidate, every stat, same row. Scroll horizontally for more candidates."
          right={<SourceButton label="FEC · LDA · VoteSmart" />}
        />
        <div className="cv-vs-multi-scroll">
          <table className="cv-vs-multi" role="table">
            <thead>
              <tr>
                <th className="cv-vsm-rowhd cv-vsm-corner"/>
                {pols.map((p) => (
                  <th key={p.id} className="cv-vsm-colhd">
                    <button className="cv-vsm-colhd-btn" onClick={() => onOpenCard(p.id)}>
                      <Avatar photo={p.photo} name={p.name} size={36} shape="circle" party={p.party}/>
                      <span className="cv-vsm-colhd-name">{p.short || p.name}</span>
                      <span className="cv-vsm-colhd-meta">
                        <PartyTag party={p.party}/>
                        {p.state}{p.district ? `-${p.district}` : ""}
                      </span>
                    </button>
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {[...rows, ...promiseRows].map((row, i) => (
                <MultiStatRow key={i} row={row} pols={pols}/>
              ))}
              {ratingRows.length > 0 && (
                <tr className="cv-vsm-divider">
                  <td colSpan={pols.length + 1}>
                    <span><Term name="interest-group-rating">Interest-group ratings</Term></span>
                  </td>
                </tr>
              )}
              {ratingRows.map((row, i) => (
                <MultiRatingRow key={`r${i}`} row={row} pols={pols}/>
              ))}
            </tbody>
          </table>
        </div>
        <div className="cv-vsm-foot">
          <SourceIcon/>
          Numbers are tappable for the underlying records · "—" means the candidate has no disclosed data for that metric.
        </div>
      </section>

      <VSMultiOverlaps pols={pols}/>
      <VSMultiReversedPromises pols={pols} activeIssues={activeIssues} allOn={allOn} onPickBill={onPickBill}/>
    </>
  );
}

function MultiStatRow({ row, pols }) {
  const values = pols.map(p => row.get(p));
  // For "lower better", invert the bar; null values get no bar
  const max = row.max || Math.max(...values.filter(v => v != null), 1);
  const best = row.better === "lower"
    ? Math.min(...values.filter(v => v != null))
    : Math.max(...values.filter(v => v != null));

  return (
    <tr>
      <th className="cv-vsm-rowhd">{row.label}</th>
      {pols.map((p, i) => {
        const v = values[i];
        const fillPct = (v == null || max === 0) ? 0 : Math.min(100, (Math.abs(v) / max) * 100);
        const isBest = v != null && v === best && values.filter(x => x === best).length === 1;
        return (
          <td key={p.id} className={`cv-vsm-cell ${isBest ? "is-best" : ""} ${v == null ? "is-empty" : ""}`}>
            {v != null && (
              <span className="cv-vsm-cell-bar" style={{ width: `${fillPct}%` }}/>
            )}
            <span className="cv-vsm-cell-v">{row.fmt(v, p)}</span>
          </td>
        );
      })}
    </tr>
  );
}

function MultiRatingRow({ row, pols }) {
  const scores = pols.map(p => row.get(p));
  const pcts = scores.map(s => s == null ? null : ratingMeta(s).pct);
  return (
    <tr>
      <th className="cv-vsm-rowhd cv-vsm-rowhd-rating">
        <Term name="interest-group-rating">{row.group}</Term>
      </th>
      {pols.map((p, i) => {
        const v = scores[i];
        const pct = pcts[i] ?? 0;
        return (
          <td key={p.id} className={`cv-vsm-cell ${v == null ? "is-empty" : ""}`}>
            {v != null && (
              <span className="cv-vsm-cell-bar cv-vsm-cell-bar-rating" style={{ width: `${pct}%` }}/>
            )}
            <span className="cv-vsm-cell-v">{row.fmt(v)}</span>
          </td>
        );
      })}
    </tr>
  );
}

// ─── Donor / industry overlaps across N candidates ──────────────────────
function VSMultiOverlaps({ pols }) {
  // Find industries that appear in topIndustries of 2+ candidates
  const industryToCands = {};
  pols.forEach(p => {
    (p.topIndustries || []).forEach(d => {
      industryToCands[d.name] = industryToCands[d.name] || {};
      industryToCands[d.name][p.id] = d.amount;
    });
  });
  const overlaps = Object.entries(industryToCands)
    .filter(([, m]) => Object.keys(m).length >= 2)
    .sort((a, b) => Object.keys(b[1]).length - Object.keys(a[1]).length);

  if (overlaps.length === 0) {
    return (
      <section className="cv-vs-section">
        <SectionHead title="Shared donor sectors" sub="Industries that show up in more than one candidate's top-5 list." />
        <EmptyState text="No overlapping donor sectors found across the current lineup." detail="Different candidates draw from different industries; widen the lineup to see overlaps." />
      </section>
    );
  }

  return (
    <section className="cv-vs-section cv-vs-overlaps">
      <SectionHead
        title="Shared donor sectors"
        sub={<>Industries that fund <strong>{overlaps[0] && Object.keys(overlaps[0][1]).length} of {pols.length}</strong> candidates in this lineup. The overlaps are the interesting part.</>}
        right={<SourceButton label="FEC + LDA aggregated"/>}
      />
      <table className="cv-vsm cv-vsm-overlap">
        <thead>
          <tr>
            <th className="cv-vsm-rowhd cv-vsm-corner"/>
            {pols.map(p => (
              <th key={p.id} className="cv-vsm-overlap-colhd">{p.short || p.name}</th>
            ))}
            <th className="cv-vsm-overlap-tally">overlap</th>
          </tr>
        </thead>
        <tbody>
          {overlaps.slice(0, 8).map(([ind, m]) => (
            <tr key={ind}>
              <th className="cv-vsm-rowhd">{ind}</th>
              {pols.map(p => {
                const amt = m[p.id];
                return (
                  <td key={p.id} className={`cv-vsm-cell cv-vsm-overlap-cell ${amt ? "is-hit" : "is-miss"}`}>
                    {amt ? <span className="cv-vsm-cell-v">{amt}</span> : <span className="cv-vsm-miss">·</span>}
                  </td>
                );
              })}
              <td className="cv-vsm-overlap-tally">
                <span className="cv-vsm-overlap-n">{Object.keys(m).length}</span>
                <span className="cv-vsm-overlap-of">of {pols.length}</span>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </section>
  );
}

// ─── All flagged reversed promises across N candidates ──────────────────
function VSMultiReversedPromises({ pols, activeIssues, allOn, onPickBill }) {
  const matches = (tags) => allOn || (tags && tags.some(t => activeIssues.has(t)));
  const allFlags = pols.flatMap(p =>
    (p.promises || [])
      .filter(pr => pr.status === "reversed" && matches(pr.issues))
      .map(pr => ({ pr, p }))
  );
  if (allFlags.length === 0) return null;
  return (
    <section className="cv-vs-section">
      <SectionHead
        title="Flagged reversals"
        sub={<>{allFlags.length} promise-vs-vote conflict{allFlags.length === 1 ? "" : "s"} across this lineup. Click any to open the bill.</>}
        right={<SourceButton label="ClarionVote editorial"/>}
      />
      <div className="cv-vs-prom-flags">
        {allFlags.map(({ pr, p }, i) => (
          <div key={i} className="cv-vs-prom-flag-row-card">
            <span className="cv-vs-prom-who">{p.short || p.name}</span>
            <span className="cv-vs-prom-text">"{pr.text}"</span>
            {pr.vote && (
              <button className="cv-link-bill cv-vs-prom-vote" onClick={() => onPickBill(pr.vote.bill)}>
                Voted {pr.vote.position} on <span style={{ textDecoration: "underline", textDecorationColor: "var(--cv-accent)", textUnderlineOffset: "3px" }}>{pr.vote.bill}</span> →
              </button>
            )}
          </div>
        ))}
      </div>
    </section>
  );
}
function VSQuickStats({ A, B }) {
  const rows = [
    { label: <Term name="tenure">Years in Office</Term>, a: A.years,   b: B.years,   max: 20,  fmt: (v) => v },
    { label: <Term name="party-loyalty">Party Loyalty</Term>, a: A.loyalty, b: B.loyalty, max: 100, fmt: (v) => `${v}%` },
    { label: <Term name="missed-votes">Missed Votes</Term>, a: A.missed,  b: B.missed,  max: 15,  fmt: (v) => `${v}%` },
    { label: <Term name="fec">Total Raised</Term>, a: parseFloat(A.raised), b: parseFloat(B.raised), max: 80, fmt: (v) => `$${v}M` },
  ];
  return (
    <section className="cv-vs-stats">
      <SectionHead title="By the numbers" sub="Direct stat comparison · same denominators · public sources." right={<SourceButton label="FEC · House &amp; Senate clerks" />} />
      <div className="cv-vs-stat-table">
        {rows.map((r, i) => (
          <div key={i} className="cv-vs-stat-row">
            <span className="cv-vs-stat-val cv-vs-stat-val-a">{r.fmt(r.a)}</span>
            <div className="cv-vs-stat-mid">
              <div className="cv-vs-stat-mirror">
                <div className="cv-vs-stat-mirror-l">
                  <div className="cv-vs-stat-mirror-fill" style={{ width: `${Math.min(100, (r.a / r.max) * 100)}%` }} />
                </div>
                <div className="cv-vs-stat-mirror-r">
                  <div className="cv-vs-stat-mirror-fill" style={{ width: `${Math.min(100, (r.b / r.max) * 100)}%` }} />
                </div>
              </div>
              <div className="cv-vs-stat-label">{r.label}</div>
            </div>
            <span className="cv-vs-stat-val cv-vs-stat-val-b">{r.fmt(r.b)}</span>
          </div>
        ))}
      </div>
    </section>
  );
}

// ─── Voting agreement ───────────────────────────────────────────────────
function VSVotingAgreement({ A, B, sv, disagreements, allOn, goBill }) {
  return (
    <section className="cv-vs-section">
      <SectionHead
        title={<><Term name="agreement-rate">Voting agreement</Term></>}
        sub={<>Of {sv.total.toLocaleString()} shared <Term name="roll-call">roll-call votes</Term>, they agreed on {sv.agreed}.</>}
        right={<SourceButton label="Roll-call records" />}
      />
      <div className="cv-vs-agree">
        <div className="cv-vs-agree-pct">
          <div className="cv-vs-agree-num">{sv.rate}<span className="cv-vs-agree-pct-sym">%</span></div>
          <div className="cv-vs-agree-l">agreement rate</div>
        </div>
        <div className="cv-vs-agree-ring" aria-hidden="true">
          <svg viewBox="0 0 120 120" width="120" height="120">
            <circle cx="60" cy="60" r="52" stroke="var(--cv-line)" strokeWidth="10" fill="none"/>
            <circle cx="60" cy="60" r="52"
              stroke="var(--cv-accent)" strokeWidth="10" fill="none"
              strokeDasharray={`${(sv.rate / 100) * 2 * Math.PI * 52} ${2 * Math.PI * 52}`}
              strokeDashoffset={(2 * Math.PI * 52) * 0.25}
              transform="rotate(-90 60 60)" strokeLinecap="butt"
            />
          </svg>
        </div>
        <div className="cv-vs-agree-key-d">
          <div className="cv-vs-agree-h">
            Key disagreements
            {!allOn && <span className="cv-vs-agree-h-tag">filtered</span>}
          </div>
          {disagreements.length === 0 ? (
            <EmptyState text="No disagreements matching the selected issues." />
          ) : (
            <ul className="cv-vs-disagree">
              {disagreements.slice(0, 5).map((d, i) => (
                <li key={i} className="cv-vs-disagree-row">
                  <button className="cv-vs-disagree-bill cv-link-bill" onClick={() => goBill(d.billRef)}>{d.bill}</button>
                  <button className="cv-vs-disagree-title cv-link-bill" onClick={() => goBill(d.billRef)}>
                    {d.title}
                    {d.issues && (
                      <span className="cv-vs-disagree-tags">
                        {d.issues.map((t) => <span key={t} className="cv-vs-disagree-tag">{t}</span>)}
                      </span>
                    )}
                  </button>
                  <span className="cv-vs-disagree-votes">
                    <VoteBadge position={d.a} />
                    <span className="cv-vs-disagree-mid">vs</span>
                    <VoteBadge position={d.b} />
                  </span>
                </li>
              ))}
            </ul>
          )}
        </div>
      </div>
    </section>
  );
}

// ─── SHARED DONORS — the headline section ───────────────────────────────
function VSSharedDonors({ A, B, sd, totalDonors, allOn }) {
  const totalA = sd.reduce((s, d) => s + Number(d.a.replace(/[^0-9.]/g, "")), 0);
  const totalB = sd.reduce((s, d) => s + Number(d.b.replace(/[^0-9.]/g, "")), 0);
  return (
    <section className="cv-vs-hero">
      <div className="cv-vs-hero-eyebrow">
        <span className="cv-vs-hero-tag">The shared donor file</span>
        {!allOn && <span className="cv-vs-hero-filt">filtered ({sd.length} of {totalDonors})</span>}
        <SourceButton label="FEC itemized contributions" />
      </div>
      <h2 className="cv-vs-hero-title">
        <span className="cv-vs-hero-num">{sd.length}</span>
        <span> donors funded both</span>
        <span className="cv-vs-hero-names"> {A.short} <span className="cv-vs-hero-amp">and</span> {B.short}</span>
      </h2>
      <p className="cv-vs-hero-sub">
        Same employer, same cycle. ${totalA.toLocaleString()} to {A.short}, ${totalB.toLocaleString()} to {B.short}.
      </p>
      {sd.length === 0 ? (
        <EmptyState text="No shared donors match the selected issues." detail="Try widening the issue filter above." />
      ) : (
        <div className="cv-vs-shared-table">
          <div className="cv-vs-shared-hd">
            <span>Donor</span>
            <span>Sector</span>
            <span className="cv-vs-shared-a">to {A.short}</span>
            <span className="cv-vs-shared-b">to {B.short}</span>
          </div>
          {sd.map((d, i) => {
            const a = Number(d.a.replace(/[^0-9.]/g, ""));
            const b = Number(d.b.replace(/[^0-9.]/g, ""));
            const max = Math.max(a, b, 25000);
            return (
              <div key={i} className="cv-vs-shared-row">
                <span className="cv-vs-shared-name">{d.name}</span>
                <span className="cv-vs-shared-emp">{d.employer}</span>
                <span className="cv-vs-shared-a">
                  <span className="cv-vs-shared-bar cv-vs-shared-bar-a">
                    <span style={{ width: `${(a / max) * 100}%` }} />
                  </span>
                  <span className="cv-vs-shared-amt">{d.a}</span>
                </span>
                <span className="cv-vs-shared-b">
                  <span className="cv-vs-shared-amt">{d.b}</span>
                  <span className="cv-vs-shared-bar cv-vs-shared-bar-b">
                    <span style={{ width: `${(b / max) * 100}%` }} />
                  </span>
                </span>
              </div>
            );
          })}
        </div>
      )}
      <div className="cv-vs-hero-foot">
        <SourceIcon />
        <span>Aggregated by employer from <Term name="fec">FEC</Term> individual contributions. <Term name="pac-share">PAC</Term> donations excluded.</span>
        <a href="#" onClick={(e) => e.preventDefault()} className="cv-link">Methodology →</a>
      </div>
    </section>
  );
}

// ─── Promise comparison (Kept % + Reversed count + flagged reversals) ──
function VSPromises({ A, B, activeIssues, allOn, onPickBill }) {
  const matches = (tags) => allOn || (tags && tags.some(t => activeIssues.has(t)));
  const aProm = (A.promises || []).filter(pr => matches(pr.issues));
  const bProm = (B.promises || []).filter(pr => matches(pr.issues));

  if (aProm.length === 0 && bProm.length === 0) {
    return (
      <section className="cv-vs-section">
        <SectionHead title="Promises tracker" sub="Compare what each said they'd do — and what they did." right={<SourceButton label="ClarionVote editorial" />} />
        <EmptyState text="No documented promises match the selected issues for either candidate." detail="Try widening the issue filter, or check each card's Promises tab." />
      </section>
    );
  }

  const pct = (arr, st) => arr.length === 0 ? 0 : Math.round((arr.filter(p => p.status === st).length / arr.length) * 100);
  const aKept = pct(aProm, "kept"); const bKept = pct(bProm, "kept");
  const aRev  = aProm.filter(p => p.status === "reversed");
  const bRev  = bProm.filter(p => p.status === "reversed");
  const aRevCount = aRev.length;
  const bRevCount = bRev.length;
  const max = Math.max(1, aProm.length, bProm.length);

  return (
    <section className="cv-vs-section cv-vs-promises">
      <SectionHead
        title="Promises tracker"
        sub={<>What each said they'd do — and what they did about it. <span className="cv-vs-prom-tally">{aProm.length} vs {bProm.length} promises{!allOn ? " match this filter" : ""}.</span></>}
        right={<SourceButton label="ClarionVote editorial" />}
      />
      <div className="cv-vs-prom-rows">
        <div className="cv-vs-stat-row cv-vs-prom-stat">
          <span className="cv-vs-stat-val cv-vs-stat-val-a">{aKept}%</span>
          <div className="cv-vs-stat-mid">
            <div className="cv-vs-stat-mirror">
              <div className="cv-vs-stat-mirror-l"><div className="cv-vs-stat-mirror-fill" style={{ width: `${aKept}%` }}/></div>
              <div className="cv-vs-stat-mirror-r"><div className="cv-vs-stat-mirror-fill cv-vs-mirror-kept" style={{ width: `${bKept}%` }}/></div>
            </div>
            <div className="cv-vs-stat-label">Promises kept</div>
          </div>
          <span className="cv-vs-stat-val cv-vs-stat-val-b">{bKept}%</span>
        </div>
        <div className="cv-vs-stat-row cv-vs-prom-stat">
          <span className="cv-vs-stat-val cv-vs-stat-val-a">
            {aRevCount} {aRevCount > 0 && <span className="cv-vs-prom-flag-row">{"🚩".repeat(Math.min(aRevCount, 3))}</span>}
          </span>
          <div className="cv-vs-stat-mid">
            <div className="cv-vs-stat-mirror">
              <div className="cv-vs-stat-mirror-l"><div className="cv-vs-stat-mirror-fill cv-vs-mirror-rev" style={{ width: `${(aRevCount / max) * 100}%` }}/></div>
              <div className="cv-vs-stat-mirror-r"><div className="cv-vs-stat-mirror-fill cv-vs-mirror-rev" style={{ width: `${(bRevCount / max) * 100}%` }}/></div>
            </div>
            <div className="cv-vs-stat-label">Promises reversed</div>
          </div>
          <span className="cv-vs-stat-val cv-vs-stat-val-b">
            {bRevCount > 0 && <span className="cv-vs-prom-flag-row">{"🚩".repeat(Math.min(bRevCount, 3))}</span>} {bRevCount}
          </span>
        </div>
      </div>
      {(aRevCount + bRevCount) > 0 && (
        <div className="cv-vs-prom-flags">
          <div className="cv-h4">
            <span>Flagged reversals</span>
            <span className="cv-h4-sub">promise vs vote conflicts</span>
          </div>
          {aRev.map((pr, i) => (
            <div key={`a${i}`} className="cv-vs-prom-flag-row-card cv-vs-prom-side-a">
              <span className="cv-vs-prom-who">{A.short}</span>
              <span className="cv-vs-prom-text">"{pr.text}"</span>
              {pr.vote && (
                <button className="cv-link-bill cv-vs-prom-vote" onClick={() => onPickBill(pr.vote.bill)}>
                  Voted {pr.vote.position} on <span style={{ textDecoration: "underline", textDecorationColor: "var(--cv-accent)", textUnderlineOffset: "3px" }}>{pr.vote.bill}</span> →
                </button>
              )}
            </div>
          ))}
          {bRev.map((pr, i) => (
            <div key={`b${i}`} className="cv-vs-prom-flag-row-card cv-vs-prom-side-b">
              <span className="cv-vs-prom-who">{B.short}</span>
              <span className="cv-vs-prom-text">"{pr.text}"</span>
              {pr.vote && (
                <button className="cv-link-bill cv-vs-prom-vote" onClick={() => onPickBill(pr.vote.bill)}>
                  Voted {pr.vote.position} on <span style={{ textDecoration: "underline", textDecorationColor: "var(--cv-accent)", textUnderlineOffset: "3px" }}>{pr.vote.bill}</span> →
                </button>
              )}
            </div>
          ))}
        </div>
      )}
    </section>
  );
}

// ─── Money side-by-side ────────────────────────────────────────────────
function VSMoney({ A, B }) {
  const rows = [
    { label: <Term name="small-dollar">Small-dollar share</Term>, a: A.smallDollar, b: B.smallDollar, suffix: "%" },
    { label: <Term name="pac-share">PAC share</Term>,             a: A.pacShare,    b: B.pacShare,    suffix: "%" },
  ];
  return (
    <section className="cv-vs-section">
      <SectionHead title="Money breakdown" sub="Who pays the bills." right={<SourceButton label="FEC" />} />
      <div className="cv-vs-money">
        {rows.map((r, i) => (
          <div key={i} className="cv-vs-money-row">
            <div className="cv-vs-money-row-l">{r.label}</div>
            <div className="cv-vs-money-row-bars">
              <div className="cv-vs-money-bar-a">
                <div className="cv-vs-money-fill" style={{ width: `${r.a}%` }} />
                <span className="cv-vs-money-num">{r.a}{r.suffix}</span>
              </div>
              <div className="cv-vs-money-bar-b">
                <span className="cv-vs-money-num">{r.b}{r.suffix}</span>
                <div className="cv-vs-money-fill" style={{ width: `${r.b}%` }} />
              </div>
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}

// ─── Ratings comparison ─────────────────────────────────────────────────
function VSRatings({ A, B, sr, totalRatings, allOn }) {
  return (
    <section className="cv-vs-section">
      <SectionHead
        title={<><Term name="interest-group-rating">Interest-group ratings</Term></>}
        sub="Same groups, same scoring methodology — but every group uses its own scale."
        right={<SourceButton label="VoteSmart scorecards" />}
      />
      {!allOn && (
        <div className="cv-vs-filter-note">
          Showing {sr.length} of {totalRatings} groups · {sr.length === 0 ? "no groups match the selected issues" : "filtered by issue"}.
        </div>
      )}
      {sr.length === 0 ? (
        <EmptyState text="No ratings match the selected issues." detail="Try widening the filter above." />
      ) : (
        <div className="cv-vs-ratings-rich">
          <div className="cv-vs-ratings-rich-hd">
            <span/>
            <span className="cv-vs-ratings-rich-hd-a">{A.short}</span>
            <span className="cv-vs-ratings-rich-hd-b">{B.short}</span>
          </div>
          {sr.map((r, i) => (
            <RatingCompareRow key={i} group={r.group} category={r.category} a={r.a} b={r.b} />
          ))}
        </div>
      )}
    </section>
  );
}

// ─── OG share preview ───────────────────────────────────────────────────
function VSShareCard({ A, B, sv, sd }) {
  return (
    <section className="cv-vs-section">
      <SectionHead title="If you share this link…" sub="The social-card preview your friends will see." />
      <div className="cv-og-wrap">
        <div className="cv-og">
          <div className="cv-og-brand">CLARION<span className="cv-og-brand-em">VOTE</span></div>
          <div className="cv-og-headline">{A.short} <span>vs</span> {B.short}</div>
          <div className="cv-og-faces">
            <div className="cv-og-face">
              <Avatar photo={A.photo} name={A.name} size={120} shape="rounded" party={A.party} />
              <PartyTag party={A.party} />
            </div>
            <div className="cv-og-divider">VS</div>
            <div className="cv-og-face">
              <Avatar photo={B.photo} name={B.name} size={120} shape="rounded" party={B.party} />
              <PartyTag party={B.party} />
            </div>
          </div>
          <div className="cv-og-stats">
            <div><div className="cv-og-stat-v">{sv.rate}%</div><div className="cv-og-stat-l">agreement</div></div>
            <div><div className="cv-og-stat-v">{sd.length}</div><div className="cv-og-stat-l">shared donors</div></div>
            <div><div className="cv-og-stat-v">{sv.total.toLocaleString()}</div><div className="cv-og-stat-l">shared votes</div></div>
          </div>
          <div className="cv-og-foot">clarionvote.com · powered by public data · no opinion</div>
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { VSMode });
