import React, { useEffect, useMemo, useRef, useState } from "react";
import { FolderOpen, Search, ArrowRight, FileClock, FlaskConical, Upload, UploadCloud, Trash2, HelpCircle, X, Lock, Fingerprint, ShieldAlert, Lightbulb, MessageSquare, Cpu, Database, Server, Eraser, ShieldCheck, PlayCircle } from "lucide-react";
import { C, FD, FM, FB } from "../theme.js";
import { fetchSessions, useApi } from "../useAnalysis.js";
import { withClient, clearMyData } from "../client.js";
import SessionBrowser from "./SessionBrowser.jsx";
import DisclaimerModal from "./DisclaimerModal.jsx";

// TOP-LEVEL LANDING — the projects in ~/.claude (or, on the hosted Space, the sessions
// you've uploaded), grouped by the TRUE cwd the engine reads from inside each file
// (never the lossy encoded folder name, NN#5). Pick a project -> its sessions
// (ProjectView) -> a session (the graph). Nothing is analyzed here; pure navigation.

function fmtAge(sec) {
  if (!sec) return "";
  const d = Date.now() / 1000 - sec;
  if (d < 3600) return Math.max(1, Math.round(d / 60)) + "m ago";
  if (d < 86400) return Math.round(d / 3600) + "h ago";
  return Math.round(d / 86400) + "d ago";
}
const _MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function fmtDateTime(iso_or_ms) {
  if (!iso_or_ms) return "";
  const dt = new Date(iso_or_ms);
  if (isNaN(dt.getTime())) return "";
  const mon = _MONTHS[dt.getMonth()];
  const day = String(dt.getDate()).padStart(2, "0");
  const hh = String(dt.getHours()).padStart(2, "0");
  const mm = String(dt.getMinutes()).padStart(2, "0");
  return `${mon} ${day}, ${hh}:${mm}`;
}

export default function ProjectsHome({ onOpenProject, onOpenSession, onDemo }) {
  const [state, setState] = useState({ status: "loading", data: null, error: null });
  const [q, setQ] = useState("");
  const [browse, setBrowse] = useState(false);
  const fileRef = useRef(null);
  const [uploading, setUploading] = useState(false);
  const [uploadErr, setUploadErr] = useState(null);
  const [progress, setProgress] = useState(null); // {done,total} during a multi-upload
  const [dragOver, setDragOver] = useState(false);
  const [showHelp, setShowHelp] = useState(false);
  const [showPrivacy, setShowPrivacy] = useState(false);
  const [showVideo, setShowVideo] = useState(false);
  const [clearing, setClearing] = useState(false);

  async function refresh() {
    try {
      const data = await fetchSessions();
      setState({ status: "ready", data, error: null });
    } catch (e) {
      setState({ status: "error", data: null, error: String(e) });
    }
  }

  // Upload one or many .jsonl session exports → stored under YOUR namespace on the
  // Space → grouped into the Projects view. The deterministic /api/upload route is
  // plain REST (no GPU); the X-Her-Client header scopes the upload to this browser.
  async function uploadFiles(fileList) {
    const files = Array.from(fileList || []).filter((f) => f.name.toLowerCase().endsWith(".jsonl"));
    if (!files.length) { setUploadErr("Please choose .jsonl session export(s)."); return; }
    setUploading(true); setUploadErr(null);
    setProgress({ done: 0, total: files.length });
    let lastPath = null, ok = 0;
    for (const f of files) {
      try {
        const fd = new FormData();
        fd.append("file", f);
        fd.append("project", "uploads");
        // withClient() sets only X-Her-Client — NOT Content-Type, so the browser keeps
        // the multipart boundary it generates for FormData.
        const r = await fetch("/api/upload", { method: "POST", body: fd, headers: withClient() });
        const j = await r.json();
        if (r.ok && j.path) { lastPath = j.path; ok++; }
        else if (j && j.error) setUploadErr(j.error);
      } catch (err) {
        setUploadErr(String(err && err.message ? err.message : err));
      }
      setProgress((p) => ({ done: (p?.done || 0) + 1, total: files.length }));
    }
    setUploading(false); setProgress(null);
    if (ok === 0) { setUploadErr((e) => e || "Upload failed."); return; }
    setUploadErr(null);
    if (files.length === 1 && lastPath) onOpenSession(lastPath); // single → open it
    else await refresh(); // many → land on the populated Projects view
  }

  function onInputChange(e) {
    // SNAPSHOT first: e.target.files is a LIVE FileList tied to the input. Resetting
    // value (to allow re-selecting the same file) empties that same list in place, so
    // reading it after the reset yields 0 files — the "Please choose .jsonl…" bug on
    // the Upload button (drag-drop was unaffected; it uses a separate FileList).
    const fl = Array.from(e.target.files || []);
    if (e.target) e.target.value = ""; // allow re-selecting the same file(s)
    uploadFiles(fl);
  }

  async function onClear() {
    if (clearing) return;
    setClearing(true);
    try { await clearMyData(); await refresh(); } finally { setClearing(false); }
  }

  // drag-and-drop over the whole landing body
  function onDragOver(e) { e.preventDefault(); if (!dragOver) setDragOver(true); }
  function onDragLeave(e) { e.preventDefault(); if (e.currentTarget === e.target) setDragOver(false); }
  function onDrop(e) { e.preventDefault(); setDragOver(false); uploadFiles(e.dataTransfer && e.dataTransfer.files); }

  useEffect(() => { refresh(); /* eslint-disable-next-line */ }, []);

  const projects = useMemo(() => {
    const ps = (state.data?.projects || []).map((p) => ({
      ...p,
      name: p.cwd.split("/").filter(Boolean).pop() || p.cwd,
      lastMtime: Math.max(0, ...p.sessions.map((s) => s.mtime || 0)),
    }));
    ps.sort((a, b) => b.lastMtime - a.lastMtime);
    if (!q.trim()) return ps;
    const n = q.toLowerCase();
    return ps.filter((p) => p.cwd.toLowerCase().includes(n) || p.name.toLowerCase().includes(n));
  }, [state.data, q]);

  const hasSessions = (state.data?.total || 0) > 0;
  const btn = (uploading) => ({ cursor: uploading ? "default" : "pointer", display: "inline-flex", alignItems: "center", gap: 7, background: C.orange, color: "#fff", fontFamily: FD, fontWeight: 600, borderRadius: 9, whiteSpace: "nowrap" });

  return (
    <div onDragOver={onDragOver} onDragLeave={onDragLeave} onDrop={onDrop}
      style={{ fontFamily: FB, color: C.text, height: "100vh", display: "flex", flexDirection: "column", background: "radial-gradient(1200px 520px at 25% -8%, #3b3633 0%, #2b2927 55%)", position: "relative" }}>
      {/* header */}
      <div style={{ height: 58, background: C.header, borderBottom: `1px solid ${C.borderSoft}`, boxShadow: `0 1px 0 0 ${C.orange}22`, display: "flex", alignItems: "center", padding: "0 18px", gap: 12, flexShrink: 0 }}>
        <img src="/her-logo-light.png" alt="Her" style={{ height: 28, display: "block" }} />
        <span style={{ fontFamily: FD, fontWeight: 600, fontSize: 13, color: C.muted, letterSpacing: 1 }}>हेर</span>
        <span style={{ fontFamily: FM, fontSize: 11, color: C.muted }}>· a detective for your coding-agent sessions</span>
        <div style={{ flex: 1 }} />
        <div className="row lift" onClick={() => setShowPrivacy(true)} title="How your data is handled (we never store your sessions)"
          style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: 6, fontFamily: FM, fontSize: 11, color: C.text2, border: `1px solid ${C.border}`, borderRadius: 7, padding: "5px 10px" }}>
          <Lock size={13} color={C.cyan} /> privacy
        </div>
        <div className="row lift" onClick={() => setShowHelp(true)} style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: 6, fontFamily: FM, fontSize: 11, color: C.text2, border: `1px solid ${C.border}`, borderRadius: 7, padding: "5px 10px" }}>
          <HelpCircle size={13} color={C.cyan} /> how to get my sessions
        </div>
        {hasSessions && (
          <div className="row lift" onClick={onClear} title="Delete the sessions you've uploaded (also auto-cleared after 24h)"
            style={{ cursor: clearing ? "default" : "pointer", display: "flex", alignItems: "center", gap: 6, fontFamily: FM, fontSize: 11, color: C.amber, border: `1px solid ${C.amber}55`, borderRadius: 7, padding: "5px 10px" }}>
            <Trash2 size={13} /> {clearing ? "clearing…" : "clear my data"}
          </div>
        )}
        <div style={{ display: "flex", alignItems: "center", gap: 6, fontFamily: FM, fontSize: 11, color: C.cyan, border: `1px solid ${C.cyan}33`, borderRadius: 20, padding: "3px 10px" }}>
          <span style={{ width: 7, height: 7, borderRadius: "50%", background: C.cyan }} /> PRIVATE · auto-clears 24h
        </div>
      </div>

      {/* body */}
      <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "26px 30px 40px" }}>
        <div style={{ display: "flex", alignItems: "baseline", gap: 12 }}>
          <span style={{ fontFamily: FD, fontWeight: 700, fontSize: 22, letterSpacing: 0.3 }}>Projects</span>
          {state.data && (
            <span style={{ fontFamily: FM, fontSize: 12, color: C.muted }}>
              {state.data.projectCount} projects · {state.data.total} sessions
            </span>
          )}
        </div>
        <div style={{ fontFamily: FM, fontSize: 11.5, color: C.muted, marginTop: 4 }}>
          Pick a project to see its sessions, then open a session to trace it. Grouped by the real working directory read from inside each file.
        </div>

        {/* search + upload */}
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 16, flexWrap: "wrap" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8, background: C.black, border: `1px solid ${C.borderSoft}`, borderRadius: 9, padding: "8px 12px", flex: 1, minWidth: 240, maxWidth: 460 }}>
            <Search size={14} color={C.muted} />
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="filter projects…" style={{ flex: 1, background: "transparent", border: "none", outline: "none", color: C.text, fontFamily: FM, fontSize: 12.5 }} />
          </div>
          <input ref={fileRef} type="file" multiple accept=".jsonl,application/x-ndjson,application/jsonl" onChange={onInputChange} style={{ display: "none" }} />
          <div className="row lift" onClick={() => !uploading && fileRef.current && fileRef.current.click()}
            style={{ ...btn(uploading), fontSize: 12.5, padding: "9px 15px" }} title="Upload Claude Code session export(s) (.jsonl)">
            <Upload size={14} /> {uploading ? (progress ? `Uploading ${progress.done}/${progress.total}…` : "Uploading…") : "Upload .jsonl"}
          </div>
        </div>
        {uploadErr && <div style={{ marginTop: 8, fontFamily: FM, fontSize: 11, color: C.amber }}>{uploadErr}</div>}

        {/* empty inventory → drop zone (left) + "what is this" (fills the blank space on
            the right). Both gated to the same empty state, so they vanish the moment a
            session/project loads. Wraps to stacked on narrow widths. */}
        {state.status === "ready" && (state.data?.projectCount || 0) === 0 && (
          <div style={{ display: "flex", gap: 24, marginTop: 22, alignItems: "flex-start", flexWrap: "wrap" }}>
            <div style={{ flex: "1 1 340px", minWidth: 300, maxWidth: 520, display: "flex", flexDirection: "column", gap: 12 }}>
            <div className="pop" onClick={() => !uploading && fileRef.current && fileRef.current.click()}
              style={{ cursor: "pointer", background: `linear-gradient(135deg,${C.card},${C.panel})`, border: `1.5px dashed ${C.orangeBd}`, borderRadius: 12, padding: "26px 22px", textAlign: "center" }}>
              <UploadCloud size={30} color={C.orange} />
              <div style={{ fontFamily: FD, fontWeight: 700, fontSize: 16, color: C.text, marginTop: 8 }}>Drag &amp; drop your sessions here</div>
              <div style={{ fontFamily: FM, fontSize: 12, color: C.muted, marginTop: 7, lineHeight: 1.6 }}>
                Drop one or many <code style={{ color: C.text2 }}>.jsonl</code> exports (or click to choose). Her analyzes them live — the journey,
                the dataflow, the cost, what to do next time. Your data is private and auto-clears in 24h.
              </div>
              <div style={{ display: "flex", gap: 10, justifyContent: "center", marginTop: 14, flexWrap: "wrap" }}>
                <span style={{ ...btn(uploading), fontSize: 13, padding: "10px 18px" }}>
                  <Upload size={15} /> {uploading ? "Uploading…" : "Choose .jsonl files"}
                </span>
                <span className="lift" onClick={(e) => { e.stopPropagation(); setShowHelp(true); }}
                  style={{ cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 7, border: `1px solid ${C.border}`, color: C.text2, fontFamily: FM, fontSize: 12.5, borderRadius: 9, padding: "10px 16px" }}>
                  <HelpCircle size={14} color={C.cyan} /> Where are my sessions?
                </span>
              </div>
            </div>
              {onDemo && (
                <div className="row lift" onClick={onDemo} title="A real coding-agent session, credential-scanned & identity-sanitized"
                  style={{ cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 8, background: C.card, border: `1px dashed ${C.cyan}66`, borderRadius: 10, padding: "11px 14px", fontFamily: FM, fontSize: 12, color: C.text2 }}>
                  <FlaskConical size={14} color={C.cyan} /> no session handy?&nbsp;<b style={{ color: C.cyan, fontWeight: 600 }}>explore a real demo session</b>&nbsp;→
                </div>
              )}
              <div className="row lift" onClick={() => setShowVideo(true)} title="A short walkthrough of what Her does"
                style={{ cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 8, background: C.card, border: `1px dashed ${C.cyan}66`, borderRadius: 10, padding: "11px 14px", fontFamily: FM, fontSize: 12, color: C.text2 }}>
                <PlayCircle size={14} color={C.cyan} /> prefer to watch?&nbsp;<b style={{ color: C.cyan, fontWeight: 600 }}>see the demo video</b>&nbsp;→
              </div>
              <BuiltOn />
            </div>
            <div style={{ flex: "1 1 460px", minWidth: 320 }}>
              <LandingExplainer />
            </div>
          </div>
        )}

        {state.status === "loading" && (
          <div style={{ marginTop: 28, color: C.muted, fontFamily: FM, fontSize: 12 }}>loading your sessions…</div>
        )}
        {state.status === "error" && (
          <div style={{ marginTop: 28, color: C.amber, fontFamily: FM, fontSize: 12.5, border: `1px solid ${C.amber}`, borderRadius: 9, padding: "14px 16px", maxWidth: 520 }}>
            Couldn't load sessions. {state.error}
          </div>
        )}

        {/* project grid */}
        {state.status === "ready" && (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(330px, 1fr))", gap: 12, marginTop: 18 }}>
            {projects.map((p) => {
              const latest = p.sessions[0];
              return (
                <div key={p.cwd} className="lift pop" onClick={() => onOpenProject(p.cwd)}
                  style={{ cursor: "pointer", background: `linear-gradient(135deg,${C.card},${C.panel})`, border: `1px solid ${C.borderSoft}`, borderRadius: 11, padding: "14px 15px", display: "flex", flexDirection: "column", gap: 9 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
                    <div style={{ width: 30, height: 30, borderRadius: 8, flexShrink: 0, background: C.elevated, display: "flex", alignItems: "center", justifyContent: "center", border: `1px solid ${C.orangeBd}` }}>
                      <FolderOpen size={15} color={C.orange} />
                    </div>
                    <div style={{ minWidth: 0, flex: 1 }}>
                      <div style={{ fontFamily: FD, fontWeight: 600, fontSize: 14, color: C.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.name}</div>
                      <div style={{ fontFamily: FM, fontSize: 10, color: C.muted, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={p.cwd}>{p.cwd}</div>
                    </div>
                    <ArrowRight size={15} color={C.muted} />
                  </div>
                  <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                    <span style={{ fontFamily: FM, fontSize: 10, color: C.text2, background: C.black, border: `1px solid ${C.borderSoft}`, borderRadius: 20, padding: "2px 9px" }}>
                      {p.count} session{p.count === 1 ? "" : "s"}
                    </span>
                    {latest && fmtDateTime(latest.startedAt || (latest.mtime ? latest.mtime * 1000 : 0)) && (
                      <span style={{ fontFamily: FM, fontSize: 10, color: C.text2, fontWeight: 600 }}>
                        {fmtDateTime(latest.startedAt || (latest.mtime ? latest.mtime * 1000 : 0))}
                      </span>
                    )}
                    {p.lastMtime > 0 && <span style={{ fontFamily: FM, fontSize: 10, color: C.muted }}>active {fmtAge(p.lastMtime)}</span>}
                    {latest && (
                      <span className="lift" onClick={(e) => { e.stopPropagation(); onOpenSession(latest.path); }} title="open the most recent session directly"
                        style={{ marginLeft: "auto", fontFamily: FM, fontSize: 9.5, color: C.orange, border: `1px solid ${C.orangeBd}`, background: C.orangeMut, borderRadius: 5, padding: "2px 8px" }}>
                        latest →
                      </span>
                    )}
                  </div>
                </div>
              );
            })}
            {projects.length === 0 && hasSessions && (
              <div style={{ color: C.muted, fontFamily: FM, fontSize: 12 }}>no projects match “{q}”.</div>
            )}
          </div>
        )}

        {/* When projects exist there's no drop zone, so keep a demo affordance at the
            foot of the list. In the empty state the prominent one lives under the drop
            zone instead (above), so this is gated to the populated state. */}
        {(state.data?.projectCount || 0) > 0 && (
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 22, flexWrap: "wrap" }}>
            {onDemo && (
              <div className="row lift" onClick={onDemo} title="A real coding-agent session, credential-scanned & identity-sanitized" style={{ cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 8, fontFamily: FM, fontSize: 11.5, color: C.text2, border: `1px dashed ${C.border}`, borderRadius: 8, padding: "8px 13px" }}>
                <FlaskConical size={13} color={C.cyan} /> no session handy? explore a real demo session →
              </div>
            )}
            <div className="row lift" onClick={() => setShowVideo(true)} title="A short walkthrough of what Her does" style={{ cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 8, fontFamily: FM, fontSize: 11.5, color: C.text2, border: `1px dashed ${C.border}`, borderRadius: 8, padding: "8px 13px" }}>
              <PlayCircle size={13} color={C.cyan} /> watch the demo video →
            </div>
          </div>
        )}
      </div>

      {/* drag overlay */}
      {dragOver && (
        <div style={{ position: "absolute", inset: 0, zIndex: 60, background: "rgba(20,18,16,0.86)", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 14, border: `2px dashed ${C.orange}`, pointerEvents: "none" }}>
          <UploadCloud size={48} color={C.orange} />
          <div style={{ fontFamily: FD, fontWeight: 700, fontSize: 20, color: C.text }}>Drop .jsonl sessions to analyze</div>
          <div style={{ fontFamily: FM, fontSize: 12, color: C.muted }}>one or many — they’ll group into your Projects view</div>
        </div>
      )}

      {showHelp && <HelpModal onClose={() => setShowHelp(false)} />}
      {showPrivacy && <DisclaimerModal onDone={() => setShowPrivacy(false)} />}
      {showVideo && <VideoModal onClose={() => setShowVideo(false)} />}
      {browse && <SessionBrowser current={null} onPick={onOpenSession} onOpenProject={onOpenProject} onClose={() => setBrowse(false)} />}
    </div>
  );
}

// The empty-landing "what is this" — only renders before anything is uploaded. Mostly
// scannable labels (one short paragraph), themed to Tactical Grey: orange stays brand-
// only (the हेर wordmark), cyan/grey carry the rest. Don't oversell.
function LandingExplainer() {
  const Eyebrow = ({ children }) => (
    <div style={{ fontFamily: FM, fontSize: 10, letterSpacing: 1.5, color: C.cyan, fontWeight: 600 }}>{children}</div>
  );
  const features = [
    { icon: Fingerprint, t: "Trace any action", d: "why it ran · which tools fired · what it touched" },
    { icon: ShieldAlert, t: "Risky moves, surfaced", d: "deploys, prod & config changes, secrets" },
    { icon: Lightbulb, t: "Do it better", d: "tips grounded in Anthropic & community practice" },
    { icon: MessageSquare, t: "Ask Her", d: "chat about one session — or a whole project" },
  ];
  const build = [
    { icon: Cpu, t: "Local Nemotron", d: "writes summaries & enrichment" },
    { icon: Database, t: "Local identifier DB", d: "names your tools & binaries" },
    { icon: Server, t: "Temporary bucket", d: "holds your data only while analyzing" },
    { icon: Eraser, t: "Best-effort scrubbers", d: "redact likely secrets on the way in" },
    { icon: ShieldCheck, t: "Yours alone", d: "only you can read it · cleared on demand or after 24h" },
  ];
  return (
    <div style={{ maxWidth: 760 }}>
      {/* what is Her */}
      <Eyebrow>WHAT IS THIS</Eyebrow>
      <div style={{ display: "flex", alignItems: "center", gap: 11, marginTop: 9 }}>
        <img src="/her-mark-light.png" alt="Her" style={{ width: 38, height: 38, objectFit: "contain", flexShrink: 0 }} />
        <div style={{ fontFamily: FD, fontWeight: 700, fontSize: 21, color: C.text, lineHeight: 1.25 }}>
          <span style={{ color: C.orange }}>हेर</span> — a detective for your coding-agent sessions.
        </div>
      </div>
      <div style={{ fontFamily: FB, fontSize: 13, color: C.text2, marginTop: 9, lineHeight: 1.65, maxWidth: 700 }}>
        <b style={{ color: C.text }}>her</b> (हेर) is Marathi for <i>detective</i>. Drop a Claude Code session export
        (<code style={{ fontFamily: FM, color: C.text2 }}>.jsonl</code>) and Her reads the whole trace — so you can see
        what actually happened, and what to do better next time.
      </div>

      {/* features */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 10, marginTop: 18 }}>
        {features.map((f) => (
          <div key={f.t} style={{ background: `linear-gradient(135deg,${C.card},${C.panel})`, border: `1px solid ${C.borderSoft}`, borderRadius: 10, padding: "12px 13px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <f.icon size={15} color={C.cyan} />
              <span style={{ fontFamily: FD, fontWeight: 600, fontSize: 13, color: C.text }}>{f.t}</span>
            </div>
            <div style={{ fontFamily: FM, fontSize: 10.5, color: C.muted, marginTop: 6, lineHeight: 1.55 }}>{f.d}</div>
          </div>
        ))}
      </div>

      {/* two ways in */}
      <div style={{ fontFamily: FM, fontSize: 11, color: C.muted, marginTop: 14, lineHeight: 1.6 }}>
        One file opens a <b style={{ color: C.text2 }}>session view</b>; the uploader script builds a{" "}
        <b style={{ color: C.text2 }}>project view</b> across many — both with summaries and deep dives.
      </div>

      {/* under the hood + trust */}
      <div style={{ marginTop: 22, borderTop: `1px solid ${C.borderSoft}`, paddingTop: 14 }}>
        <Eyebrow>UNDER THE HOOD</Eyebrow>
        <div style={{ display: "flex", flexWrap: "wrap", gap: "9px 20px", marginTop: 9 }}>
          {build.map((b) => (
            <div key={b.t} style={{ display: "flex", alignItems: "center", gap: 7 }}>
              <b.icon size={13} color={C.text2} />
              <span style={{ fontFamily: FM, fontSize: 10.5, color: C.text2 }}>{b.t}</span>
              <span style={{ fontFamily: FM, fontSize: 10.5, color: C.muted }}>· {b.d}</span>
            </div>
          ))}
        </div>
      </div>

      {/* legend */}
      <div style={{ fontFamily: FM, fontSize: 10.5, fontStyle: "italic", color: C.muted, marginTop: 20, opacity: 0.85 }}>
        Built for a couple of wizards who couldn’t quite harness their magic staff. 🪄
      </div>
    </div>
  );
}

// One grayscale brand mark. The source SVGs carry their own brand colours, so we render
// them as a single-tint silhouette via a CSS mask (alpha → fill) — uniform on the dark
// skin regardless of each logo's palette, and genuinely "grayscale". Sized by height,
// width follows the artwork's aspect ratio. Decorative (a credit), so not a link.
function Logo({ src, h, ar, title }) {
  return (
    <span role="img" aria-label={title} title={title}
      style={{
        display: "inline-block", height: h, width: Math.round(h * ar), flexShrink: 0,
        backgroundColor: C.text2, opacity: 0.6,
        WebkitMaskImage: `url(${src})`, maskImage: `url(${src})`,
        WebkitMaskRepeat: "no-repeat", maskRepeat: "no-repeat",
        WebkitMaskPosition: "center", maskPosition: "center",
        WebkitMaskSize: "contain", maskSize: "contain",
      }} />
  );
}

// "built on" credit — the stack Her runs on, in grayscale. Nemotron is NVIDIA's model,
// so its mark is the NVIDIA wordmark. Heights are tuned per-logo for optical balance
// (a square icon next to two wordmarks of different aspect ratios).
function BuiltOn() {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap", marginTop: 2, paddingTop: 13, borderTop: `1px solid ${C.borderSoft}` }}>
      <span style={{ fontFamily: FM, fontSize: 10, letterSpacing: 1.5, color: C.muted, fontWeight: 600 }}>BUILT ON</span>
      <Logo src="/brand/gradio.svg" h={15} ar={2.868} title="Gradio" />
      <Logo src="/brand/huggingface.svg" h={20} ar={1.08} title="Hugging Face" />
      <Logo src="/brand/nvidia.png" h={21} ar={1.4675} title="NVIDIA Nemotron" />
    </div>
  );
}

// The recorded product demo — served by GET /api/demo-video (the bucket copy on the
// Space, the repo's demo/ copy locally). FileResponse honours Range, so the player can
// seek. Click-outside / X to close, matching the other modals (no client-side router).
function VideoModal({ onClose }) {
  const [err, setErr] = useState(false);
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 100, background: "rgba(0,0,0,0.8)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: 980, maxWidth: "100%", background: C.panel, border: `1px solid ${C.border}`, borderRadius: 14, overflow: "hidden", boxShadow: "0 20px 64px rgba(0,0,0,0.5)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "12px 16px", borderBottom: `1px solid ${C.borderSoft}` }}>
          <PlayCircle size={17} color={C.cyan} />
          <span style={{ fontFamily: FD, fontWeight: 700, fontSize: 14.5, color: C.text }}>Her — demo walkthrough</span>
          <div style={{ flex: 1 }} />
          <X size={18} color={C.muted} style={{ cursor: "pointer" }} onClick={onClose} />
        </div>
        {err ? (
          <div style={{ fontFamily: FM, fontSize: 12.5, color: C.amber, padding: "40px 20px", textAlign: "center", lineHeight: 1.6 }}>
            The demo video isn’t available right now.
          </div>
        ) : (
          <video src="/api/demo-video" controls autoPlay playsInline onError={() => setErr(true)}
            style={{ display: "block", width: "100%", maxHeight: "78vh", background: "#000" }} />
        )}
      </div>
    </div>
  );
}

// "How do I get my sessions?" — where Claude Code stores them, how to drop one, and the
// one-shot uploader script that brings in ALL your projects (scan → scrub → upload).
function HelpModal({ onClose }) {
  // Point the download at THIS Space (server-reported SPACE_ID), so a visitor pulls
  // the copy whose DEFAULT_SPACE already self-references — never the author's Space.
  const api = useApi();
  const space = api?.space || "<owner>/<space>";
  const dl = `hf download ${space} scripts/her_upload.py --repo-type space --local-dir .`;
  const run = "python scripts/her_upload.py";
  const Code = ({ children }) => (
    <div style={{ fontFamily: FM, fontSize: 11.5, color: C.text, background: C.black, border: `1px solid ${C.borderSoft}`, borderRadius: 7, padding: "9px 11px", marginTop: 6, overflowX: "auto", whiteSpace: "pre" }}>{children}</div>
  );
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 100, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: 640, maxWidth: "100%", maxHeight: "86vh", overflowY: "auto", background: C.panel, border: `1px solid ${C.border}`, borderRadius: 14, padding: "22px 24px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
          <HelpCircle size={18} color={C.cyan} />
          <span style={{ fontFamily: FD, fontWeight: 700, fontSize: 17 }}>Get your Claude Code sessions in</span>
          <div style={{ flex: 1 }} />
          <X size={18} color={C.muted} style={{ cursor: "pointer" }} onClick={onClose} />
        </div>

        <div style={{ marginTop: 16, fontFamily: FD, fontWeight: 600, fontSize: 13.5, color: C.text }}>Where they live</div>
        <div style={{ fontFamily: FM, fontSize: 12, color: C.muted, marginTop: 4, lineHeight: 1.6 }}>
          Claude Code writes one <code style={{ color: C.text2 }}>.jsonl</code> per session under
          <Code>~/.claude/projects/&lt;encoded-folder&gt;/&lt;session-id&gt;.jsonl</Code>
        </div>

        <div style={{ marginTop: 16, fontFamily: FD, fontWeight: 600, fontSize: 13.5, color: C.text }}>Quickest: drag one in</div>
        <div style={{ fontFamily: FM, fontSize: 12, color: C.muted, marginTop: 4, lineHeight: 1.6 }}>
          Open that folder, grab any <code style={{ color: C.text2 }}>.jsonl</code>, and <b style={{ color: C.text2 }}>drag it onto this page</b> (or use “Upload .jsonl”). Drop several at once to build a Projects view.
        </div>

        <div style={{ marginTop: 16, fontFamily: FD, fontWeight: 600, fontSize: 13.5, color: C.text }}>All your projects at once (recommended)</div>
        <div style={{ fontFamily: FM, fontSize: 12, color: C.muted, marginTop: 4, lineHeight: 1.6 }}>
          Run our uploader — it <b style={{ color: C.text2 }}>copies</b> the sessions you pick into a staging folder, <b style={{ color: C.text2 }}>scrubs secrets</b>,
          and <b style={{ color: C.text2 }}>uploads</b> them — each step waits for your approval. Then it prints a link that opens your Projects view here.
          <Code>{dl}{"\n"}{run}</Code>
        </div>
        <div style={{ fontFamily: FM, fontSize: 11, color: C.muted, marginTop: 8, lineHeight: 1.6 }}>
          (You can also grab <code style={{ color: C.text2 }}>scripts/her_upload.py</code> from this Space’s <b style={{ color: C.text2 }}>Files</b> tab.)
        </div>

        <div style={{ marginTop: 16, display: "flex", alignItems: "center", gap: 8, fontFamily: FM, fontSize: 11.5, color: C.cyan, background: `${C.cyan}11`, border: `1px solid ${C.cyan}33`, borderRadius: 8, padding: "10px 12px" }}>
          <Trash2 size={14} /> Your uploads are private to your browser and auto-deleted after 24 hours (or instantly with “clear my data”, or when you close the tab).
        </div>
      </div>
    </div>
  );
}
