/* === Quorum CSV re-upload ===
   Ingestion per spec §13: Quorum owns the FACTS (status, sponsors, dates);
   the dashboard owns the INTERPRETATION. Re-sync therefore:
     - keys on Quorum ID (falls back to state + bill label for pre-Quorum rows)
     - updates factual fields only (status, dates, sponsors, party, link)
     - NEVER overwrites a curated title on an existing bill
     - drops the bulky summary columns at parse — they are never stored
     - deletes nothing: bills missing from the export are flagged, not removed
   Every change lands in the event log like any other edit. Admin only
   (bill facts are shared data). */

function parseCsvText(text) {
  const rows = [];
  let row = [], field = "", inQ = false;
  for (let i = 0; i < text.length; i++) {
    const c = text[i];
    if (inQ) {
      if (c === '"') {
        if (text[i + 1] === '"') { field += '"'; i++; } else inQ = false;
      } else field += c;
    } else if (c === '"') {
      inQ = true;
    } else if (c === ",") {
      row.push(field); field = "";
    } else if (c === "\n" || c === "\r") {
      if (c === "\r" && text[i + 1] === "\n") i++;
      row.push(field); field = "";
      if (row.length > 1 || row[0] !== "") rows.push(row);
      row = [];
    } else field += c;
  }
  if (field !== "" || row.length) { row.push(field); rows.push(row); }
  return rows;
}

function usDate(s) {
  if (!s || !s.trim()) return null;
  const d = new Date(s.trim());
  if (isNaN(d)) return null;
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
}

/* "AK Sen. Bill Wielechowski (D-AK-000K), AK Rep. …" → "Wielechowski (D), …" */
function condenseSponsors(s) {
  if (!s || !s.trim()) return "";
  return s.split(/\),\s*/).map((x) => {
    const m = x.match(/^(.*?)\s*\((\w)/);
    if (!m) return null;
    const name = m[1].replace(/^[A-Z]{2}\s+(Sen\.|Rep\.|Del\.|Asm\.|Assem\.|Sens\.)\s+/i, "").trim();
    const last = name.split(" ").pop().replace(/["“”]/g, "");
    return `${last} (${m[2]})`;
  }).filter(Boolean).join(", ");
}

/* Build the sync plan from CSV text vs current window.BILLS. */
function buildQuorumPlan(text) {
  const rows = parseCsvText(text);
  if (rows.length < 2) throw new Error("That file has no data rows.");
  const header = rows[0].map((h) => h.trim());
  const col = (name) => header.indexOf(name);
  const need = ["Quorum ID", "Region Abbreviation", "State Abbreviation and Bill Label", "Status Text", "Status Date", "Date Introduced", "Primary Party", "Sponsors List", "Title (no number)", "Source Link"];
  for (const n of need) if (col(n) === -1) throw new Error(`Missing expected column "${n}" — is this the Quorum retirement-savings export?`);

  // Factual fields the sync may update on an existing bill.
  const FACT_KEYS = ["quorumId", "status", "statusDate", "intro", "sponsors", "party", "link"];

  const existingByQuorum = {}, existingById = {};
  for (const b of window.BILLS) {
    if (b.quorumId) existingByQuorum[String(b.quorumId)] = b;
    existingById[b.id] = b;
  }

  const creates = [], updates = [], unchanged = [];
  const seen = new Set();

  for (let i = 1; i < rows.length; i++) {
    const r = rows[i];
    if (r.length < header.length - 2) continue;
    const get = (name) => (r[col(name)] || "").trim();
    const quorumId = get("Quorum ID");
    const state = get("Region Abbreviation");
    const label = get("State Abbreviation and Bill Label"); // "AK S.B. 21"
    if (!state || !label) continue;
    const bill = label.replace(new RegExp("^" + state + "\\s+"), "");
    const id = state + "-" + bill.replace(/[^A-Za-z0-9]/g, "");

    const facts = {
      quorumId: quorumId || null,
      status: get("Status Text"),
      statusDate: usDate(get("Status Date")),
      intro: usDate(get("Date Introduced")),
      sponsors: condenseSponsors(get("Sponsors List")),
      party: { Democrat: "D", Republican: "R" }[get("Primary Party")] || "",
      link: get("Source Link") || null,
    };

    const existing = (quorumId && existingByQuorum[quorumId]) || existingById[id];
    if (existing) {
      seen.add(existing.id);
      const patch = {}, prev = {};
      for (const k of FACT_KEYS) {
        const nv = facts[k], ov = existing[k] === undefined ? null : existing[k];
        if (nv !== null && nv !== "" && nv !== ov) { patch[k] = nv; prev[k] = ov; }
      }
      if (Object.keys(patch).length) updates.push({ id: existing.id, bill: existing.bill, state: existing.state, patch, prev });
      else unchanged.push(existing.id);
    } else {
      seen.add(id);
      creates.push({
        id, data: {
          id, state, bill,
          title: get("Title (no number)") || bill,   // set once at create; curated afterwards
          ...facts,
        },
      });
    }
  }

  const missing = window.BILLS.filter((b) => !seen.has(b.id)).map((b) => `${b.state} ${b.bill}`);
  return { creates, updates, unchangedCount: unchanged.length, missing };
}

function QuorumUpload({ onClose }) {
  const me = window.Store.session();
  const isAdmin = me && me.role === "admin";
  const [plan, setPlan] = React.useState(null);
  const [err, setErr] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [done, setDone] = React.useState(false);

  React.useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);

  // Dev: ?upload=quorum&sample=1 parses the repo's Appendix A automatically (local mode).
  React.useEffect(() => {
    if (window.Store.mode === "local" && new URLSearchParams(location.search).get("sample") === "1") loadSample();
  }, []);

  function readFile(file) {
    setErr(null); setPlan(null);
    const rd = new FileReader();
    rd.onload = () => {
      try { setPlan(buildQuorumPlan(String(rd.result))); }
      catch (e) { setErr(String(e.message || e)); }
    };
    rd.readAsText(file);
  }

  async function loadSample() {
    setErr(null);
    try {
      const res = await fetch("/Appendix%20A%20-%20Quorum%20export%20(retirement-savings).csv");
      if (!res.ok) throw new Error("Sample not found on this server.");
      setPlan(buildQuorumPlan(await res.text()));
    } catch (e) { setErr(String(e.message || e)); }
  }

  async function apply() {
    setBusy(true); setErr(null);
    try {
      const events = [];
      for (const c of plan.creates) {
        events.push({ entityType: "bill", entityId: c.id, action: "create", patch: c.data, note: "Quorum upload" });
      }
      for (const u of plan.updates) {
        events.push({ entityType: "bill", entityId: u.id, action: "update", patch: u.patch, prev: u.prev, note: "Quorum upload" });
      }
      events.push({
        entityType: "meta", entityId: "app", action: "update",
        patch: { lastQuorumUpload: new Date().toISOString() }, note: "Quorum upload",
      });
      await window.Store.commit(events);
      setDone(true);
    } catch (e) { setErr(String(e.message || e)); }
    finally { setBusy(false); }
  }

  return (
    <>
      <div className="scrim is-open" onClick={onClose}></div>
      <div style={{
        position: "fixed", top: "50%", left: "50%", transform: "translate(-50%,-50%)",
        width: 560, maxWidth: "94vw", maxHeight: "86vh", overflowY: "auto", zIndex: 50,
      }} className="card">
        <div className="card__head">
          <div>
            <h3>Quorum upload</h3>
            <div className="section-sub">Re-sync tracked bills from a Quorum CSV export. Facts update; curated titles and dashboard interpretation are never touched.</div>
          </div>
          <button className="btn btn--ghost" onClick={onClose}>✕</button>
        </div>
        <div className="card__body">
          {!isAdmin ? (
            <div className="muted" style={{ fontSize: 13 }}>Bill data is shared — ask an admin (Andrew or John) to run uploads.</div>
          ) : done ? (
            <div>
              <div style={{ fontSize: 14, fontWeight: 600, color: "var(--green)" }}>✓ Sync applied</div>
              <div style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 6 }}>
                {plan.creates.length} new bills · {plan.updates.length} updated · every change is in the edit log.
              </div>
              <button className="btn btn--primary" style={{ marginTop: 14 }} onClick={onClose}>Close</button>
            </div>
          ) : (
            <>
              <div className="row" style={{ gap: 10, flexWrap: "wrap" }}>
                <label className="btn btn--primary" style={{ cursor: "pointer" }}>
                  Choose CSV…
                  <input type="file" accept=".csv,text/csv" style={{ display: "none" }}
                         onChange={(e) => e.target.files[0] && readFile(e.target.files[0])} />
                </label>
                {window.Store.mode === "local" ? (
                  <button className="btn" onClick={loadSample}>Load Appendix A sample</button>
                ) : null}
              </div>

              {err ? (
                <div style={{ marginTop: 12, padding: "8px 12px", borderRadius: 4, background: "var(--red-bg)", color: "var(--red)", fontSize: 12 }}>{err}</div>
              ) : null}

              {plan ? (
                <div style={{ marginTop: 16 }}>
                  <div className="tab-summary" style={{ padding: "0 0 12px", marginBottom: 12 }}>
                    <SumStat label="New bills" value={plan.creates.length} accent={plan.creates.length ? "green" : null} />
                    <SumStat label="Updated" value={plan.updates.length} accent={plan.updates.length ? "yellow" : null} />
                    <SumStat label="Unchanged" value={plan.unchangedCount} muted />
                    <SumStat label="Not in export" value={plan.missing.length} muted />
                  </div>

                  {plan.creates.length ? (
                    <div style={{ marginBottom: 12 }}>
                      <div className="eyebrow" style={{ marginBottom: 4 }}>New</div>
                      {plan.creates.slice(0, 15).map((c) => (
                        <div key={c.id} style={{ fontSize: 12, padding: "3px 0" }}>
                          <span className="mono" style={{ fontWeight: 600 }}>{c.data.state} {c.data.bill}</span>
                          <span className="muted"> — {c.data.status}{c.data.sponsors ? ` · ${c.data.sponsors}` : ""}</span>
                        </div>
                      ))}
                      {plan.creates.length > 15 ? <div className="mono faint" style={{ fontSize: 11 }}>+ {plan.creates.length - 15} more</div> : null}
                    </div>
                  ) : null}

                  {plan.updates.length ? (
                    <div style={{ marginBottom: 12 }}>
                      <div className="eyebrow" style={{ marginBottom: 4 }}>Changes</div>
                      {plan.updates.slice(0, 20).map((u) => (
                        <div key={u.id} style={{ fontSize: 12, padding: "3px 0" }}>
                          <span className="mono" style={{ fontWeight: 600 }}>{u.state} {u.bill}</span>
                          <span className="muted"> — {Object.keys(u.patch).map((k) =>
                            k === "quorumId" || k === "link" ? k + " set" : `${k}: ${u.prev[k] || "—"} → ${u.patch[k]}`
                          ).join(" · ")}</span>
                        </div>
                      ))}
                      {plan.updates.length > 20 ? <div className="mono faint" style={{ fontSize: 11 }}>+ {plan.updates.length - 20} more</div> : null}
                    </div>
                  ) : null}

                  {plan.missing.length ? (
                    <div style={{ marginBottom: 12 }}>
                      <div className="eyebrow" style={{ marginBottom: 4 }}>Tracked here but not in this export (kept, not deleted)</div>
                      <div style={{ fontSize: 12, color: "var(--ink-mute)" }}>{plan.missing.join(" · ")}</div>
                    </div>
                  ) : null}

                  <div className="row" style={{ marginTop: 8, gap: 8 }}>
                    <button className="btn btn--primary" disabled={busy || (!plan.creates.length && !plan.updates.length)} onClick={apply}>
                      {busy ? "Applying…" : `Apply sync (${plan.creates.length + plan.updates.length} changes)`}
                    </button>
                    <button className="btn btn--ghost" onClick={() => setPlan(null)}>Start over</button>
                  </div>
                </div>
              ) : (
                <div className="mono faint" style={{ fontSize: 11, marginTop: 14, lineHeight: 1.6 }}>
                  Expected format: the standard Quorum retirement-savings export (Appendix A columns).
                  Summary columns are dropped at parse and never stored.
                </div>
              )}
            </>
          )}
        </div>
      </div>
    </>
  );
}

function timeAgoShort(iso) {
  if (!iso) return "seed data";
  const s = (Date.now() - new Date(iso).getTime()) / 1000;
  if (isNaN(s)) return "—";
  if (s < 90) return "just now";
  if (s < 5400) return Math.round(s / 60) + "m ago";
  if (s < 129600) return Math.round(s / 3600) + "h ago";
  return Math.round(s / 86400) + "d ago";
}

Object.assign(window, { QuorumUpload, timeAgoShort });
