/* Saints Calendar — Research workspace
   A tabbed dashboard of analytical tools over the saints corpus. */

const ResearchView = ({ onOpenSaint }) => {
  const [tab, setTab] = React.useState("dashboard");
  const tabs = [
    { id: "dashboard", label: "Dashboard" },
    { id: "compare", label: "Compare" },
    { id: "query", label: "Query Builder" },
    { id: "network", label: "Network" },
    { id: "concordance", label: "Concordance" },
    { id: "patronage", label: "Patronage" },
    { id: "orders", label: "Orders" },
    { id: "atlas", label: "Atlas" },
    { id: "lexicon", label: "Lexicon" },
    { id: "calendars", label: "Calendars" },
    { id: "citations", label: "Citations" },
  ];

  return (
    <div className="view-enter">
      <section style={{ padding: "40px 0 24px", borderBottom: "1px solid var(--hairline-soft)" }}>
        <div className="container">
          <div className="flex items-baseline justify-between" style={{ flexWrap: "wrap", gap: 16 }}>
            <div>
              <div className="t-rubric mb-1" style={{ color: "var(--rubric)" }}>Scriptorium</div>
              <h1 className="t-display" style={{ fontSize: 56 }}>
                Research <span style={{ color: "var(--ink-mute)", fontStyle: "italic" }}>workspace</span>
              </h1>
              <p style={{ color: "var(--ink-soft)", fontSize: 16, marginTop: 8, maxWidth: 640 }}>
                Cross-corpus tools for serious enquiry — statistics, comparison, networks, queries, citations.
              </p>
            </div>
            <div className="t-meta" style={{ textAlign: "right" }}>
              <div>Corpus · {window.SAINTS.length} saints</div>
              <div>Calendar · {Object.keys(window.CALENDAR).length} feast days populated</div>
              <div>Sources · {window.SAINTS.reduce((n, s) => n + s.sources.length, 0)} citations</div>
            </div>
          </div>
        </div>
      </section>

      {/* Tabs */}
      <div style={{ position: "sticky", top: 68, zIndex: 30, background: "color-mix(in oklab, var(--parchment) 92%, transparent)", backdropFilter: "blur(10px)", borderBottom: "1px solid var(--hairline-soft)" }}>
        <div className="container" style={{ display: "flex", overflowX: "auto", gap: 4 }}>
          {tabs.map(t => (
            <button key={t.id} onClick={() => setTab(t.id)}
              style={{
                fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.16em", textTransform: "uppercase",
                padding: "16px 16px", whiteSpace: "nowrap",
                color: tab === t.id ? "var(--gilt-deep)" : "var(--ink-mute)",
                borderBottom: tab === t.id ? "2px solid var(--gilt)" : "2px solid transparent",
                marginBottom: -1,
              }}>
              {t.label}
            </button>
          ))}
        </div>
      </div>

      <div style={{ padding: "40px 0 80px" }}>
        {tab === "dashboard" && <DashboardTab onOpenSaint={onOpenSaint} />}
        {tab === "compare" && <CompareTab onOpenSaint={onOpenSaint} />}
        {tab === "query" && <QueryTab onOpenSaint={onOpenSaint} />}
        {tab === "network" && <NetworkTab onOpenSaint={onOpenSaint} />}
        {tab === "concordance" && <ConcordanceTab onOpenSaint={onOpenSaint} />}
        {tab === "patronage" && <PatronageTab onOpenSaint={onOpenSaint} />}
        {tab === "orders" && <OrdersTab onOpenSaint={onOpenSaint} />}
        {tab === "atlas" && <AtlasTab onOpenSaint={onOpenSaint} />}
        {tab === "lexicon" && <LexiconTab onOpenSaint={onOpenSaint} />}
        {tab === "calendars" && <CalendarsTab onOpenSaint={onOpenSaint} />}
        {tab === "citations" && <CitationsTab onOpenSaint={onOpenSaint} />}
      </div>
    </div>
  );
};

// ===== DASHBOARD =====
const DashboardTab = ({ onOpenSaint }) => {
  const stats = computeCorpusStats();
  return (
    <div className="container">
      {/* KPI strip */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(170px, 1fr))", gap: 12, marginBottom: 40 }}>
        <KPI label="Saints" value={stats.total} sub="in corpus" />
        <KPI label="Centuries spanned" value={stats.centurySpan} sub={`${stats.earliestYear} → ${stats.latestYear}`} />
        <KPI label="Median lifespan" value={`${stats.medianLifespan}y`} sub={`Mean ${stats.meanLifespan}y`} />
        <KPI label="Doctors of the Church" value={stats.doctors} sub="of 37 total" />
        <KPI label="Martyrs" value={stats.martyrs} sub={`${stats.martyrPct}% of corpus`} />
        <KPI label="Religious orders" value={stats.uniqueOrders} sub={`${stats.orderedSaints} saints in orders`} />
        <KPI label="Regions" value={stats.uniqueRegions} sub="of origin" />
        <KPI label="Citations" value={stats.totalSources} sub={`${stats.avgSourcesPerSaint} per saint avg.`} />
      </div>

      {/* Charts */}
      <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 28, marginBottom: 28 }}>
        <ChartCard title="Saints by century" eyebrow="Distribution">
          <CenturyBars data={stats.byCentury} onSelect={c => {/* future */}} />
        </ChartCard>
        <ChartCard title="By classification" eyebrow="Composition">
          <DonutChart data={stats.byClassification} />
        </ChartCard>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 28, marginBottom: 28 }}>
        <ChartCard title="By region of origin" eyebrow="Geography">
          <HBars data={stats.byRegion} accent="var(--gilt)" />
        </ChartCard>
        <ChartCard title="By religious order" eyebrow="Affiliation">
          <HBars data={stats.byOrder} accent="var(--lapis)" />
        </ChartCard>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 28 }}>
        <ChartCard title="Liturgical rank" eyebrow="Ranking">
          <HBars data={stats.byRank} accent="var(--rubric)" />
        </ChartCard>
        <ChartCard title="Calendar tradition" eyebrow="Cross-tradition">
          <HBars data={stats.byTradition} accent="var(--ink)" />
        </ChartCard>
        <ChartCard title="Lifespan distribution" eyebrow="Age at death">
          <Histogram data={stats.lifespans} />
        </ChartCard>
      </div>

      <div className="hairline-gilt mt-8" style={{ marginTop: 56, paddingTop: 32 }}>
        <div className="t-rubric mb-2">Recently sampled</div>
        <h3 className="t-display mb-3" style={{ fontSize: 26 }}>Spotlight saints</h3>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))", gap: 14 }}>
          {[...window.SAINTS].sort(() => 0.5 - Math.random()).slice(0, 4).map(s => (
            <button key={s.id} onClick={() => onOpenSaint(s.id)} className="card" style={{ textAlign: "left", padding: 18 }}
              onMouseEnter={(e) => e.currentTarget.style.borderColor = "var(--gilt)"}
              onMouseLeave={(e) => e.currentTarget.style.borderColor = "var(--hairline-soft)"}
            >
              <div className="t-meta">{formatDate(s.feast.month, s.feast.day)} · {yearLabel(s.dates.born)}</div>
              <div style={{ fontFamily: "var(--font-display)", fontSize: 19, marginTop: 4 }}>{s.honorific} {s.name}</div>
              <div style={{ color: "var(--ink-mute)", fontSize: 13, fontStyle: "italic" }}>{s.epithet}</div>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
};

const KPI = ({ label, value, sub }) => (
  <div className="card" style={{ padding: 18 }}>
    <div className="t-rubric" style={{ color: "var(--ink-mute)", fontSize: 9 }}>{label}</div>
    <div style={{ fontFamily: "var(--font-display)", fontSize: 36, lineHeight: 1, marginTop: 6, color: "var(--ink)" }}>{value}</div>
    <div className="t-meta" style={{ marginTop: 4 }}>{sub}</div>
  </div>
);

const ChartCard = ({ title, eyebrow, children }) => (
  <div className="card" style={{ padding: 24 }}>
    <div className="t-rubric mb-1">{eyebrow}</div>
    <h3 style={{ fontFamily: "var(--font-display)", fontSize: 22, marginBottom: 18 }}>{title}</h3>
    {children}
  </div>
);

// ===== CHARTS =====
const CenturyBars = ({ data }) => {
  const max = Math.max(...data.map(d => d.value));
  return (
    <div style={{ display: "flex", alignItems: "flex-end", gap: 4, height: 220, paddingBottom: 24, position: "relative" }}>
      {data.map(d => (
        <div key={d.label} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 4, position: "relative" }}>
          <div style={{
            width: "100%",
            height: `${(d.value / max) * 180}px`,
            background: d.value > 0 ? "var(--gilt)" : "var(--hairline)",
            opacity: d.value > 0 ? (0.4 + 0.6 * (d.value / max)) : 0.3,
            transition: "all 0.3s",
            position: "relative",
          }} title={`${d.label}: ${d.value} saint${d.value!==1?"s":""}`}>
            {d.value > 0 && (
              <div style={{ position: "absolute", top: -18, left: "50%", transform: "translateX(-50%)", fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--ink-mute)" }}>{d.value}</div>
            )}
          </div>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 9, color: "var(--ink-mute)", whiteSpace: "nowrap" }}>{d.label}</div>
        </div>
      ))}
    </div>
  );
};

const DonutChart = ({ data }) => {
  const total = data.reduce((s, d) => s + d.value, 0);
  let cum = 0;
  const colors = ["var(--gilt)", "var(--rubric)", "var(--lapis)", "var(--ink-soft)", "var(--gilt-deep)", "var(--ink-mute)"];
  const r = 60, cx = 90, cy = 90;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 18 }}>
      <svg viewBox="0 0 180 180" width={160} height={160}>
        {data.map((d, i) => {
          const startA = (cum / total) * 2 * Math.PI - Math.PI / 2;
          cum += d.value;
          const endA = (cum / total) * 2 * Math.PI - Math.PI / 2;
          const large = (endA - startA) > Math.PI ? 1 : 0;
          const x1 = cx + r * Math.cos(startA), y1 = cy + r * Math.sin(startA);
          const x2 = cx + r * Math.cos(endA), y2 = cy + r * Math.sin(endA);
          return (
            <path key={d.label}
              d={`M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`}
              fill={colors[i % colors.length]}
              stroke="var(--parchment)"
              strokeWidth="2"
            />
          );
        })}
        <circle cx={cx} cy={cy} r={32} fill="var(--parchment)" />
        <text x={cx} y={cy-2} textAnchor="middle" style={{ fontFamily: "var(--font-display)", fontSize: 22 }} fill="var(--ink)">{total}</text>
        <text x={cx} y={cy+14} textAnchor="middle" style={{ fontFamily: "var(--font-mono)", fontSize: 9 }} fill="var(--ink-mute)">SAINTS</text>
      </svg>
      <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 6, fontSize: 13 }}>
        {data.map((d, i) => (
          <div key={d.label} style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ width: 10, height: 10, background: colors[i % colors.length], display: "inline-block" }}></span>
            <span style={{ flex: 1, textTransform: "capitalize" }}>{d.label}</span>
            <span className="t-meta">{d.value}</span>
          </div>
        ))}
      </div>
    </div>
  );
};

const HBars = ({ data, accent }) => {
  const max = Math.max(...data.map(d => d.value));
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
      {data.map(d => (
        <div key={d.label} style={{ display: "grid", gridTemplateColumns: "120px 1fr 30px", gap: 10, alignItems: "center", fontSize: 13 }}>
          <span style={{ color: "var(--ink-soft)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }} title={d.label}>{d.label}</span>
          <div style={{ height: 14, background: "var(--hairline-soft)", position: "relative" }}>
            <div style={{ width: `${(d.value / max) * 100}%`, height: "100%", background: accent, opacity: 0.85 }}></div>
          </div>
          <span className="t-meta" style={{ textAlign: "right" }}>{d.value}</span>
        </div>
      ))}
    </div>
  );
};

const Histogram = ({ data }) => {
  // bucket 0-100 in 10s
  const buckets = Array.from({ length: 10 }, (_, i) => ({ label: `${i*10}`, value: 0 }));
  data.forEach(v => {
    const idx = Math.min(9, Math.floor(v / 10));
    if (idx >= 0) buckets[idx].value++;
  });
  return <CenturyBars data={buckets} />;
};

// ===== STATS =====
function computeCorpusStats() {
  const S = window.SAINTS;
  const lifespans = S.map(s => s.dates.died - s.dates.born).filter(n => n > 0);
  const meanLifespan = Math.round(lifespans.reduce((a,b)=>a+b,0) / lifespans.length);
  const medianLifespan = lifespans.slice().sort((a,b)=>a-b)[Math.floor(lifespans.length/2)];
  const earliestYear = Math.min(...S.map(s => s.dates.born));
  const latestYear = Math.max(...S.map(s => s.dates.died));

  const byCentury = [];
  for (let c = 0; c <= 20; c++) {
    const count = S.filter(s => {
      const cs = Math.ceil(s.dates.born / 100);
      const ce = Math.ceil(s.dates.died / 100);
      return c >= (cs <= 0 ? 0 : cs) && c <= ce;
    }).length;
    byCentury.push({ label: c === 0 ? "I" : `${c}`, value: count });
  }

  const tally = (key) => {
    const m = {};
    S.forEach(s => {
      const v = typeof key === "function" ? key(s) : s[key];
      const arr = Array.isArray(v) ? v : [v];
      arr.forEach(x => { if (x) m[x] = (m[x] || 0) + 1; });
    });
    return Object.entries(m).map(([label, value]) => ({ label, value })).sort((a,b) => b.value - a.value);
  };

  return {
    total: S.length,
    centurySpan: 21,
    earliestYear: yearLabel(earliestYear),
    latestYear,
    meanLifespan, medianLifespan,
    doctors: S.filter(s => s.titles.includes("Doctor of the Church")).length,
    martyrs: S.filter(s => s.classification === "martyr").length,
    martyrPct: Math.round(S.filter(s => s.classification === "martyr").length / S.length * 100),
    uniqueOrders: new Set(S.map(s => s.order).filter(Boolean)).size,
    orderedSaints: S.filter(s => s.order).length,
    uniqueRegions: new Set(S.map(s => s.region)).size,
    totalSources: S.reduce((n, s) => n + s.sources.length, 0),
    avgSourcesPerSaint: (S.reduce((n, s) => n + s.sources.length, 0) / S.length).toFixed(1),
    byCentury,
    byClassification: tally("classification"),
    byRegion: tally("region").slice(0, 8),
    byOrder: tally(s => s.order || "Diocesan / Lay").slice(0, 8),
    byRank: tally("rank"),
    byTradition: tally("tradition").slice(0, 6),
    lifespans,
  };
}

// ===== COMPARE =====
const CompareTab = ({ onOpenSaint }) => {
  const [aId, setAId] = React.useState("augustine-hippo");
  const [bId, setBId] = React.useState("francis-assisi");
  const [cId, setCId] = React.useState(null);
  const ids = [aId, bId, cId].filter(Boolean);
  const saints = ids.map(id => window.getSaintById(id)).filter(Boolean);

  const fields = [
    ["Born", s => yearLabel(s.dates.born)],
    ["Birthplace", s => s.dates.bornPlace],
    ["Died", s => yearLabel(s.dates.died)],
    ["Place of death", s => s.dates.diedPlace],
    ["Lifespan", s => `${s.dates.died - s.dates.born} years`],
    ["Feast", s => formatDate(s.feast.month, s.feast.day)],
    ["Rank", s => s.rank],
    ["Region", s => s.region],
    ["Order", s => s.order || "—"],
    ["Classification", s => s.classification],
    ["Titles", s => s.titles.join(", ")],
    ["Tradition", s => s.tradition.join(", ")],
    ["Patronages", s => s.patronage.length],
    ["Attributes", s => s.attributes.join(", ")],
    ["Quotes recorded", s => s.quotes.filter(q => q.source !== "—").length],
    ["Sources cited", s => s.sources.length],
  ];

  return (
    <div className="container">
      <div className="t-rubric mb-2">Side-by-side</div>
      <h2 className="t-display mb-4" style={{ fontSize: 32 }}>Compare saints</h2>

      <div style={{ display: "grid", gridTemplateColumns: `200px repeat(${saints.length + (cId ? 0 : 1)}, 1fr)`, gap: 0, border: "1px solid var(--hairline-soft)" }}>
        {/* Header */}
        <div style={{ padding: 16, background: "color-mix(in oklab, var(--parchment) 88%, var(--parchment-deep))", borderBottom: "1px solid var(--hairline-soft)" }}></div>
        {saints.map((s, i) => (
          <div key={s.id} style={{ padding: 16, borderLeft: "1px solid var(--hairline-soft)", borderBottom: "1px solid var(--hairline-soft)", background: "color-mix(in oklab, var(--parchment) 88%, var(--parchment-deep))" }}>
            <SaintPicker
              value={s.id}
              exclude={ids.filter(x => x !== s.id)}
              onChange={(v) => { if (i===0) setAId(v); else if (i===1) setBId(v); else setCId(v); }}
            />
            <div style={{ fontFamily: "var(--font-display)", fontSize: 22, marginTop: 8, lineHeight: 1.1 }}>
              {s.honorific} {s.name}
            </div>
            <div className="t-meta" style={{ fontStyle: "italic", marginTop: 2 }}>{s.epithet}</div>
            {i === 2 && (
              <button className="t-meta" onClick={() => setCId(null)} style={{ marginTop: 6, color: "var(--rubric)" }}>Remove</button>
            )}
          </div>
        ))}
        {!cId && (
          <div style={{ padding: 16, borderLeft: "1px solid var(--hairline-soft)", borderBottom: "1px solid var(--hairline-soft)" }}>
            <button className="btn btn-sm" onClick={() => setCId("therese-lisieux")}>+ Add third</button>
          </div>
        )}

        {/* Rows */}
        {fields.map(([label, getter], rowIdx) => (
          <React.Fragment key={label}>
            <div style={{ padding: "12px 16px", fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--ink-mute)", borderBottom: "1px solid var(--hairline-soft)", background: rowIdx % 2 ? "transparent" : "color-mix(in oklab, var(--parchment) 95%, var(--parchment-deep))" }}>
              {label}
            </div>
            {saints.map(s => (
              <div key={s.id + label} style={{ padding: "12px 16px", borderLeft: "1px solid var(--hairline-soft)", borderBottom: "1px solid var(--hairline-soft)", fontSize: 14, background: rowIdx % 2 ? "transparent" : "color-mix(in oklab, var(--parchment) 95%, var(--parchment-deep))" }}>
                {getter(s)}
              </div>
            ))}
            {!cId && <div style={{ borderLeft: "1px solid var(--hairline-soft)", borderBottom: "1px solid var(--hairline-soft)", background: rowIdx % 2 ? "transparent" : "color-mix(in oklab, var(--parchment) 95%, var(--parchment-deep))" }}></div>}
          </React.Fragment>
        ))}
      </div>

      <div className="mt-6" style={{ display: "grid", gridTemplateColumns: `repeat(${saints.length}, 1fr)`, gap: 24 }}>
        {saints.map(s => (
          <div key={s.id}>
            <div className="t-rubric mb-2">Summary</div>
            <p style={{ fontSize: 14, lineHeight: 1.55, color: "var(--ink-soft)" }}>{s.summary}</p>
            <button className="btn btn-sm mt-3" onClick={() => onOpenSaint(s.id)}>Open profile <IconArrowR /></button>
          </div>
        ))}
      </div>
    </div>
  );
};

const SaintPicker = ({ value, exclude, onChange }) => (
  <select value={value} onChange={(e) => onChange(e.target.value)}
    style={{ background: "var(--parchment)", border: "1px solid var(--hairline)", padding: "6px 10px", fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-soft)", width: "100%" }}>
    {window.SAINTS.filter(s => !exclude.includes(s.id)).map(s => (
      <option key={s.id} value={s.id}>{s.honorific} {s.name}</option>
    ))}
  </select>
);

// ===== QUERY BUILDER =====
const QueryTab = ({ onOpenSaint }) => {
  const [conds, setConds] = React.useState([
    { field: "classification", op: "is", value: "martyr" }
  ]);
  const [logic, setLogic] = React.useState("AND");

  const addCond = () => setConds(c => [...c, { field: "region", op: "is", value: "Italy" }]);
  const updateCond = (i, patch) => setConds(c => c.map((x, j) => j === i ? { ...x, ...patch } : x));
  const removeCond = i => setConds(c => c.filter((_, j) => j !== i));

  const fieldDefs = {
    classification: { label: "Classification", values: [...new Set(window.SAINTS.map(s => s.classification))], get: s => s.classification },
    region: { label: "Region", values: [...new Set(window.SAINTS.map(s => s.region))], get: s => s.region },
    order: { label: "Order", values: [...new Set(window.SAINTS.map(s => s.order || "—"))], get: s => s.order || "—" },
    rank: { label: "Liturgical rank", values: [...new Set(window.SAINTS.map(s => s.rank))], get: s => s.rank },
    title: { label: "Title", values: [...new Set(window.SAINTS.flatMap(s => s.titles))], get: s => s.titles, multi: true },
    tradition: { label: "Tradition", values: [...new Set(window.SAINTS.flatMap(s => s.tradition))], get: s => s.tradition, multi: true },
    patronage: { label: "Patronage includes", values: null, get: s => s.patronage.join(" ").toLowerCase(), text: true },
    century: { label: "Active in century", values: ["1","2","3","4","5","6","7","8","11","12","13","15","16","19"], get: s => s.centuries.map(String), multi: true },
    bornAfter: { label: "Born after (year)", values: null, get: s => s.dates.born, num: true },
    bornBefore: { label: "Born before (year)", values: null, get: s => s.dates.born, num: true },
    lifespanMin: { label: "Lifespan ≥ years", values: null, get: s => s.dates.died - s.dates.born, num: true },
  };

  const matches = window.SAINTS.filter(s => {
    const test = (c) => {
      const def = fieldDefs[c.field]; if (!def) return true;
      const v = def.get(s);
      if (def.text) return v.includes((c.value || "").toLowerCase());
      if (def.num) return c.op === ">=" ? v >= +c.value : c.op === "<=" ? v <= +c.value : v === +c.value;
      if (def.multi) return c.op === "is" ? v.includes(c.value) : !v.includes(c.value);
      return c.op === "is" ? v === c.value : v !== c.value;
    };
    return logic === "AND" ? conds.every(test) : conds.some(test);
  });

  return (
    <div className="container">
      <div className="t-rubric mb-2">Advanced</div>
      <h2 className="t-display mb-4" style={{ fontSize: 32 }}>Query builder</h2>

      <div className="card" style={{ padding: 24, marginBottom: 28 }}>
        <div className="flex items-center gap-2 mb-3">
          <span className="t-eyebrow">Match</span>
          <button className={`chip ${logic === "AND" ? "active" : ""}`} onClick={() => setLogic("AND")}>ALL conditions</button>
          <button className={`chip ${logic === "OR" ? "active" : ""}`} onClick={() => setLogic("OR")}>ANY condition</button>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {conds.map((c, i) => {
            const def = fieldDefs[c.field];
            return (
              <div key={i} style={{ display: "grid", gridTemplateColumns: "200px 100px 1fr 40px", gap: 8, alignItems: "center" }}>
                <select value={c.field} onChange={e => updateCond(i, { field: e.target.value, value: fieldDefs[e.target.value].values?.[0] || "" })}
                  style={{ padding: "8px 10px", border: "1px solid var(--hairline)", background: "var(--parchment)" }}>
                  {Object.entries(fieldDefs).map(([k, d]) => <option key={k} value={k}>{d.label}</option>)}
                </select>
                <select value={c.op} onChange={e => updateCond(i, { op: e.target.value })}
                  style={{ padding: "8px 10px", border: "1px solid var(--hairline)", background: "var(--parchment)" }}>
                  {def.num ? <><option value=">=">≥</option><option value="<=">≤</option></> : <><option value="is">is</option><option value="not">is not</option></>}
                </select>
                {def.values ? (
                  <select value={c.value} onChange={e => updateCond(i, { value: e.target.value })}
                    style={{ padding: "8px 10px", border: "1px solid var(--hairline)", background: "var(--parchment)" }}>
                    {def.values.map(v => <option key={v} value={v}>{v}</option>)}
                  </select>
                ) : (
                  <input type={def.num ? "number" : "text"} value={c.value || ""} onChange={e => updateCond(i, { value: e.target.value })}
                    style={{ padding: "8px 10px", border: "1px solid var(--hairline)", background: "var(--parchment)" }} />
                )}
                <button onClick={() => removeCond(i)} className="btn btn-ghost btn-sm" style={{ padding: 6 }}><IconClose /></button>
              </div>
            );
          })}
        </div>
        <button className="btn btn-sm mt-3" onClick={addCond}>+ Add condition</button>
      </div>

      <div className="flex items-baseline justify-between mb-3">
        <div>
          <span className="t-rubric">Results</span>
          <span style={{ fontFamily: "var(--font-display)", fontSize: 28, marginLeft: 12, color: "var(--gilt-deep)" }}>{matches.length}</span>
          <span style={{ color: "var(--ink-mute)", marginLeft: 6 }}>of {window.SAINTS.length}</span>
        </div>
      </div>
      <div>
        {matches.map(s => <SaintListItem key={s.id} saint={s} onOpen={onOpenSaint} />)}
        {matches.length === 0 && <div className="t-meta" style={{ padding: 40, textAlign: "center" }}>No saints match these conditions.</div>}
      </div>
    </div>
  );
};

// ===== NETWORK =====
const NetworkTab = ({ onOpenSaint }) => {
  // Force-directed-ish layout: arrange in concentric rings by century, link by `related`
  const S = window.SAINTS;
  const [hoveredId, setHoveredId] = React.useState(null);
  const W = 900, H = 620, cx = W/2, cy = H/2;

  // Positions: angle by id hash, radius by century
  const positions = {};
  S.forEach((s, i) => {
    const angle = (i / S.length) * 2 * Math.PI - Math.PI/2;
    const c = Math.max(1, Math.ceil(s.dates.born / 100) || 1);
    const r = 60 + (c / 21) * 220 + (i % 3) * 18;
    positions[s.id] = { x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle) };
  });

  const edges = [];
  S.forEach(s => (s.related || []).forEach(rid => {
    if (positions[rid]) edges.push({ from: s.id, to: rid });
  }));

  const linkedToHover = new Set();
  if (hoveredId) {
    linkedToHover.add(hoveredId);
    edges.forEach(e => {
      if (e.from === hoveredId) linkedToHover.add(e.to);
      if (e.to === hoveredId) linkedToHover.add(e.from);
    });
  }

  return (
    <div className="container">
      <div className="t-rubric mb-2">Relationships</div>
      <h2 className="t-display mb-2" style={{ fontSize: 32 }}>Network of holiness</h2>
      <p style={{ color: "var(--ink-soft)", fontSize: 15, marginBottom: 24, maxWidth: 640 }}>
        Saints connected by direct discipleship, contemporary friendship, or shared cause.
        Distance from centre encodes century — the apostolic age at the heart, the modern world at the rim.
      </p>

      <div className="card" style={{ padding: 12, position: "relative" }}>
        <svg viewBox={`0 0 ${W} ${H}`} style={{ width: "100%", height: "auto", display: "block" }}>
          {/* Century rings */}
          {[5, 10, 15, 20].map(c => (
            <circle key={c} cx={cx} cy={cy} r={60 + (c / 21) * 220}
              fill="none" stroke="var(--hairline)" strokeDasharray="2 4" opacity="0.5" />
          ))}
          {[5, 10, 15, 20].map(c => (
            <text key={"l"+c} x={cx + (60 + (c / 21) * 220) + 4} y={cy + 4}
              fontSize="9" fontFamily="var(--font-mono)" fill="var(--ink-mute)">{c}c</text>
          ))}

          {/* Edges */}
          {edges.map((e, i) => {
            const a = positions[e.from], b = positions[e.to];
            const active = !hoveredId || (linkedToHover.has(e.from) && linkedToHover.has(e.to));
            return (
              <line key={i} x1={a.x} y1={a.y} x2={b.x} y2={b.y}
                stroke="var(--gilt)" strokeWidth={active ? 1 : 0.4} opacity={active ? 0.6 : 0.12} />
            );
          })}

          {/* Nodes */}
          {S.map(s => {
            const p = positions[s.id];
            const active = !hoveredId || linkedToHover.has(s.id);
            const isMartyr = s.classification === "martyr";
            const radius = s.titles.includes("Doctor of the Church") ? 8 : isMartyr ? 6 : 5;
            return (
              <g key={s.id}
                transform={`translate(${p.x}, ${p.y})`}
                style={{ cursor: "pointer", opacity: active ? 1 : 0.25, transition: "opacity 0.2s" }}
                onMouseEnter={() => setHoveredId(s.id)}
                onMouseLeave={() => setHoveredId(null)}
                onClick={() => onOpenSaint(s.id)}
              >
                <circle r={radius + 3} fill="var(--parchment)" />
                <circle r={radius} fill={isMartyr ? "var(--rubric)" : "var(--gilt)"}
                  stroke={hoveredId === s.id ? "var(--ink)" : "none"} strokeWidth="1.5" />
                {(active || hoveredId === s.id) && (
                  <text x={radius + 6} y={4} fontSize="11" fontFamily="var(--font-display)"
                    fill="var(--ink)">{s.name}</text>
                )}
              </g>
            );
          })}
        </svg>

        <div style={{ position: "absolute", left: 24, bottom: 24, fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--ink-mute)" }}>
          <div><span style={{ display: "inline-block", width: 8, height: 8, background: "var(--gilt)", borderRadius: "50%", marginRight: 6 }}></span>Confessor / virgin / etc.</div>
          <div><span style={{ display: "inline-block", width: 8, height: 8, background: "var(--rubric)", borderRadius: "50%", marginRight: 6 }}></span>Martyr</div>
          <div style={{ marginTop: 4 }}>Larger node = Doctor of the Church</div>
        </div>

        {hoveredId && (
          <div style={{ position: "absolute", right: 24, top: 24, width: 260, padding: 18, background: "var(--parchment)", border: "1px solid var(--hairline)", boxShadow: "var(--shadow-md)" }}>
            {(() => { const s = window.getSaintById(hoveredId);
              return (<>
                <div className="t-rubric mb-1">{s.rank}</div>
                <div style={{ fontFamily: "var(--font-display)", fontSize: 20, marginBottom: 6 }}>{s.honorific} {s.name}</div>
                <div className="t-meta" style={{ fontStyle: "italic" }}>{s.epithet}</div>
                <div className="t-meta mt-2">{yearLabel(s.dates.born)}–{yearLabel(s.dates.died)} · {s.region}</div>
                <div className="t-meta mt-1">{(s.related||[]).length} connection{(s.related||[]).length!==1?"s":""}</div>
              </>); })()}
          </div>
        )}
      </div>
    </div>
  );
};

// ===== CONCORDANCE (text search across bios + quotes) =====
const ConcordanceTab = ({ onOpenSaint }) => {
  const [q, setQ] = React.useState("prayer");
  const Q = q.toLowerCase().trim();

  const hits = [];
  window.SAINTS.forEach(s => {
    const sources = {
      "Summary": s.summary,
      "Early Life": s.bio.earlyLife,
      "Conversion": s.bio.conversion,
      "Ministry": s.bio.ministry,
      "Death": s.bio.death,
      "Legacy": s.bio.legacy,
      "Prayer": s.prayer,
    };
    Object.entries(sources).forEach(([section, text]) => {
      if (!text) return;
      const lower = text.toLowerCase();
      let idx = 0;
      while ((idx = lower.indexOf(Q, idx)) !== -1 && Q) {
        const start = Math.max(0, idx - 60);
        const end = Math.min(text.length, idx + Q.length + 60);
        hits.push({
          saint: s, section,
          before: (start > 0 ? "…" : "") + text.slice(start, idx),
          match: text.slice(idx, idx + Q.length),
          after: text.slice(idx + Q.length, end) + (end < text.length ? "…" : "")
        });
        idx += Q.length;
      }
    });
    s.quotes.forEach(quote => {
      if (quote.text.toLowerCase().includes(Q) && Q) {
        const text = quote.text;
        const lower = text.toLowerCase();
        const idx = lower.indexOf(Q);
        hits.push({
          saint: s, section: `Quote (${quote.source})`,
          before: text.slice(0, idx), match: text.slice(idx, idx + Q.length), after: text.slice(idx + Q.length)
        });
      }
    });
  });

  return (
    <div className="container">
      <div className="t-rubric mb-2">KWIC</div>
      <h2 className="t-display mb-4" style={{ fontSize: 32 }}>Concordance</h2>

      <div className="search-shell mb-4" style={{ maxWidth: 540 }}>
        <IconSearch size={18} />
        <input value={q} onChange={e => setQ(e.target.value)} placeholder="Search every word in the corpus…" style={{ marginLeft: 14 }} />
      </div>

      <div className="t-meta mb-3">{Q ? `${hits.length} occurrence${hits.length !== 1 ? "s" : ""} of "${q}"` : "Type to search"}</div>

      <div style={{ fontFamily: "var(--font-mono)", fontSize: 13, lineHeight: 1.7 }}>
        {hits.slice(0, 80).map((h, i) => (
          <button key={i} onClick={() => onOpenSaint(h.saint.id)}
            style={{ display: "grid", gridTemplateColumns: "200px 1fr 1fr 1fr", gap: 16, padding: "8px 0", borderBottom: "1px solid var(--hairline-soft)", width: "100%", textAlign: "left" }}>
            <span style={{ color: "var(--ink-mute)", fontSize: 11 }}>{h.saint.honorific} {h.saint.name} · {h.section}</span>
            <span style={{ color: "var(--ink-soft)", textAlign: "right", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{h.before}</span>
            <span style={{ background: "color-mix(in oklab, var(--gilt) 25%, transparent)", padding: "1px 4px", color: "var(--ink)", fontWeight: 500, textAlign: "center" }}>{h.match}</span>
            <span style={{ color: "var(--ink-soft)" }}>{h.after}</span>
          </button>
        ))}
        {hits.length > 80 && <div className="t-meta mt-3">+ {hits.length - 80} more results — refine your query</div>}
      </div>
    </div>
  );
};

// ===== ATLAS (relics & geography table) =====
const AtlasTab = ({ onOpenSaint }) => {
  const rows = [];
  window.SAINTS.forEach(s => {
    rows.push({ saint: s, kind: "Birth", place: s.dates.bornPlace });
    rows.push({ saint: s, kind: "Death", place: s.dates.diedPlace });
    s.relics.forEach(r => rows.push({ saint: s, kind: "Relic", place: `${r.place}, ${r.city}` }));
  });
  const [filter, setFilter] = React.useState("");
  const [kind, setKind] = React.useState("All");
  const filtered = rows.filter(r =>
    (kind === "All" || r.kind === kind) &&
    (!filter || r.place.toLowerCase().includes(filter.toLowerCase()))
  );

  return (
    <div className="container">
      <div className="t-rubric mb-2">Geography</div>
      <h2 className="t-display mb-4" style={{ fontSize: 32 }}>Atlas of relics & places</h2>

      <div className="flex gap-2 mb-3" style={{ flexWrap: "wrap" }}>
        {["All", "Birth", "Death", "Relic"].map(k => (
          <button key={k} className={`chip ${kind === k ? "active" : ""}`} onClick={() => setKind(k)}>{k}</button>
        ))}
        <input value={filter} onChange={e => setFilter(e.target.value)} placeholder="Filter places…"
          style={{ padding: "6px 12px", border: "1px solid var(--hairline)", background: "var(--parchment)", marginLeft: "auto", width: 240 }} />
      </div>

      <div style={{ border: "1px solid var(--hairline-soft)" }}>
        <div style={{ display: "grid", gridTemplateColumns: "100px 1fr 1.5fr 100px", padding: "10px 16px", background: "color-mix(in oklab, var(--parchment) 88%, var(--parchment-deep))", borderBottom: "1px solid var(--hairline-soft)" }}>
          <div className="t-rubric">Type</div>
          <div className="t-rubric">Saint</div>
          <div className="t-rubric">Place</div>
          <div className="t-rubric">Era</div>
        </div>
        {filtered.slice(0, 200).map((r, i) => (
          <button key={i} onClick={() => onOpenSaint(r.saint.id)}
            style={{ display: "grid", gridTemplateColumns: "100px 1fr 1.5fr 100px", padding: "10px 16px", borderBottom: "1px solid var(--hairline-soft)", width: "100%", textAlign: "left", fontSize: 14, alignItems: "baseline" }}>
            <div><span className={`chip ${r.kind === "Relic" ? "chip-rubric" : ""}`} style={{ fontSize: 9 }}>{r.kind}</span></div>
            <div style={{ fontFamily: "var(--font-display)", fontSize: 16, fontStyle: "italic" }}>{r.saint.honorific} {r.saint.name}</div>
            <div style={{ color: "var(--ink-soft)" }}>{r.place}</div>
            <div className="t-meta">{yearLabel(r.saint.dates.born)}</div>
          </button>
        ))}
      </div>
      <div className="t-meta mt-2">{filtered.length} entr{filtered.length !== 1 ? "ies" : "y"}</div>
    </div>
  );
};

// ===== LEXICON (every patronage / attribute / title with frequency) =====
const LexiconTab = ({ onOpenSaint }) => {
  const [field, setField] = React.useState("patronage");
  const fieldOpts = {
    patronage: { label: "Patronages", get: s => s.patronage },
    attributes: { label: "Iconographic attributes", get: s => s.attributes },
    titles: { label: "Titles", get: s => s.titles },
    tradition: { label: "Calendar traditions", get: s => s.tradition },
  };

  const tally = {};
  window.SAINTS.forEach(s => fieldOpts[field].get(s).forEach(v => {
    if (!tally[v]) tally[v] = [];
    tally[v].push(s);
  }));
  const sorted = Object.entries(tally).sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]));

  return (
    <div className="container">
      <div className="t-rubric mb-2">Index</div>
      <h2 className="t-display mb-2" style={{ fontSize: 32 }}>Lexicon</h2>
      <p style={{ color: "var(--ink-soft)", fontSize: 15, marginBottom: 18 }}>Every {fieldOpts[field].label.toLowerCase()} in the corpus, with frequencies.</p>

      <div className="flex gap-2 mb-4">
        {Object.entries(fieldOpts).map(([k, v]) => (
          <button key={k} className={`chip ${field === k ? "active" : ""}`} onClick={() => setField(k)}>{v.label}</button>
        ))}
      </div>

      <div style={{ columnCount: 3, columnGap: 32 }}>
        {sorted.map(([term, saints]) => (
          <div key={term} style={{ breakInside: "avoid", marginBottom: 14, paddingBottom: 10, borderBottom: "1px solid var(--hairline-soft)" }}>
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 10 }}>
              <span style={{ fontFamily: "var(--font-display)", fontSize: 17 }}>{term}</span>
              <span className="t-meta">{saints.length}</span>
            </div>
            <div style={{ fontSize: 12, color: "var(--ink-mute)", lineHeight: 1.5, marginTop: 2 }}>
              {saints.slice(0, 4).map((s, i) => (
                <React.Fragment key={s.id}>
                  <button onClick={() => onOpenSaint(s.id)} style={{ fontStyle: "italic" }}
                    onMouseEnter={(e) => e.currentTarget.style.color = "var(--gilt-deep)"}
                    onMouseLeave={(e) => e.currentTarget.style.color = "inherit"}
                  >{s.honorific} {s.name}</button>
                  {i < Math.min(saints.length, 4) - 1 ? ", " : ""}
                </React.Fragment>
              ))}
              {saints.length > 4 && <span> + {saints.length - 4}</span>}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

// ===== CALENDARS (cross-tradition table) =====
const CalendarsTab = ({ onOpenSaint }) => {
  const traditions = ["Roman", "Anglican", "Lutheran", "Eastern Orthodox", "Coptic", "Maronite"];
  return (
    <div className="container">
      <div className="t-rubric mb-2">Cross-tradition</div>
      <h2 className="t-display mb-2" style={{ fontSize: 32 }}>Calendars compared</h2>
      <p style={{ color: "var(--ink-soft)", fontSize: 15, marginBottom: 24, maxWidth: 640 }}>
        Which apostolic Churches keep the memory of which saint. A green mark indicates the saint appears in that calendar; absence does not always mean rejection — some saints simply pre-date or post-date a particular tradition's reception.
      </p>

      <div style={{ overflowX: "auto" }}>
        <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
          <thead>
            <tr>
              <th style={{ textAlign: "left", padding: "10px 12px", borderBottom: "1px solid var(--hairline)", fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--ink-mute)" }}>Saint</th>
              <th style={{ textAlign: "left", padding: "10px 12px", borderBottom: "1px solid var(--hairline)", fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--ink-mute)" }}>Feast</th>
              {traditions.map(t => (
                <th key={t} style={{ textAlign: "center", padding: "10px 8px", borderBottom: "1px solid var(--hairline)", fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--ink-mute)", whiteSpace: "nowrap" }}>{t}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {window.SAINTS.map(s => (
              <tr key={s.id} style={{ borderBottom: "1px solid var(--hairline-soft)", cursor: "pointer" }}
                onClick={() => onOpenSaint(s.id)}
                onMouseEnter={(e) => e.currentTarget.style.background = "var(--highlight)"}
                onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}
              >
                <td style={{ padding: "10px 12px", fontFamily: "var(--font-display)", fontSize: 16 }}>{s.honorific} {s.name}</td>
                <td style={{ padding: "10px 12px", color: "var(--ink-mute)" }}>{formatDate(s.feast.month, s.feast.day)}</td>
                {traditions.map(t => (
                  <td key={t} style={{ textAlign: "center", padding: "10px 8px" }}>
                    {s.tradition.includes(t)
                      ? <span style={{ color: "var(--gilt-deep)", fontSize: 18 }}>✦</span>
                      : <span style={{ color: "var(--hairline)" }}>·</span>}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
};

// ===== CITATIONS (export tools) =====
const CitationsTab = ({ onOpenSaint }) => {
  const [selected, setSelected] = React.useState(["augustine-hippo", "francis-assisi"]);
  const [style, setStyle] = React.useState("Chicago");

  const toggle = (id) => setSelected(s => s.includes(id) ? s.filter(x => x !== id) : [...s, id]);

  const formatCitation = (saint, style) => {
    const dates = `${yearLabel(saint.dates.born)}–${yearLabel(saint.dates.died)}`;
    if (style === "Chicago") {
      return `"${saint.honorific} ${saint.name}" (${dates}). Saints Calendar. Accessed May 1, 2026. https://saintscalendar.org/saint/${saint.id}.`;
    } else if (style === "MLA") {
      return `"${saint.honorific} ${saint.name}." Saints Calendar, ${dates}, saintscalendar.org/saint/${saint.id}. Accessed 1 May 2026.`;
    } else if (style === "APA") {
      return `Saints Calendar. (2026). ${saint.honorific} ${saint.name} (${dates}). Retrieved May 1, 2026, from https://saintscalendar.org/saint/${saint.id}`;
    } else if (style === "BibTeX") {
      return `@misc{${saint.id},\n  title  = {${saint.honorific} ${saint.name} (${dates})},\n  author = {{Saints Calendar}},\n  year   = {2026},\n  url    = {https://saintscalendar.org/saint/${saint.id}}\n}`;
    }
  };

  const exportText = selected.map(id => {
    const s = window.getSaintById(id);
    return [
      formatCitation(s, style),
      "",
      "Sources cited:",
      ...s.sources.map((src, i) => `  [${i+1}] ${src}`),
      ""
    ].join("\n");
  }).join("\n");

  const copyToClipboard = () => {
    navigator.clipboard.writeText(exportText);
  };

  return (
    <div className="container">
      <div className="t-rubric mb-2">Bibliography</div>
      <h2 className="t-display mb-4" style={{ fontSize: 32 }}>Citation export</h2>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1.4fr", gap: 32 }}>
        <div>
          <div className="t-eyebrow mb-2">Select saints</div>
          <div style={{ maxHeight: 400, overflowY: "auto", border: "1px solid var(--hairline-soft)", padding: "8px 14px" }}>
            {window.SAINTS.map(s => (
              <label key={s.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "6px 0", cursor: "pointer", borderBottom: "1px solid var(--hairline-soft)" }}>
                <input type="checkbox" checked={selected.includes(s.id)} onChange={() => toggle(s.id)} />
                <span style={{ fontFamily: "var(--font-display)", fontSize: 15 }}>{s.honorific} {s.name}</span>
                <span className="t-meta" style={{ marginLeft: "auto" }}>{yearLabel(s.dates.born)}</span>
              </label>
            ))}
          </div>
          <div className="t-meta mt-2">{selected.length} selected</div>
        </div>

        <div>
          <div className="t-eyebrow mb-2">Style</div>
          <div className="flex gap-2 mb-3">
            {["Chicago", "MLA", "APA", "BibTeX"].map(s => (
              <button key={s} className={`chip ${style === s ? "active" : ""}`} onClick={() => setStyle(s)}>{s}</button>
            ))}
          </div>

          <pre style={{
            background: "color-mix(in oklab, var(--parchment) 88%, var(--parchment-deep))",
            border: "1px solid var(--hairline-soft)",
            padding: 18, fontSize: 12, fontFamily: "var(--font-mono)",
            whiteSpace: "pre-wrap", lineHeight: 1.6,
            maxHeight: 360, overflowY: "auto",
            margin: 0,
          }}>{exportText || "(select one or more saints)"}</pre>

          <div className="flex gap-2 mt-3">
            <button className="btn btn-primary btn-sm" onClick={copyToClipboard}>Copy to clipboard</button>
            <button className="btn btn-sm" onClick={() => {
              const blob = new Blob([exportText], { type: "text/plain" });
              const url = URL.createObjectURL(blob);
              const a = document.createElement("a"); a.href = url; a.download = `saints-bibliography-${style}.txt`; a.click();
            }}>Download .txt</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ===== PATRONAGE TAB =====
const PatronageTab = ({ onOpenSaint }) => {
  const [q, setQ] = useStateW("");
  const map = {};
  window.SAINTS.forEach(s => s.patronage.forEach(p => {
    if (!map[p]) map[p] = [];
    map[p].push(s);
  }));
  const groups = {
    "Vocations & Work": ["Theologians", "Workers", "Soldiers", "Educators", "Students", "Booksellers", "Printers", "Brewers", "Florists", "Merchants", "Engineers", "Pharmacists", "Lacemakers", "Embroiderers", "Goldsmiths", "Mothers", "Wives", "Fathers", "Midwives", "Travellers", "Aviators", "Radio operators", "Pastry chefs", "Hatters", "Spiritual writers", "Chimney sweeps", "Laundry workers", "Dairymaids", "Universities", "Catholic schools", "Spiritual exercises", "Mystics", "Contemplatives", "Spanish poets", "Hermits", "Healings"],
    "Places & Peoples": ["Italy", "Ireland", "France", "Spain", "Poland", "Lebanon", "Belgium", "Canada", "Mexico", "Russia", "Cyprus", "Uruguay", "Nigeria", "Austria", "The Basque Country", "City of Hippo", "Kraków", "Universal Church", "Coptic Christians", "Order of the White Eagle"],
    "Causes & Conditions": ["Sore eyes", "Eye disease", "Headache sufferers", "Cattle", "Newborns", "Television", "Lost things", "The poor", "Captives", "Martyrs", "Abuse victims", "Rape victims", "Mental illness", "Difficult marriages", "Disappointing children", "Pregnant women", "Animals", "Ecology", "Stowaways", "AIDS sufferers", "Missions", "Speleologists", "Against poisoning", "Against snakes", "A happy death", "Poets"],
  };
  const filter = q.toLowerCase();
  return (
    <div className="container">
      <div className="t-rubric mb-2">Browse</div>
      <h2 className="t-display mb-2" style={{ fontSize: 32 }}>Patronage</h2>
      <p style={{ fontSize: 15, color: "var(--ink-soft)", marginBottom: 18, maxWidth: 640 }}>
        Vocations, places, and causes that the saints have been invoked to protect.
      </p>
      <input value={q} onChange={e => setQ(e.target.value)} placeholder="Filter patronages…"
        style={{ width: 320, padding: "8px 12px", border: "1px solid var(--hairline)", background: "var(--parchment)", marginBottom: 28, fontFamily: "var(--font-mono)", fontSize: 12 }} />
      {Object.entries(groups).map(([groupName, items]) => {
        const present = items.filter(i => map[i] && (!filter || i.toLowerCase().includes(filter)));
        if (!present.length) return null;
        return (
          <div key={groupName} style={{ padding: "20px 0", borderTop: "1px solid var(--hairline-soft)" }}>
            <div className="t-rubric mb-3" style={{ color: "var(--gilt-deep)" }}>{groupName}</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))", gap: 20 }}>
              {present.map(p => (
                <div key={p}>
                  <div style={{ fontFamily: "var(--font-display)", fontSize: 18, marginBottom: 4, fontStyle: "italic" }}>{p}</div>
                  <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
                    {map[p].map(s => (
                      <button key={s.id} onClick={() => onOpenSaint(s.id)}
                        style={{ textAlign: "left", color: "var(--ink-soft)", fontSize: 14, padding: "2px 0" }}
                        onMouseEnter={(e) => e.currentTarget.style.color = "var(--gilt-deep)"}
                        onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-soft)"}
                      >{s.honorific} {s.name}</button>
                    ))}
                  </div>
                </div>
              ))}
            </div>
          </div>
        );
      })}
    </div>
  );
};

// ===== ORDERS TAB =====
const OrdersTab = ({ onOpenSaint }) => {
  const map = {};
  window.SAINTS.forEach(s => {
    const o = s.order || "Diocesan / Lay / No order";
    if (!map[o]) map[o] = [];
    map[o].push(s);
  });
  const orderDescriptions = {
    "Franciscan (O.F.M.)": "Founded 1209 by Francis of Assisi. The Friars Minor live a life of poverty and itinerant preaching.",
    "Dominicans (O.P.)": "Founded 1216 by Dominic Guzmán. The Order of Preachers, devoted to study and the salvation of souls.",
    "Benedictines (O.S.B.)": "Founded c. 530 by Benedict of Nursia. The first stable rule of Western monasticism — ora et labora.",
    "Discalced Carmelites (O.C.D.)": "Reformed branch of the Carmelite order, established by Teresa of Ávila and John of the Cross in 1568.",
    "Society of Jesus (S.J.)": "Founded 1540 by Ignatius of Loyola. Soldiers of Christ, devoted to mission, education, and obedience to the pope.",
    "Poor Clares (O.S.C.)": "Founded 1212 by Clare of Assisi. The female Franciscan order, fully enclosed and contemplative.",
    "Lebanese Maronite Order (O.L.M.)": "Founded 1695 in Lebanon. The principal Maronite religious order, contemplative and active.",
  };
  return (
    <div className="container">
      <div className="t-rubric mb-2">Browse</div>
      <h2 className="t-display mb-4" style={{ fontSize: 32 }}>Religious orders</h2>
      {Object.entries(map).map(([order, saints]) => (
        <div key={order} style={{ padding: "32px 0", borderTop: "1px solid var(--hairline-soft)" }}>
          <div style={{ display: "grid", gridTemplateColumns: "300px 1fr", gap: 40, alignItems: "start" }}>
            <div>
              <div className="t-rubric mb-2" style={{ color: "var(--gilt-deep)" }}>{saints.length} {saints.length === 1 ? "Saint" : "Saints"}</div>
              <h3 className="t-display" style={{ fontSize: 26, marginBottom: 10 }}>{order}</h3>
              {orderDescriptions[order] && (
                <p style={{ fontSize: 14, color: "var(--ink-soft)", lineHeight: 1.6, fontStyle: "italic" }}>
                  {orderDescriptions[order]}
                </p>
              )}
            </div>
            <div>
              {saints.map(s => <SaintListItem key={s.id} saint={s} onOpen={onOpenSaint} />)}
            </div>
          </div>
        </div>
      ))}
    </div>
  );
};

Object.assign(window, { ResearchView, PatronageTab, OrdersTab });
