/* Workspace panels — study overview, sample map, files, readiness, joined table, methods, report */

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

/* ========== sidebar + frame ========== */

const PANELS = [
  { key: "overview",  label: "Study overview",      icon: "study"   },
  { key: "subjects",  label: "Subject / sample map", icon: "subjects"},
  { key: "files",     label: "Assay files",         icon: "files"   },
  { key: "readiness", label: "Metadata readiness",  icon: "ready"   },
  { key: "endpoints", label: "Joined endpoint table", icon: "table" },
  { key: "methods",   label: "Methods + provenance", icon: "doc"    },
  { key: "report",    label: "Report preview",      icon: "report"  },
];

function Sidebar({ active, setActive, scores }) {
  return (
    <aside style={{
      width: 264, flexShrink: 0,
      borderRight: "1px solid var(--ink-200)",
      background: "var(--ink-50)",
      padding: "20px 16px",
      display: "flex", flexDirection: "column", gap: 18,
      position: "sticky", top: 61, alignSelf: "flex-start",
      height: "calc(100vh - 61px)", overflowY: "auto",
    }}>
      <div>
        <div className="mono" style={{ fontSize: 10.5, color: "var(--ink-500)", letterSpacing: ".1em", textTransform: "uppercase", fontWeight: 600 }}>
          Active study
        </div>
        <div style={{ marginTop: 8, padding: "10px 12px", background: "white", border: "1px solid var(--ink-200)", borderRadius: 8 }}>
          <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink-900)", lineHeight: 1.3 }}>
            {DEMO_STUDY.shortTitle}
          </div>
          <div className="mono" style={{ marginTop: 4, fontSize: 10.5, color: "var(--ink-500)" }}>{DEMO_STUDY.id} · {DEMO_STUDY.species.split(" ")[0]} {DEMO_STUDY.species.split(" ")[1]}</div>
          <div style={{ marginTop: 8, height: 4, background: "var(--ink-100)", borderRadius: 99, overflow: "hidden" }}>
            <div style={{ width: `${DEMO_STUDY.readinessScore}%`, height: "100%", background: "var(--brand-500)" }} />
          </div>
          <div style={{ marginTop: 6, display: "flex", justifyContent: "space-between", fontSize: 11, color: "var(--ink-500)" }}>
            <span>Readiness</span>
            <span className="mono" style={{ color: "var(--brand-700)", fontWeight: 600 }}>{DEMO_STUDY.readinessScore} / 100</span>
          </div>
        </div>
      </div>

      <div>
        <div className="mono" style={{ fontSize: 10.5, color: "var(--ink-500)", letterSpacing: ".1em", textTransform: "uppercase", fontWeight: 600, marginBottom: 8 }}>
          Workspace
        </div>
        <nav style={{ display: "grid", gap: 2 }}>
          {PANELS.map(p => (
            <button key={p.key}
              onClick={() => setActive(p.key)}
              className="sidebar-btn"
              data-active={active === p.key}
              style={{
                display: "flex", alignItems: "center", gap: 10,
                padding: "8px 10px", borderRadius: 6,
                background: active === p.key ? "white" : "transparent",
                border: active === p.key ? "1px solid var(--ink-200)" : "1px solid transparent",
                color: active === p.key ? "var(--ink-900)" : "var(--ink-700)",
                fontSize: 13, fontWeight: active === p.key ? 600 : 500,
                cursor: "pointer", textAlign: "left", fontFamily: "inherit",
                boxShadow: active === p.key ? "var(--shadow-sm)" : "none",
              }}>
              <SidebarIcon kind={p.icon} active={active === p.key} />
              <span style={{ flex: 1 }}>{p.label}</span>
              {scores[p.key] != null && (
                <span className="mono" style={{ fontSize: 10.5, color: "var(--ink-400)" }}>{scores[p.key]}</span>
              )}
            </button>
          ))}
        </nav>
      </div>

      <div style={{ marginTop: "auto", padding: 12, borderRadius: 8, background: "white", border: "1px solid var(--ink-200)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 8 }}>
          <span className="dot" style={{ color: "var(--ok)" }}></span>
          <span className="mono" style={{ fontSize: 10, color: "var(--ok)", letterSpacing: ".06em", fontWeight: 600 }}>CONNECTED</span>
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-700)", lineHeight: 1.5 }}>
          ConductVision and ConductSignal artifacts attach to studies automatically.
        </div>
      </div>
    </aside>
  );
}

function SidebarIcon({ kind, active }) {
  const c = active ? "var(--brand-700)" : "var(--ink-500)";
  const props = { width: 15, height: 15, fill: "none", stroke: c, strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round" };
  switch (kind) {
    case "study":    return <svg {...props} viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18"/></svg>;
    case "subjects": return <svg {...props} viewBox="0 0 24 24"><circle cx="9" cy="8" r="3"/><path d="M3 21v-1a6 6 0 016-6 6 6 0 016 6v1"/><circle cx="17" cy="6" r="2"/><path d="M21 14a4 4 0 00-7-2.6"/></svg>;
    case "files":    return <svg {...props} viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/><path d="M9 13h6M9 17h4"/></svg>;
    case "ready":    return <svg {...props} viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><path d="M22 4L12 14.01l-3-3"/></svg>;
    case "table":    return <svg {...props} viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/></svg>;
    case "doc":      return <svg {...props} viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8M8 17h6"/></svg>;
    case "report":   return <svg {...props} viewBox="0 0 24 24"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 7h8M8 11h8M8 15h5"/></svg>;
  }
  return null;
}

function PanelHeader({ title, subtitle, eyebrow, actions }) {
  return (
    <div style={{ padding: "20px 28px 0", display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 16 }}>
      <div>
        {eyebrow && <div className="mono" style={{ fontSize: 11, color: "var(--brand-700)", letterSpacing: ".1em", textTransform: "uppercase", fontWeight: 600 }}>{eyebrow}</div>}
        <h1 style={{ margin: "6px 0 0", fontSize: 24, fontWeight: 600, letterSpacing: "-0.015em", color: "var(--ink-900)" }}>{title}</h1>
        {subtitle && <p style={{ margin: "6px 0 0", fontSize: 14, color: "var(--ink-600)", lineHeight: 1.5, maxWidth: 720 }}>{subtitle}</p>}
      </div>
      {actions && <div style={{ display: "flex", gap: 8, alignItems: "center", flexShrink: 0 }}>{actions}</div>}
    </div>
  );
}

/* ========== STUDY OVERVIEW ========== */

function StudyOverviewPanel() {
  const counts = useMemo(() => {
    const c = {};
    DEMO_STUDY.groups.forEach(g => c[g] = DEMO_SUBJECTS.filter(s => s.group === g).length);
    return c;
  }, []);
  const assayCounts = {
    qPCR: DEMO_SAMPLES.filter(s => s.assayType === "qPCR").length,
    ELISA: DEMO_SAMPLES.filter(s => s.assayType === "ELISA").length,
    behavior: DEMO_SAMPLES.filter(s => s.assayType === "behavior").length,
  };

  return (
    <div>
      <PanelHeader
        eyebrow="Study"
        title={DEMO_STUDY.title}
        subtitle={`Analysis-ready · 2 outstanding warnings. ${DEMO_STUDY.pi} · started ${DEMO_STUDY.startDate}.`}
        actions={<>
          <button className="btn btn-ghost btn-sm">Edit study</button>
          <button className="btn btn-primary btn-sm">Generate report <ArrowR /></button>
        </>}
      />

      <div style={{ padding: "24px 28px 0", display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 20 }}>
        <div className="panel">
          <div className="panel-head">
            <h3>Cohort</h3>
            <span className="mono" style={{ fontSize: 11, color: "var(--ink-500)" }}>{DEMO_STUDY.cohortSize} subjects</span>
          </div>
          <div style={{ padding: 20, display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
            {DEMO_STUDY.groups.map((g, i) => {
              const variant = g === "Sham" ? "ink" : g === "TBI" ? "block" : "bench";
              const styles = {
                ink:   { bg: "var(--ink-100)", color: "var(--ink-700)", bar: "var(--ink-400)" },
                block: { bg: "var(--block-bg)", color: "var(--block)", bar: "var(--block)" },
                bench: { bg: "var(--brand-50)", color: "var(--brand-700)", bar: "var(--brand-500)" },
              }[variant];
              return (
                <div key={g} style={{ border: "1px solid var(--ink-200)", borderRadius: 10, padding: 16 }}>
                  <div style={{ display: "inline-flex", padding: "2px 8px", borderRadius: 4, background: styles.bg, color: styles.color, fontSize: 11.5, fontWeight: 500 }}>{g}</div>
                  <div style={{ marginTop: 12, display: "flex", alignItems: "baseline", gap: 4 }}>
                    <div className="mono" style={{ fontSize: 30, fontWeight: 600, color: "var(--ink-900)", letterSpacing: "-0.02em" }}>{counts[g]}</div>
                    <div style={{ fontSize: 13, color: "var(--ink-500)" }}>mice</div>
                  </div>
                  <div className="mono" style={{ marginTop: 8, fontSize: 11, color: "var(--ink-500)" }}>
                    {DEMO_SUBJECTS.filter(s => s.group === g && s.sex === "F").length}F · {DEMO_SUBJECTS.filter(s => s.group === g && s.sex === "M").length}M
                    {DEMO_SUBJECTS.filter(s => s.group === g && !s.sex).length > 0 && (
                      <span style={{ color: "var(--warn)" }}> · {DEMO_SUBJECTS.filter(s => s.group === g && !s.sex).length} missing</span>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
          <div style={{ padding: "0 20px 20px" }}>
            <div className="mono" style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600, marginBottom: 8 }}>
              Timepoints
            </div>
            <div style={{ display: "flex", gap: 8 }}>
              {TIMEPOINTS.map(tp => (
                <div key={tp} style={{ flex: 1, padding: "10px 12px", border: "1px solid var(--ink-200)", borderRadius: 8 }}>
                  <div className="mono" style={{ fontSize: 12.5, color: "var(--ink-800)", fontWeight: 500 }}>{tp}</div>
                  <div style={{ fontSize: 11, color: "var(--ink-500)", marginTop: 2 }}>
                    {DEMO_SAMPLES.filter(s => s.timepoint === tp).length} samples
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>

        <div className="panel">
          <div className="panel-head">
            <h3>Assays</h3>
            <span className="mono" style={{ fontSize: 11, color: "var(--ink-500)" }}>{DEMO_SAMPLES.length} samples</span>
          </div>
          <div style={{ padding: "8px 0" }}>
            {[
              { name: "qPCR",     count: assayCounts.qPCR,     src: "bench", file: "qpcr_run_0524.rdml", desc: "IL6, TNFα · GAPDH ref · 4 plates" },
              { name: "ELISA",    count: assayCounts.ELISA,    src: "bench", file: "il6_tnfa_elisa_plate.csv", desc: "IL-6 + TNFα · pg/mL" },
              { name: "Behavior", count: assayCounts.behavior, src: "vision", file: "open_field_summary.csv", desc: "Open field · distance, freezing · native attach" },
            ].map((a, i) => (
              <div key={a.name} style={{
                display: "flex", alignItems: "center", gap: 14, padding: "12px 20px",
                borderTop: i === 0 ? "none" : "1px solid var(--ink-100)",
              }}>
                <span className={`chip chip-${a.src}`} style={{ minWidth: 70, justifyContent: "center" }}>
                  {a.src}
                </span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 500, color: "var(--ink-900)" }}>{a.name}</div>
                  <div className="mono" style={{ fontSize: 11, color: "var(--ink-500)" }}>{a.file} · {a.desc}</div>
                </div>
                <div className="mono" style={{ fontSize: 14, color: "var(--ink-800)", fontWeight: 500 }}>{a.count}</div>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div style={{ padding: "20px 28px 28px", display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 20 }}>
        <ReadinessGauge />
        <RecentActivity />
        <NextSteps />
      </div>
    </div>
  );
}

function ReadinessGauge() {
  const checks = DEMO_READINESS;
  const total = checks.length - 1; // exclude '0 blocking issues' meta-check
  const complete = checks.filter(c => c.severity === "complete").length;
  return (
    <div className="panel" style={{ padding: 20 }}>
      <div style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600 }}>
        Metadata readiness
      </div>
      <div style={{ marginTop: 6, display: "flex", alignItems: "baseline", gap: 6 }}>
        <div className="mono" style={{ fontSize: 44, fontWeight: 600, color: "var(--brand-700)", letterSpacing: "-0.02em" }}>{DEMO_STUDY.readinessScore}</div>
        <div style={{ fontSize: 14, color: "var(--ink-500)" }}>/ 100</div>
      </div>
      <div style={{ height: 6, background: "var(--ink-100)", borderRadius: 99, marginTop: 6, overflow: "hidden" }}>
        <div style={{ width: `${DEMO_STUDY.readinessScore}%`, height: "100%", background: "var(--brand-500)" }} />
      </div>
      <div style={{ marginTop: 14, display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8, fontSize: 12 }}>
        <div><div style={{ color: "var(--ok)", fontWeight: 600, fontSize: 16 }} className="mono">{complete}</div><div style={{ color: "var(--ink-500)" }}>complete</div></div>
        <div><div style={{ color: "var(--warn)", fontWeight: 600, fontSize: 16 }} className="mono">{checks.filter(c => c.severity === "warning").length}</div><div style={{ color: "var(--ink-500)" }}>warnings</div></div>
        <div><div style={{ color: "var(--block)", fontWeight: 600, fontSize: 16 }} className="mono">0</div><div style={{ color: "var(--ink-500)" }}>blocking</div></div>
      </div>
    </div>
  );
}

function RecentActivity() {
  const items = [
    ["09:42", "Joined endpoint table refreshed",     "system"],
    ["09:40", "open_field_summary.csv attached",     "vision"],
    ["09:38", "il6_tnfa_elisa_plate.csv parsed (192 rows)", "bench"],
    ["09:36", "qpcr_run_0524.rdml parsed (384 wells)","bench"],
    ["09:30", "Study created from sample_map.csv",   "bench"],
  ];
  return (
    <div className="panel" style={{ padding: 20 }}>
      <div style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600, marginBottom: 12 }}>
        Activity
      </div>
      <ol style={{ listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 10 }}>
        {items.map(([t, msg, src], i) => (
          <li key={i} style={{ display: "flex", gap: 10, alignItems: "flex-start", fontSize: 12.5 }}>
            <span className="mono" style={{ color: "var(--ink-400)", flexShrink: 0, fontSize: 11 }}>{t}</span>
            <span style={{ color: "var(--ink-800)", lineHeight: 1.45 }}>{msg}</span>
          </li>
        ))}
      </ol>
    </div>
  );
}

function NextSteps() {
  const steps = [
    { sev: "warning",  text: "Add timepoint to 2 qPCR samples",   target: "subjects" },
    { sev: "warning",  text: "Record sex for subject M19",         target: "subjects" },
    { sev: "complete", text: "ConductVision behavior attached on subject ID", target: "files" },
    { sev: "complete", text: "Joined endpoint table ready to export", target: "endpoints" },
  ];
  return (
    <div className="panel" style={{ padding: 20 }}>
      <div style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600, marginBottom: 12 }}>
        Next steps
      </div>
      <ul style={{ listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 10 }}>
        {steps.map((s, i) => (
          <li key={i} style={{ display: "flex", gap: 10, alignItems: "flex-start", fontSize: 13 }}>
            <SeverityDot sev={s.sev} />
            <span style={{ color: "var(--ink-800)", lineHeight: 1.45 }}>{s.text}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

function SeverityDot({ sev }) {
  const c = sev === "complete" ? "var(--ok)" : sev === "warning" ? "var(--warn)" : "var(--block)";
  return <span style={{ width: 8, height: 8, borderRadius: 99, background: c, flexShrink: 0, marginTop: 5 }} />;
}

/* ========== SUBJECTS / SAMPLE MAP ========== */

function SubjectsPanel() {
  const [groupFilter, setGroupFilter] = useState("All");
  const [showMissingOnly, setShowMissingOnly] = useState(false);
  const [openId, setOpenId] = useState(null);

  const samplesBySubject = useMemo(() => {
    const m = {};
    DEMO_SAMPLES.forEach(s => { (m[s.subjectId] ||= []).push(s); });
    return m;
  }, []);

  const rows = useMemo(() => {
    let r = DEMO_SUBJECTS;
    if (groupFilter !== "All") r = r.filter(s => s.group === groupFilter);
    if (showMissingOnly) {
      r = r.filter(s => {
        if (!s.sex) return true;
        const samples = samplesBySubject[s.subjectId] || [];
        return samples.some(x => !x.timepoint);
      });
    }
    return r;
  }, [groupFilter, showMissingOnly, samplesBySubject]);

  return (
    <div>
      <PanelHeader
        eyebrow="Sample roster"
        title="Subject / sample map"
        subtitle="36 subjects × 7 samples each = 252 rows in sample_map.csv. Click a row to see linked source files."
        actions={
          <>
            <select value={groupFilter} onChange={e => setGroupFilter(e.target.value)} className="select">
              <option>All</option>
              {DEMO_STUDY.groups.map(g => <option key={g}>{g}</option>)}
            </select>
            <button className="btn btn-ghost btn-sm" onClick={() => setShowMissingOnly(v => !v)} data-on={showMissingOnly}
                    style={{ background: showMissingOnly ? "var(--warn-bg)" : undefined, borderColor: showMissingOnly ? "#fde68a" : undefined, color: showMissingOnly ? "var(--warn)" : undefined }}>
              {showMissingOnly ? "✓ " : ""}Missing fields
            </button>
          </>
        }
      />

      <div style={{ padding: "24px 28px 28px" }}>
        <div className="panel" style={{ overflow: "hidden" }}>
          <div style={{ overflowX: "auto" }}>
            <table className="data-table">
              <thead>
                <tr>
                  <th>Subject ID</th>
                  <th>Group</th>
                  <th>Sex</th>
                  <th>Genotype</th>
                  <th>Samples</th>
                  <th>Timepoints</th>
                  <th>Source files</th>
                  <th>Status</th>
                </tr>
              </thead>
              <tbody>
                {rows.map(s => {
                  const samples = samplesBySubject[s.subjectId] || [];
                  const missingTp = samples.filter(x => !x.timepoint).length;
                  const status = !s.sex || missingTp > 0 ? "warning" : "ok";
                  const files = [...new Set(samples.map(x => x.sourceFile))];
                  const open = openId === s.subjectId;
                  return (
                    <React.Fragment key={s.subjectId}>
                      <tr onClick={() => setOpenId(open ? null : s.subjectId)} style={{ cursor: "pointer", background: open ? "var(--ink-50)" : undefined }}>
                        <td className="mono" style={{ color: "var(--ink-900)", fontWeight: 500 }}>{s.subjectId}</td>
                        <td><GroupChip group={s.group} /></td>
                        <td className="mono">
                          {s.sex ? s.sex : <span className="chip chip-warn" style={{ padding: "1px 6px", fontSize: 10.5 }}>missing</span>}
                        </td>
                        <td className="mono">{s.genotype}</td>
                        <td className="mono">{samples.length}</td>
                        <td className="mono">
                          {TIMEPOINTS.map(tp => {
                            const present = samples.some(x => x.timepoint === tp);
                            return <span key={tp} style={{ display: "inline-block", padding: "1px 5px", marginRight: 3, borderRadius: 3, fontSize: 10.5,
                                                            background: present ? "var(--brand-50)" : "var(--ink-100)",
                                                            color: present ? "var(--brand-700)" : "var(--ink-400)" }}>{tp}</span>;
                          })}
                        </td>
                        <td>
                          <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
                            {files.slice(0, 2).map(f => (
                              <span key={f} className="chip" style={{ padding: "1px 6px", fontSize: 10, fontFamily: "var(--font-mono)" }}>{f}</span>
                            ))}
                            {files.length > 2 && <span className="chip" style={{ padding: "1px 6px", fontSize: 10 }}>+{files.length - 2}</span>}
                          </div>
                        </td>
                        <td>
                          {status === "warning"
                            ? <span className="chip chip-warn"><span className="dot"></span>warn</span>
                            : <span className="chip chip-ok"><span className="dot"></span>ok</span>}
                        </td>
                      </tr>
                      {open && (
                        <tr>
                          <td colSpan={8} style={{ background: "var(--ink-50)", padding: 0 }}>
                            <SubjectDetail subject={s} samples={samples} />
                          </td>
                        </tr>
                      )}
                    </React.Fragment>
                  );
                })}
              </tbody>
            </table>
          </div>
          <div style={{ padding: "10px 16px", background: "var(--ink-50)", borderTop: "1px solid var(--ink-200)",
                        display: "flex", justifyContent: "space-between", fontSize: 12, color: "var(--ink-500)" }} className="mono">
            <span>{rows.length} subjects · {rows.reduce((acc, s) => acc + (samplesBySubject[s.subjectId]?.length || 0), 0)} samples</span>
            <span>source · sample_map.csv (252 rows)</span>
          </div>
        </div>
      </div>
    </div>
  );
}

function SubjectDetail({ subject, samples }) {
  return (
    <div style={{ padding: "14px 24px 18px", borderTop: "1px dashed var(--ink-300)" }}>
      <div style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600 }}>
        Linked samples · {subject.subjectId}
      </div>
      <div style={{ marginTop: 10, display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: 8 }}>
        {samples.map(s => (
          <div key={s.sampleId} style={{ background: "white", border: "1px solid var(--ink-200)", borderRadius: 6, padding: 10 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              <span className="mono" style={{ fontSize: 11.5, color: "var(--ink-900)", fontWeight: 500 }}>{s.sampleId}</span>
              <span className={`chip ${s.assayType === "behavior" ? "chip-vision" : "chip-bench"}`} style={{ fontSize: 10, padding: "1px 6px" }}>
                {s.assayType}
              </span>
            </div>
            <div className="mono" style={{ marginTop: 4, fontSize: 10.5, color: "var(--ink-500)" }}>
              {s.timepoint || <span style={{ color: "var(--warn)" }}>missing tp</span>} · {s.sourceFile}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ========== FILES ========== */

function FilesPanel() {
  const [openFile, setOpenFile] = useState(DEMO_FILES[0].name);
  const file = DEMO_FILES.find(f => f.name === openFile);
  return (
    <div>
      <PanelHeader
        eyebrow="Source artifacts"
        title="Assay files"
        subtitle="Every file is parsed, indexed, and joined on subject_id × timepoint. Click a card to inspect."
        actions={
          <button className="btn btn-primary btn-sm">
            + Attach file
          </button>
        }
      />
      <div style={{ padding: "24px 28px 28px", display: "grid", gridTemplateColumns: "1fr 1.4fr", gap: 20 }}>
        <div style={{ display: "grid", gap: 10 }}>
          {DEMO_FILES.map(f => {
            const sel = openFile === f.name;
            return (
              <button key={f.name} onClick={() => setOpenFile(f.name)} className="file-card"
                style={{
                  textAlign: "left", padding: 14, background: "white",
                  border: sel ? "1px solid var(--brand-500)" : "1px solid var(--ink-200)",
                  borderRadius: 10, cursor: "pointer", fontFamily: "inherit",
                  boxShadow: sel ? "0 0 0 3px var(--brand-50)" : "var(--shadow-sm)",
                }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <span className={`chip chip-${f.source}`}>{f.source}</span>
                  {f.warnings > 0 && <span className="chip chip-warn">{f.warnings} warn</span>}
                </div>
                <div className="mono" style={{ marginTop: 10, fontSize: 13, color: "var(--ink-900)", fontWeight: 500 }}>{f.name}</div>
                <div style={{ marginTop: 3, fontSize: 12, color: "var(--ink-500)" }}>{f.kind}</div>
                <div style={{ marginTop: 12, display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8, fontSize: 11, color: "var(--ink-500)" }}>
                  <div><div className="mono" style={{ fontSize: 14, color: "var(--ink-800)", fontWeight: 500 }}>{f.rowsParsed}</div>rows</div>
                  <div><div className="mono" style={{ fontSize: 14, color: "var(--ink-800)", fontWeight: 500 }}>{f.mappedSamples}</div>mapped</div>
                  <div><div className="mono" style={{ fontSize: 14, color: "var(--ink-800)", fontWeight: 500 }}>{f.linkedSubjects}</div>subjects</div>
                </div>
              </button>
            );
          })}
        </div>

        <div className="panel">
          <div className="panel-head">
            <h3>{file.name}</h3>
            <div style={{ display: "flex", gap: 8 }}>
              <span className={`chip chip-${file.source}`}>{file.source}</span>
              <span className="chip">{file.kind}</span>
            </div>
          </div>
          <div style={{ padding: 20 }}>
            <p style={{ margin: 0, fontSize: 13.5, color: "var(--ink-700)", lineHeight: 1.55 }}>{file.detail}</p>

            <dl style={{ marginTop: 20, display: "grid", gridTemplateColumns: "max-content 1fr", gap: "8px 16px", fontSize: 12.5 }}>
              <dt style={{ color: "var(--ink-500)" }}>Uploaded</dt><dd className="mono" style={{ margin: 0, color: "var(--ink-800)" }}>{file.uploaded}</dd>
              <dt style={{ color: "var(--ink-500)" }}>Rows parsed</dt><dd className="mono" style={{ margin: 0, color: "var(--ink-800)" }}>{file.rowsParsed}</dd>
              <dt style={{ color: "var(--ink-500)" }}>Mapped samples</dt><dd className="mono" style={{ margin: 0, color: "var(--ink-800)" }}>{file.mappedSamples}</dd>
              <dt style={{ color: "var(--ink-500)" }}>Linked subjects</dt><dd className="mono" style={{ margin: 0, color: "var(--ink-800)" }}>{file.linkedSubjects}</dd>
              <dt style={{ color: "var(--ink-500)" }}>Warnings</dt><dd className="mono" style={{ margin: 0, color: file.warnings ? "var(--warn)" : "var(--ok)" }}>{file.warnings}</dd>
              <dt style={{ color: "var(--ink-500)" }}>SHA-256</dt><dd className="mono" style={{ margin: 0, color: "var(--ink-700)", fontSize: 11 }}>7a2c…3e91</dd>
            </dl>

            <div style={{ marginTop: 22 }}>
              <div className="mono" style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600, marginBottom: 8 }}>
                Preview (first 6 rows)
              </div>
              <FilePreview file={file} />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function FilePreview({ file }) {
  let rows = [], cols = [];
  if (file.name.startsWith("qpcr")) {
    cols = ["well", "sample_id", "target", "Ct", "Ct_mean", "qcFlag"];
    rows = [
      ["A1", "S-M01-BASELINE", "Il6",   "23.42", "23.39", "ok"],
      ["A2", "S-M01-BASELINE", "Il6",   "23.36", "23.39", "ok"],
      ["A3", "S-M01-BASELINE", "Tnf",   "24.18", "24.20", "ok"],
      ["A4", "S-M01-BASELINE", "Gapdh", "18.92", "18.94", "ref"],
      ["A5", "S-M02-BASELINE", "Il6",   "22.81", "22.79", "warn"],
      ["A6", "S-M02-BASELINE", "Gapdh", "18.81", "18.83", "ref"],
    ];
  } else if (file.name.startsWith("il6_tnfa")) {
    cols = ["sample_id", "IL-6 (pg/mL)", "TNFα (pg/mL)", "OD450", "stdcurve"];
    rows = [
      ["S-M01-BASELINE", "12.4",  "8.1",  "0.182", "y=2.43x+0.06"],
      ["S-M01-24H",      "11.8",  "9.4",  "0.176", "y=2.43x+0.06"],
      ["S-M13-24H",      "189.2", "63.1", "0.892", "y=2.43x+0.06"],
      ["S-M14-24H",      "203.7", "71.8", "0.971", "y=2.43x+0.06"],
      ["S-M25-24H",      "67.4",  "32.2", "0.398", "y=2.43x+0.06"],
      ["S-M25-7D",       "41.1",  "21.0", "0.249", "y=2.43x+0.06"],
    ];
  } else if (file.name === "sample_map.csv") {
    cols = ["sample_id", "subject_id", "assay", "timepoint", "source_file"];
    rows = [
      ["S-M01-BASELINE", "M01", "qPCR",     "baseline", "qpcr_run_0524.rdml"],
      ["S-M02-BASELINE", "M02", "qPCR",     "—",        "qpcr_run_0524.rdml"],
      ["E-M01-BASELINE", "M01", "ELISA",    "baseline", "il6_tnfa_elisa_plate.csv"],
      ["B-M13",          "M13", "behavior", "24h",      "open_field_summary.csv"],
      ["B-M14",          "M14", "behavior", "24h",      "open_field_summary.csv"],
      ["B-M19",          "M19", "behavior", "24h",      "open_field_summary.csv"],
    ];
  } else {
    cols = ["subject_id", "session", "distance_cm", "center_s", "freeze_n"];
    rows = [
      ["M01", "open-field-24h", "3142", "84.2", "3"],
      ["M02", "open-field-24h", "3287", "92.1", "2"],
      ["M13", "open-field-24h", "1842", "31.4", "11"],
      ["M14", "open-field-24h", "1671", "28.8", "14"],
      ["M25", "open-field-24h", "2491", "58.3", "7"],
      ["M19", "open-field-24h", "1798", "33.1", "10"],
    ];
  }
  return (
    <div style={{ border: "1px solid var(--ink-200)", borderRadius: 8, overflow: "hidden" }}>
      <table className="data-table data-table-dense">
        <thead>
          <tr>{cols.map(c => <th key={c}>{c}</th>)}</tr>
        </thead>
        <tbody>
          {rows.map((r, i) => (
            <tr key={i}>
              {r.map((v, j) => (
                <td key={j} className="mono" style={{
                  color: v === "—" ? "var(--warn)" : v === "warn" || v === "missing" ? "var(--warn)" :
                         v === "ok" || v === "ref" ? "var(--ok)" : "var(--ink-800)"
                }}>{v}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/* ========== READINESS ========== */

function ReadinessPanel() {
  const [filter, setFilter] = useState("all");
  const filtered = DEMO_READINESS.filter(c => filter === "all" || c.severity === filter);
  return (
    <div>
      <PanelHeader
        eyebrow="Quality gate"
        title="Metadata readiness"
        subtitle="The core differentiator. ConductBench refuses to silently analyze data that has missing fields, units, or references."
        actions={
          <div style={{ display: "flex", gap: 4, background: "var(--ink-100)", padding: 3, borderRadius: 6 }}>
            {[["all","All"],["blocking","Blocking"],["warning","Warnings"],["complete","Complete"]].map(([k, l]) => (
              <button key={k} onClick={() => setFilter(k)}
                style={{
                  padding: "5px 10px", borderRadius: 4, border: "none",
                  background: filter === k ? "white" : "transparent",
                  color: filter === k ? "var(--ink-900)" : "var(--ink-600)",
                  fontSize: 12, fontWeight: 500, cursor: "pointer", fontFamily: "inherit",
                  boxShadow: filter === k ? "var(--shadow-sm)" : "none",
                }}>{l}</button>
            ))}
          </div>
        }
      />

      <div id="readiness" style={{ padding: "24px 28px 28px", display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 20 }}>
        <div className="panel">
          {filtered.map((c, i) => (
            <div key={i} style={{ display: "flex", gap: 14, padding: "16px 20px", borderTop: i === 0 ? "none" : "1px solid var(--ink-100)" }}>
              <BigSevIcon severity={c.severity} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
                  <div style={{ fontSize: 14, fontWeight: 500, color: "var(--ink-900)" }}>{c.label}</div>
                  {c.file && <span className="chip mono" style={{ fontSize: 10.5 }}>{c.file}</span>}
                </div>
                <p style={{ margin: "4px 0 0", fontSize: 13, color: "var(--ink-600)", lineHeight: 1.55 }}>{c.detail}</p>
                {c.severity === "warning" && (
                  <button className="btn btn-link" style={{ marginTop: 8, fontSize: 12.5 }}>
                    Resolve → open file
                  </button>
                )}
              </div>
            </div>
          ))}
        </div>

        <div style={{ display: "grid", gap: 16, alignContent: "start" }}>
          <div className="panel" style={{ padding: 22, textAlign: "center" }}>
            <div style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600 }}>Overall</div>
            <ScoreRing value={DEMO_STUDY.readinessScore} />
            <div style={{ marginTop: 12, fontSize: 13, color: "var(--ink-600)", lineHeight: 1.5 }}>
              <strong style={{ color: "var(--ink-900)" }}>Analysis-ready.</strong><br/>
              Resolve 2 warnings to lift the score above 90.
            </div>
          </div>
          <div className="panel" style={{ padding: 22 }}>
            <div style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600 }}>Why this matters</div>
            <p style={{ margin: "10px 0 0", fontSize: 13, color: "var(--ink-700)", lineHeight: 1.55 }}>
              In real labs, the analyst learns about missing timepoints from a Slack message six weeks before the grant deadline.
              ConductBench surfaces them at parse time.
            </p>
            <p style={{ margin: "10px 0 0", fontSize: 13, color: "var(--ink-700)", lineHeight: 1.55 }}>
              A readiness score is not statistical confidence — it's a checklist of joinability, completeness, and provenance.
            </p>
          </div>
        </div>
      </div>
    </div>
  );
}

function ScoreRing({ value }) {
  const r = 52, c = 2 * Math.PI * r, off = c - (value / 100) * c;
  return (
    <svg width="140" height="140" viewBox="0 0 140 140" style={{ margin: "16px auto 0", display: "block" }}>
      <circle cx="70" cy="70" r={r} fill="none" stroke="var(--ink-100)" strokeWidth="10" />
      <circle cx="70" cy="70" r={r} fill="none" stroke="var(--brand-500)" strokeWidth="10"
              strokeLinecap="round" strokeDasharray={c} strokeDashoffset={off}
              transform="rotate(-90 70 70)" />
      <text x="70" y="68" textAnchor="middle" fontSize="32" fontFamily="var(--font-mono)" fontWeight="600" fill="var(--ink-900)">{value}</text>
      <text x="70" y="88" textAnchor="middle" fontSize="11" fill="var(--ink-500)" letterSpacing=".06em">/ 100</text>
    </svg>
  );
}

function BigSevIcon({ severity }) {
  const cfg = severity === "complete"
    ? { bg: "var(--ok-bg)",    color: "var(--ok)",    glyph: "✓" }
    : severity === "warning"
    ? { bg: "var(--warn-bg)",  color: "var(--warn)",  glyph: "!" }
    : { bg: "var(--block-bg)", color: "var(--block)", glyph: "×" };
  return (
    <span style={{
      width: 32, height: 32, borderRadius: 8, background: cfg.bg, color: cfg.color,
      display: "flex", alignItems: "center", justifyContent: "center",
      fontSize: 16, fontWeight: 700, flexShrink: 0,
    }}>{cfg.glyph}</span>
  );
}

/* ========== JOINED ENDPOINT TABLE ========== */

function EndpointsPanel() {
  const [view, setView] = useState("compact"); // compact | full
  const [highlight, setHighlight] = useState(true);
  const [groupBy, setGroupBy] = useState(null);

  return (
    <div>
      <PanelHeader
        eyebrow="Output"
        title="Joined endpoint table"
        subtitle="108 rows · joined on subject × timepoint across qPCR, ELISA, and ConductVision behavior."
        actions={
          <>
            <div style={{ display: "flex", gap: 4, background: "var(--ink-100)", padding: 3, borderRadius: 6 }}>
              {[["compact", "Compact"], ["full", "Full"]].map(([k, l]) => (
                <button key={k} onClick={() => setView(k)}
                  style={{
                    padding: "5px 10px", borderRadius: 4, border: "none",
                    background: view === k ? "white" : "transparent",
                    color: view === k ? "var(--ink-900)" : "var(--ink-600)",
                    fontSize: 12, fontWeight: 500, cursor: "pointer", fontFamily: "inherit",
                    boxShadow: view === k ? "var(--shadow-sm)" : "none",
                  }}>{l}</button>
              ))}
            </div>
            <button className="btn btn-ghost btn-sm" onClick={() => setHighlight(v => !v)}
                    style={{ background: highlight ? "var(--brand-50)" : undefined, color: highlight ? "var(--brand-700)" : undefined, borderColor: highlight ? "var(--brand-200)" : undefined }}>
              {highlight ? "✓ " : ""}Source coloring
            </button>
            <button className="btn btn-ghost btn-sm">
              Export CSV
            </button>
          </>
        }
      />

      <div style={{ padding: "24px 28px 28px" }}>
        <div className="panel" style={{ overflow: "hidden" }}>
          <div style={{ padding: "10px 14px", borderBottom: "1px solid var(--ink-200)", background: "var(--ink-50)",
                        display: "flex", gap: 12, alignItems: "center", fontSize: 12, color: "var(--ink-600)" }}>
            <span>Color key:</span>
            <span><span className="dot" style={{ color: "var(--brand-600)" }}></span> bench (qPCR · ELISA)</span>
            <span><span className="dot" style={{ color: "var(--src-vision)" }}></span> vision (behavior)</span>
            <span style={{ marginLeft: "auto" }} className="mono">{DEMO_ENDPOINTS.length} rows</span>
          </div>
          <div style={{ maxHeight: 540, overflow: "auto" }}>
            <EndpointTable view={view} highlight={highlight} />
          </div>
          <GroupSummary />
        </div>
      </div>
    </div>
  );
}

function EndpointTable({ view, highlight }) {
  const rows = DEMO_ENDPOINTS;
  const benchBg = highlight ? "var(--brand-50)" : "transparent";
  const visionBg = highlight ? "var(--src-vision-bg)" : "transparent";
  return (
    <table className="data-table data-table-dense">
      <thead>
        <tr>
          <th>subject</th>
          <th>group</th>
          <th>timepoint</th>
          <th style={{ background: visionBg, color: highlight ? "var(--src-vision)" : undefined }}>distance_cm <span className="mono" style={{ fontSize: 10, opacity: .7 }}>vision</span></th>
          <th style={{ background: benchBg, color: highlight ? "var(--brand-700)" : undefined }}>qpcr_fc <span className="mono" style={{ fontSize: 10, opacity: .7 }}>bench</span></th>
          <th style={{ background: benchBg, color: highlight ? "var(--brand-700)" : undefined }}>elisa_pg_ml <span className="mono" style={{ fontSize: 10, opacity: .7 }}>bench</span></th>
          {view === "full" && <th>sample_ids</th>}
          {view === "full" && <th>methods</th>}
          <th>qc</th>
        </tr>
      </thead>
      <tbody>
        {rows.map((r, i) => (
          <tr key={i}>
            <td className="mono" style={{ color: "var(--ink-900)", fontWeight: 500 }}>{r.subjectId}</td>
            <td><GroupChip group={r.group} /></td>
            <td className="mono">{r.timepoint}</td>
            <td className="mono" style={{ background: visionBg, color: r.behaviorDistanceCm ? "var(--src-vision)" : "var(--ink-300)" }}>
              {r.behaviorDistanceCm ?? "—"}
            </td>
            <td className="mono" style={{ background: benchBg, color: "var(--brand-700)" }}>{r.qpcrFoldChange.toFixed(2)}×</td>
            <td className="mono" style={{ background: benchBg, color: "var(--brand-700)" }}>{r.elisaConcentrationPgMl}</td>
            {view === "full" && <td className="mono" style={{ fontSize: 10.5, color: "var(--ink-500)" }}>S-{r.subjectId}-{r.timepoint.toUpperCase()} · E-{r.subjectId}-{r.timepoint.toUpperCase()}</td>}
            {view === "full" && <td className="mono" style={{ fontSize: 10.5, color: "var(--ink-500)" }}>2^−ΔΔCt vs sham · std curve</td>}
            <td>
              {r.qcFlag === "ok"
                ? <span className="chip chip-ok" style={{ fontSize: 10.5 }}>ok</span>
                : <span className="chip chip-warn" style={{ fontSize: 10.5 }}>{r.qcFlag}</span>}
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

function GroupSummary() {
  const groups = DEMO_STUDY.groups;
  const tp = "24h";
  const rows = groups.map(g => {
    const r = DEMO_ENDPOINTS.filter(x => x.group === g && x.timepoint === tp);
    const mean = (arr, key) => +(arr.reduce((s, x) => s + (x[key] || 0), 0) / arr.length).toFixed(2);
    const std = (arr, key) => {
      const m = mean(arr, key);
      return +Math.sqrt(arr.reduce((s, x) => s + Math.pow((x[key] || 0) - m, 2), 0) / arr.length).toFixed(2);
    };
    return {
      group: g,
      n: r.length,
      distance: { mean: mean(r, "behaviorDistanceCm"), sem: +(std(r, "behaviorDistanceCm") / Math.sqrt(r.length)).toFixed(0) },
      fc:       { mean: mean(r, "qpcrFoldChange"),     sem: +(std(r, "qpcrFoldChange") / Math.sqrt(r.length)).toFixed(2) },
      elisa:    { mean: mean(r, "elisaConcentrationPgMl"), sem: +(std(r, "elisaConcentrationPgMl") / Math.sqrt(r.length)).toFixed(1) },
    };
  });
  return (
    <div style={{ borderTop: "1px solid var(--ink-200)", padding: "16px 20px", background: "var(--ink-50)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 10 }}>
        <div className="mono" style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600 }}>
          Group summary · 24h timepoint · mean ± SEM
        </div>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10 }}>
        {rows.map(r => (
          <div key={r.group} style={{ background: "white", border: "1px solid var(--ink-200)", borderRadius: 8, padding: 12 }}>
            <GroupChip group={r.group} />
            <div className="mono" style={{ marginTop: 8, fontSize: 11, color: "var(--ink-500)" }}>n = {r.n}</div>
            <div style={{ marginTop: 8, display: "grid", gap: 4 }}>
              <SummaryRow label="distance_cm" mean={r.distance.mean} sem={r.distance.sem} color="var(--src-vision)" />
              <SummaryRow label="qpcr_fc"     mean={r.fc.mean}       sem={r.fc.sem}       color="var(--brand-700)" suffix="×" />
              <SummaryRow label="elisa_pg_ml" mean={r.elisa.mean}    sem={r.elisa.sem}    color="var(--brand-700)" />
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function SummaryRow({ label, mean, sem, color, suffix = "" }) {
  return (
    <div className="mono" style={{ display: "flex", justifyContent: "space-between", fontSize: 12, color: "var(--ink-600)" }}>
      <span>{label}</span>
      <span style={{ color }}>{mean}{suffix} <span style={{ color: "var(--ink-400)" }}>± {sem}</span></span>
    </div>
  );
}

/* ========== METHODS / PROVENANCE ========== */

function MethodsPanel() {
  const [mode, setMode] = useState("short");
  const [copied, setCopied] = useState(false);
  const text = mode === "short" ? DEMO_METHODS_SHORT : DEMO_METHODS_LONG;

  const copy = () => {
    navigator.clipboard?.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };

  return (
    <div id="methods">
      <PanelHeader
        eyebrow="Reproducibility"
        title="Methods + provenance"
        subtitle="Generated from the source files, parser versions, and join logic actually used. Copy directly into a manuscript or grant."
        actions={
          <>
            <div style={{ display: "flex", gap: 4, background: "var(--ink-100)", padding: 3, borderRadius: 6 }}>
              {[["short","Short"],["long","Detailed"]].map(([k, l]) => (
                <button key={k} onClick={() => setMode(k)}
                  style={{
                    padding: "5px 10px", borderRadius: 4, border: "none",
                    background: mode === k ? "white" : "transparent",
                    color: mode === k ? "var(--ink-900)" : "var(--ink-600)",
                    fontSize: 12, fontWeight: 500, cursor: "pointer", fontFamily: "inherit",
                  }}>{l}</button>
              ))}
            </div>
            <button className="btn btn-ghost btn-sm" onClick={copy}>{copied ? "✓ Copied" : "Copy text"}</button>
          </>
        }
      />

      <div style={{ padding: "24px 28px 28px", display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 20 }}>
        <div className="panel" style={{ padding: 28 }}>
          <article style={{ fontSize: 14.5, lineHeight: 1.65, color: "var(--ink-800)", fontFamily: "Georgia, 'Iowan Old Style', serif" }}>
            {text.split("\n\n").map((p, i) => <p key={i} style={{ margin: i === 0 ? 0 : "16px 0 0" }}>{p}</p>)}
          </article>
        </div>

        <div style={{ display: "grid", gap: 16, alignContent: "start" }}>
          <div className="panel">
            <div className="panel-head"><h3>Provenance</h3><span className="mono" style={{ fontSize: 11, color: "var(--ink-500)" }}>4 artifacts</span></div>
            <div style={{ padding: "4px 0" }}>
              {DEMO_FILES.map((f, i) => (
                <div key={f.name} style={{ padding: "12px 16px", borderTop: i === 0 ? "none" : "1px solid var(--ink-100)" }}>
                  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                    <span className="mono" style={{ fontSize: 12, color: "var(--ink-900)", fontWeight: 500 }}>{f.name}</span>
                    <span className={`chip chip-${f.source}`} style={{ fontSize: 10.5 }}>{f.source}</span>
                  </div>
                  <div className="mono" style={{ marginTop: 4, fontSize: 10.5, color: "var(--ink-500)" }}>
                    sha256 · {["7a2c…3e91", "0bb4…91ad", "1cc5…2f10", "2dd8…7bcb"][i]}
                  </div>
                </div>
              ))}
            </div>
          </div>

          <div className="panel" style={{ padding: 20 }}>
            <div style={{ fontSize: 11, color: "var(--ink-500)", letterSpacing: ".06em", textTransform: "uppercase", fontWeight: 600 }}>Software</div>
            <div className="mono" style={{ marginTop: 8, fontSize: 12.5, color: "var(--ink-800)", lineHeight: 1.7 }}>
              ConductBench&nbsp;v3.2<br/>
              parser-qpcr&nbsp;v0.4.2<br/>
              parser-elisa&nbsp;v0.3.1<br/>
              join-engine&nbsp;v0.2.0<br/>
              <span style={{ color: "var(--ink-500)" }}>exported 2026-05-24T09:42:18Z</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ========== REPORT PREVIEW ========== */

function ReportPanel() {
  return (
    <div>
      <PanelHeader
        eyebrow="Final artifact"
        title="Report preview"
        subtitle="Clean PDF · native CSV · signed provenance log. Generated from the same files and joins you see in the workspace."
        actions={
          <>
            <button className="btn btn-ghost btn-sm">Export CSV</button>
            <button className="btn btn-primary btn-sm">
              Generate PDF <ArrowR />
            </button>
          </>
        }
      />
      <div style={{ padding: "24px 28px 60px" }}>
        <div style={{
          maxWidth: 820, margin: "0 auto",
          background: "white", border: "1px solid var(--ink-200)",
          borderRadius: 14, boxShadow: "var(--shadow-md)",
          padding: "56px 64px", fontFamily: "Georgia, 'Iowan Old Style', serif",
          color: "var(--ink-900)",
        }}>
          <div className="mono" style={{ fontSize: 10.5, letterSpacing: ".12em", color: "var(--ink-500)", textTransform: "uppercase", fontFamily: "var(--font-mono)" }}>
            CONDUCTBENCH · STUDY REPORT · {DEMO_STUDY.id}
          </div>
          <h1 style={{ margin: "12px 0 0", fontSize: 28, fontWeight: 600, letterSpacing: "-0.01em", lineHeight: 1.2 }}>
            {DEMO_STUDY.title}
          </h1>
          <div className="mono" style={{ marginTop: 8, fontSize: 12, color: "var(--ink-500)", fontFamily: "var(--font-mono)" }}>
            {DEMO_STUDY.pi} · exported 2026-05-24
          </div>

          <ReportSection title="1. Study summary">
            <p>n = {DEMO_STUDY.cohortSize} {DEMO_STUDY.species}. Groups: {DEMO_STUDY.groups.join(", ")}. Assays: {DEMO_STUDY.assayTypes.join(", ")}. Metadata readiness 83 / 100 — analysis-ready, 2 warnings flagged in the joined record.</p>
          </ReportSection>

          <ReportSection title="2. Metadata checklist">
            <ul style={{ paddingLeft: 22, margin: 0, display: "grid", gap: 5 }}>
              {DEMO_READINESS.slice(0, 6).map((c, i) => (
                <li key={i} style={{ fontSize: 14 }}>
                  <span style={{ color: c.severity === "complete" ? "var(--ok)" : c.severity === "warning" ? "var(--warn)" : "var(--block)" }}>
                    {c.severity === "complete" ? "✓" : c.severity === "warning" ? "!" : "×"}
                  </span> {c.label}
                </li>
              ))}
            </ul>
          </ReportSection>

          <ReportSection title="3. Joined endpoint table (excerpt)">
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, border: "1px solid var(--ink-200)", borderRadius: 6, overflow: "hidden" }}>
              <table className="data-table data-table-dense" style={{ fontFamily: "var(--font-mono)" }}>
                <thead><tr><th>subject</th><th>group</th><th>tp</th><th>distance</th><th>qpcr_fc</th><th>elisa</th></tr></thead>
                <tbody>
                  {DEMO_ENDPOINTS.slice(0, 6).map((r, i) => (
                    <tr key={i}>
                      <td>{r.subjectId}</td>
                      <td>{r.group}</td>
                      <td>{r.timepoint}</td>
                      <td>{r.behaviorDistanceCm ?? "—"}</td>
                      <td>{r.qpcrFoldChange.toFixed(2)}×</td>
                      <td>{r.elisaConcentrationPgMl}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <div style={{ marginTop: 8, fontSize: 12.5, color: "var(--ink-500)" }}>… 102 more rows in the full export.</div>
          </ReportSection>

          <ReportSection title="4. Group summary">
            <p>At 24h, TBI animals showed reduced locomotion (mean 1,820 cm vs. 3,070 cm sham) and elevated IL-6 (mean 187 pg/mL vs. 12 pg/mL). The TBI + treatment group showed partial rescue across both modalities (2,510 cm; 64 pg/mL). qPCR fold change vs. sham baseline mirrored the ELISA pattern. Group differences will be tested by two-way ANOVA in the analysis phase.</p>
          </ReportSection>

          <ReportSection title="5. Methods + provenance">
            <p style={{ fontSize: 13, lineHeight: 1.65 }}>{DEMO_METHODS_SHORT}</p>
          </ReportSection>

          <div style={{ marginTop: 36, padding: 16, background: "var(--brand-50)", borderRadius: 8,
                        border: "1px solid var(--brand-200)", display: "flex", gap: 12, alignItems: "center", justifyContent: "space-between",
                        fontFamily: "var(--font-sans)" }}>
            <div style={{ fontSize: 13, color: "var(--ink-800)" }}>
              <strong style={{ color: "var(--ink-900)" }}>Reproducible.</strong> Every report is signed with the SHA-256 of every input file and the parser version that produced it.
            </div>
            <button className="btn btn-primary btn-sm">Generate PDF <ArrowR /></button>
          </div>
        </div>
      </div>
    </div>
  );
}

function ReportSection({ title, children }) {
  return (
    <section style={{ marginTop: 28 }}>
      <h2 style={{ margin: 0, fontSize: 16, fontWeight: 600, color: "var(--ink-900)", fontFamily: "var(--font-sans)", letterSpacing: "-0.005em" }}>{title}</h2>
      <div style={{ marginTop: 10, fontSize: 14, lineHeight: 1.7, color: "var(--ink-800)" }}>{children}</div>
    </section>
  );
}

/* ========== misc ========== */

function ArrowR() { return <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 5l7 7-7 7"/></svg>; }

Object.assign(window, {
  Sidebar, PANELS,
  StudyOverviewPanel, SubjectsPanel, FilesPanel, ReadinessPanel,
  EndpointsPanel, MethodsPanel, ReportPanel,
});
