// Glossary + tooltip system. Wrap any jargon term with <Term name="...">label</Term>
// (or pass children to override the displayed label). The term renders with a
// dotted underline + a small ⓘ glyph; tap/click opens a compact popover with a
// plain-English definition, why-it-matters line, and a "learn more" link.

const TERMS = {
  "small-dollar": {
    title: "Small-dollar share",
    plain: "Percentage of donations that arrived in amounts under $200 — typically from individual people, not organizations.",
    why: "A higher number means more grassroots funding; lower numbers mean reliance on large checks and bundlers.",
    more: { label: "FEC: individual contribution thresholds", href: "#" },
  },
  "pac-share": {
    title: "PAC share",
    plain: "The percent of total fundraising that came from Political Action Committees — groups that pool donations from members to support candidates.",
    why: "PAC money signals who's organized to ask for things in return. PACs include corporate PACs, union PACs, and ideological PACs.",
    more: { label: "FEC: types of PACs", href: "#" },
  },
  "fec": {
    title: "FEC — Federal Election Commission",
    plain: "The independent government agency that tracks every dollar of federal campaign money.",
    why: "Every contribution figure on this page is a public FEC filing. If we show it, you can verify it.",
    more: { label: "View FEC filings for this politician", href: "#" },
  },
  "party-loyalty": {
    title: "Party loyalty",
    plain: "How often this politician votes the same way as the majority of their own party on roll-call votes.",
    why: "92% means they voted with their party on 92 out of 100 party-line votes. High loyalty isn't good or bad — it's a signal of how independent they are from leadership.",
    more: { label: "Methodology", href: "#" },
  },
  "missed-votes": {
    title: "Missed votes",
    plain: "Roll-call votes where this politician was absent and did not cast a vote.",
    why: "The national average is roughly 2–3%. Persistently high rates can indicate a run for higher office, illness, or disengagement.",
    more: { label: "GovTrack: missed votes leaderboard", href: "#" },
  },
  "agreement-rate": {
    title: "Agreement rate",
    plain: "Of all the votes where both politicians participated, the percentage where they voted the same way.",
    why: "This is the cleanest single number for 'how alike are these two on the floor?' — independent of party labels.",
    more: { label: "How agreement is calculated", href: "#" },
  },
  "roll-call": {
    title: "Roll-call vote",
    plain: "A recorded vote where every member's individual position (Yea, Nay, Present, or Not Voting) is documented in the official record.",
    why: "Most floor votes are voice votes with no record. Roll calls are the ones we can actually score.",
    more: { label: "How a roll-call is requested", href: "#" },
  },
  "cosponsor": {
    title: "Cosponsor",
    plain: "A member of Congress who formally signs onto a bill alongside its sponsor, signaling support before it ever reaches a vote.",
    why: "Cosponsor counts — and how they break down by party — are the earliest signal of a bill's chances and which coalition backs it.",
    more: { label: "Congress.gov on cosponsors", href: "#" },
  },
  "lda": {
    title: "LDA — Lobbying Disclosure Act",
    plain: "Federal law that requires registered lobbyists to file quarterly reports listing their clients, the bills they lobbied, and how much they spent.",
    why: "LDA filings are how we know which industries pushed for or against a specific bill, and how much they paid for that push.",
    more: { label: "Senate LDA database", href: "#" },
  },
  "stock-act": {
    title: "STOCK Act",
    plain: "A 2012 law that requires members of Congress to publicly disclose their stock and asset trades within 30–45 days.",
    why: "Disclosure doesn't prevent conflicts — it just makes them visible. Late filings and trades in sectors a member legislates over are the things worth watching.",
    more: { label: "Office of Congressional Ethics", href: "#" },
  },
  "ptr": {
    title: "PTR — Periodic Transaction Report",
    plain: "The form a member of Congress files under the STOCK Act each time they (or their spouse) buys or sells an individual asset.",
    why: "An empty PTR record is itself a fact: it means the politician hasn't disclosed any individual trades. A late or missing PTR is a violation.",
    more: { label: "House Clerk PTR archive", href: "#" },
  },
  "conflict-flag": {
    title: "Conflict flag",
    plain: "An automatically-generated note triggered when a politician's disclosed financial interest overlaps with a committee assignment or recent vote.",
    why: "A flag is not a verdict — it's a starting point. The evidence is linked so you can decide what it means.",
    more: { label: "Flag rules", href: "#" },
  },
  "yea-nay": {
    title: "Yea / Nay / Present",
    plain: "The three positions on a roll-call vote. 'Yea' = in favor. 'Nay' = against. 'Present' = registered as attending without taking a side (often a procedural protest).",
    why: "Present is rare and almost always intentional. It usually means the member opposes the bill but doesn't want the political cost of a Nay vote on record.",
    more: { label: "House voting procedures", href: "#" },
  },
  "tenure": {
    title: "Tenure",
    plain: "Years served in the current office, counting from the date of first being sworn in.",
    why: "Tenure tracks seniority — committee assignments, speaking time, and floor influence all scale with it.",
    more: null,
  },
  "interest-group-rating": {
    title: "Interest-group rating",
    plain: "A score, letter grade, or percentage that an advocacy organization assigns to a politician based on how they vote on the group's priority issues.",
    why: "Ratings tell you who *they* think is on their side. Different groups use different scales — letter grades, percentages, dollar amounts — because they're not coordinated.",
    more: { label: "VoteSmart methodology", href: "#" },
  },
};

// Term — wrap jargon. Children override the displayed label.
function Term({ name, children }) {
  const def = TERMS[name];
  const labelEl = children ?? (def ? def.title : name);
  const [open, setOpen] = React.useState(false);
  const trigRef = React.useRef(null);
  const [pos, setPos] = React.useState({ top: 0, left: 0 });

  React.useEffect(() => {
    if (!open) return;
    const close = (e) => {
      if (!trigRef.current) return;
      const pop = document.getElementById("cv-pop-active");
      if (pop && pop.contains(e.target)) return;
      if (trigRef.current.contains(e.target)) return;
      setOpen(false);
    };
    const esc = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", close, true);
    document.addEventListener("keydown", esc);
    return () => {
      document.removeEventListener("mousedown", close, true);
      document.removeEventListener("keydown", esc);
    };
  }, [open]);

  React.useEffect(() => {
    if (!open || !trigRef.current) return;
    const r = trigRef.current.getBoundingClientRect();
    const W = 320;
    const left = Math.min(window.innerWidth - W - 12, Math.max(12, r.left + (r.width / 2) - W / 2));
    const top = r.bottom + 8;
    const flip = top + 240 > window.innerHeight;
    setPos({ top: flip ? r.top - 8 : top, left, flip });
  }, [open]);

  if (!def) {
    return <span ref={trigRef} className="cv-term cv-term-undef" title={`Missing definition: ${name}`}>{labelEl}</span>;
  }
  return (
    <>
      <button
        ref={trigRef}
        type="button"
        className={`cv-term ${open ? "is-open" : ""}`}
        onClick={() => setOpen(o => !o)}
        aria-expanded={open}
      >
        <span className="cv-term-l">{labelEl}</span>
        <span className="cv-term-i" aria-hidden="true">ⓘ</span>
      </button>
      {open && (
        <TermPopover def={def} pos={pos} onClose={() => setOpen(false)} />
      )}
    </>
  );
}

function TermPopover({ def, pos, onClose }) {
  return ReactDOM.createPortal(
    <div
      id="cv-pop-active"
      className={`cv-pop ${pos.flip ? "cv-pop-up" : ""}`}
      style={{ top: pos.top, left: pos.left }}
      role="dialog"
    >
      <div className="cv-pop-arrow" aria-hidden="true"/>
      <div className="cv-pop-hd">
        <span className="cv-pop-eyebrow">Definition</span>
        <button className="cv-pop-x" onClick={onClose} aria-label="Close">×</button>
      </div>
      <div className="cv-pop-title">{def.title}</div>
      <p className="cv-pop-plain">{def.plain}</p>
      {def.why && (
        <p className="cv-pop-why">
          <span className="cv-pop-why-l">Why it matters</span>
          {def.why}
        </p>
      )}
      {def.more && (
        <a className="cv-pop-more" href={def.more.href} onClick={(e) => e.preventDefault()}>
          {def.more.label}
          <span aria-hidden="true"> →</span>
        </a>
      )}
    </div>,
    document.body
  );
}

// Source link — bigger, more prominent than the inline icon. Use this as the
// section-level "View source →" affordance the brief calls out.
function SourceButton({ label, href = "#", title }) {
  return (
    <a
      href={href}
      title={title || `Open original document: ${label}`}
      onClick={(e) => e.preventDefault()}
      className="cv-source-btn"
    >
      <SourceIcon size={12}/>
      <span className="cv-source-btn-l">View source</span>
      <span className="cv-source-btn-tag">{label}</span>
    </a>
  );
}

Object.assign(window, { Term, TERMS, SourceButton });
