// =============================================================
// page-research.jsx — Private research surface at /#/research
//
// Token-gated workbench for ad-hoc public-records research on any
// company that files with the SEC. Type a name, hit lookup, get a
// Haiku-synthesized dossier with material events, classified signals,
// and watch points. Saves every dossier to the research_dossiers
// table for return visits.
//
// Auth: a Bearer token (RESEARCH_TOKEN env var on Vercel side) is
// stored in localStorage on first visit. Without the token the
// /api/research/dossier endpoint 403s.
// =============================================================

const {
  useState: useStateRS,
  useEffect: useEffectRS,
} = React;

const RESEARCH_TOKEN_KEY = "sc.research.token.v1";

function ResearchPage({ onNav }) {
  const [token, setToken] = useStateRS(() => {
    try { return localStorage.getItem(RESEARCH_TOKEN_KEY) || ""; } catch { return ""; }
  });
  const [tokenInput, setTokenInput] = useStateRS("");
  const [company, setCompany] = useStateRS("");
  const [loading, setLoading] = useStateRS(false);
  const [result, setResult] = useStateRS(null);
  const [err, setErr] = useStateRS("");
  const [recent, setRecent] = useStateRS([]);
  // CourtListener federal-court search — separate state, same token + company
  const [courtLoading, setCourtLoading] = useStateRS(false);
  const [courtResult, setCourtResult] = useStateRS(null);
  const [courtErr, setCourtErr] = useStateRS("");

  // Pull recent dossiers from the DB on mount, if we have a token
  useEffectRS(() => {
    if (!token || !window.SI_DB || !window.SI_DB.raw) return;
    let alive = true;
    (async () => {
      try {
        const rows = await window.SI_DB.raw.select(
          "research_dossiers",
          "select=id,company,filer_name,generated_at&order=generated_at.desc&limit=12"
        ).catch(() => []);
        if (alive) setRecent(Array.isArray(rows) ? rows : []);
      } catch {}
    })();
    return () => { alive = false; };
  }, [token]);

  function saveToken(e) {
    e.preventDefault();
    // Strip surrounding whitespace AND any wrapping quotes a user might
    // have copy-pasted along with the token.
    const cleaned = tokenInput.trim().replace(/^["']|["']$/g, "");
    if (!cleaned) return;
    try { localStorage.setItem(RESEARCH_TOKEN_KEY, cleaned); } catch {}
    setToken(cleaned);
  }

  function clearToken() {
    try { localStorage.removeItem(RESEARCH_TOKEN_KEY); } catch {}
    setToken("");
    setResult(null);
  }

  async function courtLookup(e) {
    if (e) e.preventDefault();
    if (!company.trim()) return;
    setCourtLoading(true); setCourtErr(""); setCourtResult(null);
    try {
      const r = await fetch("/api/research/courtlistener", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${token}`,
        },
        body: JSON.stringify({ company: company.trim(), days: 365 * 3 }),
      });
      if (r.status === 403) {
        const j = await r.json().catch(() => ({}));
        setCourtErr(`Token rejected. ${j?.hint || ""}`);
        return;
      }
      if (!r.ok) {
        const t = await r.text();
        setCourtErr(`Court search failed (${r.status}): ${t.slice(0, 200)}`);
        return;
      }
      const j = await r.json();
      setCourtResult(j);
    } catch (e2) {
      setCourtErr(`Network error: ${e2?.message || e2}`);
    } finally {
      setCourtLoading(false);
    }
  }

  async function lookup(e) {
    if (e) e.preventDefault();
    if (!company.trim()) return;
    setLoading(true); setErr(""); setResult(null);
    try {
      const r = await fetch("/api/research/dossier", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${token}`,
        },
        body: JSON.stringify({ company: company.trim() }),
      });
      if (r.status === 403) {
        // Surface the API's diagnostic so we can tell whether the env
        // var is missing vs the token mismatches vs whitespace.
        const j = await r.json().catch(() => ({}));
        const hint = j?.hint || "";
        setErr(`Token rejected. ${hint} (env set: ${j?.env_token_set ? "yes" : "NO"} · expected len: ${j?.env_token_len ?? "?"} · sent len: ${j?.sent_token_len ?? "?"})`);
        // Don't auto-clear the token — let the user see the hint and decide.
        return;
      }
      if (!r.ok) {
        const t = await r.text();
        setErr(`Request failed (${r.status}): ${t.slice(0, 200)}`);
        return;
      }
      const j = await r.json();
      setResult(j);
    } catch (e2) {
      setErr(`Network error: ${e2?.message || e2}`);
    } finally {
      setLoading(false);
    }
  }

  // ----- Token gate -------------------------------------------------------
  if (!token) {
    return (
      <div className="page-research">
        <style>{`
          .rs-gate { max-width: 540px; margin: 80px auto; padding: 32px;
            background: #fff; border: 1px solid var(--rule); border-radius: 10px; }
          .rs-h { font-family: var(--font-serif); font-size: 28px; margin: 0 0 8px; letter-spacing: -0.02em; font-weight: 600; }
          .rs-sub { color: var(--ink-soft); font-size: 14px; line-height: 1.55; margin: 0 0 18px; }
          .rs-input { width: 100%; box-sizing: border-box;
            padding: 12px 14px; font: inherit; font-size: 14px;
            border: 1px solid var(--rule); border-radius: 6px; background: #fff; }
          .rs-input:focus { outline: none; border-color: var(--ink); }
          .rs-btn { margin-top: 12px; padding: 12px 22px;
            background: var(--ink); color: var(--paper); border: 1px solid var(--ink);
            border-radius: 6px; font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; }
        `}</style>
        <div className="rs-gate">
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--ink-soft)", marginBottom: 10 }}>Private workbench</div>
          <h1 className="rs-h">Research dossier · token required</h1>
          <p className="rs-sub">
            This surface is gated by a research token. Paste it below to unlock — the token
            stays in your browser only.
          </p>
          <form onSubmit={saveToken}>
            <input className="rs-input" type="password" autoFocus
              placeholder="research token" value={tokenInput}
              onChange={e => setTokenInput(e.target.value)} />
            <button className="rs-btn" type="submit">Unlock</button>
          </form>
        </div>
      </div>
    );
  }

  // ----- Workbench --------------------------------------------------------
  return (
    <div className="page-research">
      <style>{`
        .rs-page { max-width: 1200px; margin: 0 auto; padding: 32px 24px; }
        .rs-eyebrow { font-family: var(--font-mono); font-size: 11px;
          letter-spacing: 0.32em; text-transform: uppercase; color: var(--ink-soft);
          margin-bottom: 14px; display: inline-flex; align-items: center; gap: 10px; }
        .rs-eyebrow .dot { width: 8px; height: 8px; border-radius: 50%;
          background: oklch(0.55 0.18 145); }
        .rs-h1 { font-family: var(--font-serif); font-size: 40px; line-height: 1.05;
          letter-spacing: -0.025em; margin: 0 0 12px; font-weight: 600; }
        @media (max-width: 700px) { .rs-h1 { font-size: 28px; } }
        .rs-dek { color: var(--ink-soft); font-size: 16px; line-height: 1.55;
          margin: 0; max-width: 720px; }

        .rs-form { display: flex; gap: 10px; margin-top: 28px; max-width: 760px; flex-wrap: wrap; }
        .rs-input-big { flex: 1; min-width: 280px; padding: 14px 18px;
          font: inherit; font-size: 16px; border: 1px solid var(--rule);
          border-radius: 8px; background: #fff; color: var(--ink); }
        .rs-input-big:focus { outline: none; border-color: var(--ink); }
        .rs-btn-big { padding: 14px 24px; background: var(--ink); color: var(--paper);
          border: 1px solid var(--ink); border-radius: 8px; font: inherit;
          font-size: 14px; font-weight: 600; cursor: pointer; }
        .rs-btn-big:disabled { opacity: 0.55; cursor: wait; }

        .rs-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 24px; margin-top: 36px; }
        @media (max-width: 900px) { .rs-grid { grid-template-columns: 1fr; } }

        .rs-card { background: #fff; border: 1px solid var(--rule); border-radius: 10px;
          padding: 22px 24px; }
        .rs-card-h { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.18em;
          text-transform: uppercase; color: var(--ink-soft); margin: 0 0 12px;
          padding-bottom: 10px; border-bottom: 1px solid var(--rule); }

        .rs-headline { font-family: var(--font-serif); font-size: 22px;
          line-height: 1.25; letter-spacing: -0.01em; margin: 4px 0 12px; font-weight: 600; }
        .rs-tldr { background: var(--paper-2, #f5f1e8); padding: 12px 16px;
          border-left: 3px solid var(--ink); border-radius: 4px; margin: 0 0 18px;
          font-size: 14px; line-height: 1.55; color: var(--ink); }
        .rs-section-title { font-family: var(--font-serif); font-size: 17px;
          margin: 18px 0 6px; font-weight: 600; }
        .rs-section-body { font-size: 14px; line-height: 1.6; color: var(--ink); white-space: pre-wrap; }

        .rs-sigrow { padding: 9px 8px; margin: 0 -8px; border-bottom: 1px solid var(--rule-soft);
          font-size: 13px; line-height: 1.5; border-radius: 4px; }
        .rs-sigrow:last-child { border-bottom: none; }
        .rs-sigrow-link:hover { background: var(--paper-2, #f5f1e8); }
        .rs-sev {
          display: inline-block; padding: 1px 7px; border-radius: 3px;
          font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.08em;
          text-transform: uppercase; font-weight: 700; color: #fff; margin-right: 8px;
        }
        .rs-sev.critical { background: oklch(0.50 0.22 25); }
        .rs-sev.high     { background: oklch(0.55 0.18 25); }
        .rs-sev.moderate { background: oklch(0.55 0.16 80); }
        .rs-sev.info     { background: var(--ink-soft); }

        .rs-recent-row { display: flex; justify-content: space-between; align-items: baseline;
          padding: 8px 0; border-bottom: 1px solid var(--rule-soft); font-size: 13px; }
        .rs-recent-row:last-child { border-bottom: none; }
        .rs-recent-name { color: var(--ink); flex: 1; }
        .rs-recent-when { font-family: var(--font-mono); font-size: 10px; color: var(--ink-soft); }

        .rs-clear { margin-top: 10px; font-size: 11px; color: var(--ink-soft);
          background: transparent; border: none; cursor: pointer; padding: 0; text-decoration: underline; }
        .rs-err { color: oklch(0.55 0.18 25); font-size: 13px; margin-top: 14px; }
      `}</style>

      <section className="rs-page">
        <div className="rs-eyebrow"><span className="dot" /> Private workbench · token unlocked</div>
        <h1 className="rs-h1">Public-records research, on any company.</h1>
        <p className="rs-dek">
          Type any company name and choose a source. SEC EDGAR covers every public
          filer (10-Ks, 8-K material events, late filings, officer changes). Federal
          court records (RECAP / PACER mirror) cover EVERY company — including
          private brokers and small carriers EDGAR can't see — surfacing
          breach-of-contract suits, default judgments, cargo claims, and
          bankruptcy filings. Both run the same Haiku synthesis.
        </p>

        <form onSubmit={lookup} className="rs-form">
          <input className="rs-input-big" autoFocus
            placeholder="Company name or ticker (e.g. Hub Group, FDX, Forward Air)"
            value={company} onChange={e => setCompany(e.target.value)} />
          <button className="rs-btn-big" type="submit" disabled={loading || !company.trim()}>
            {loading ? "Synthesizing…" : "Generate SEC dossier"}
          </button>
          <button className="rs-btn-big" type="button"
                  style={{ background: "var(--paper)", color: "var(--ink)", border: "1px solid var(--ink)" }}
                  disabled={courtLoading || !company.trim()}
                  onClick={courtLookup}
                  title="Search every federal court document (RECAP archive) mentioning this company">
            {courtLoading ? "Searching…" : "Search federal courts"}
          </button>
        </form>
        {err && <div className="rs-err">{err}</div>}
        {courtErr && <div className="rs-err">{courtErr}</div>}

        <div className="rs-grid">
          {/* Result panel */}
          <div>
            {result ? (
              <div className="rs-card">
                <div className="rs-card-h">
                  Dossier · {result.filer?.name || result.company}
                  {result.filer?.cik && <span style={{ marginLeft: 12, color: "var(--ink-soft)", fontWeight: 400 }}>CIK {result.filer.cik}</span>}
                </div>

                <h2 className="rs-headline">{result.dossier?.headline || "(no headline returned)"}</h2>
                {result.dossier?.tldr && (
                  <div className="rs-tldr"><strong>TL;DR · </strong>{result.dossier.tldr}</div>
                )}

                {(result.dossier?.sections || []).map((s, i) => (
                  <div key={i}>
                    <div className="rs-section-title">{s.title}</div>
                    <div className="rs-section-body">{s.body}</div>
                  </div>
                ))}

                {result.dossier?.error && (
                  <div style={{ background: "oklch(0.97 0.04 25)", border: "1px solid oklch(0.85 0.10 25)", padding: 12, borderRadius: 6, fontSize: 13, marginTop: 12 }}>
                    Haiku returned non-JSON. Raw output: <pre style={{ whiteSpace: "pre-wrap", fontSize: 11 }}>{result.dossier.raw}</pre>
                  </div>
                )}

                {result.signals?.length > 0 && (
                  <>
                    <div className="rs-section-title" style={{ marginTop: 28 }}>Classified signals · click any row to open the filing on SEC.gov</div>
                    {result.signals.slice(0, 24).map((s, i) => {
                      // Prefer the primary document URL (actual 8-K HTML)
                      // over the filing index page. The index page lists
                      // every XBRL companion file (.xsd schema, _lab.xml,
                      // _pre.xml, _cal.xml, _def.xml) and the user can
                      // dead-end on a raw schema if they pick wrong.
                      // Falls back to the index when primaryDoc is missing.
                      const cik = result.filer?.cik || "";
                      const cikNum = cik ? String(parseInt(cik, 10)) : "";
                      const accNoDash = s.accession ? s.accession.replace(/-/g, "") : "";
                      const url = (s.primaryDoc && cikNum && accNoDash)
                        ? `https://www.sec.gov/Archives/edgar/data/${cikNum}/${accNoDash}/${s.primaryDoc}`
                        : (s.accession && cikNum)
                        ? `https://www.sec.gov/Archives/edgar/data/${cikNum}/${accNoDash}/${s.accession}-index.htm`
                        : null;
                      const Row = url ? "a" : "div";
                      const props = url ? {
                        href: url, target: "_blank", rel: "noopener noreferrer",
                        style: { display: "block", textDecoration: "none", color: "inherit", cursor: "pointer" },
                      } : {};
                      return (
                        <Row key={i} className="rs-sigrow rs-sigrow-link" {...props}>
                          <span className={`rs-sev ${s.severity}`}>{s.severity}</span>
                          <strong>{s.date}</strong> · {s.form}
                          {s.item && <span style={{ color: "var(--ink-soft)", fontFamily: "var(--font-mono)", fontSize: 11, marginLeft: 6 }}>Item {s.item}</span>}
                          <span style={{ marginLeft: 8 }}>{s.label}</span>
                          {url && <span style={{ marginLeft: 10, fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--ink-soft)" }}>SEC.gov →</span>}
                        </Row>
                      );
                    })}
                  </>
                )}
              </div>
            ) : !loading ? (
              <div className="rs-card" style={{ textAlign: "center", color: "var(--ink-soft)", padding: 64 }}>
                Enter a company name above to start.
              </div>
            ) : (
              <div className="rs-card" style={{ textAlign: "center", color: "var(--ink-soft)", padding: 64 }}>
                Pulling SEC EDGAR + classifying signals + synthesizing dossier…
              </div>
            )}

            {/* Federal-court footprint card — appears below the EDGAR card
                when the user runs a court search. Reuses the same styling. */}
            {(courtResult || courtLoading) && (
              <div className="rs-card" style={{ marginTop: 16 }}>
                <div className="rs-card-h">
                  Federal court footprint · {courtResult?.company || company}
                  {courtResult?.hit_count != null && (
                    <span style={{ marginLeft: 12, color: "var(--ink-soft)", fontWeight: 400 }}>
                      {courtResult.hit_count} hit{courtResult.hit_count === 1 ? "" : "s"} in RECAP
                    </span>
                  )}
                </div>
                {courtLoading ? (
                  <div style={{ padding: 24, textAlign: "center", color: "var(--ink-soft)" }}>
                    Searching CourtListener RECAP archive + synthesizing report…
                  </div>
                ) : courtResult && (
                  <>
                    <h2 className="rs-headline">{courtResult.dossier?.headline || "(no headline returned)"}</h2>
                    {courtResult.dossier?.tldr && (
                      <div className="rs-tldr"><strong>TL;DR · </strong>{courtResult.dossier.tldr}</div>
                    )}
                    {(courtResult.dossier?.sections || []).map((s, i) => (
                      <div key={i}>
                        <div className="rs-section-title">{s.title}</div>
                        <div className="rs-section-body">{s.body}</div>
                      </div>
                    ))}
                    {courtResult.dossier?.error && (
                      <div style={{ background: "oklch(0.97 0.04 25)", border: "1px solid oklch(0.85 0.10 25)", padding: 12, borderRadius: 6, fontSize: 13, marginTop: 12 }}>
                        Haiku returned non-JSON. Raw output: <pre style={{ whiteSpace: "pre-wrap", fontSize: 11 }}>{courtResult.dossier.raw}</pre>
                      </div>
                    )}
                    {courtResult.hits?.length > 0 && (
                      <>
                        <div className="rs-section-title" style={{ marginTop: 28 }}>
                          Top matching dockets · click any row to open on CourtListener
                        </div>
                        {courtResult.hits.slice(0, 24).map((h, i) => {
                          const Row = h.docketUrl ? "a" : "div";
                          const props = h.docketUrl ? {
                            href: h.docketUrl, target: "_blank", rel: "noopener noreferrer",
                            style: { display: "block", textDecoration: "none", color: "inherit", cursor: "pointer" },
                          } : {};
                          return (
                            <Row key={i} className="rs-sigrow rs-sigrow-link" {...props}>
                              <span className={`rs-sev ${h.severity}`}>{h.severity}</span>
                              <strong>{(h.dateFiled || "").slice(0, 10)}</strong>
                              {" · "}{h.caseName}
                              {h.court && <span style={{ color: "var(--ink-soft)", fontFamily: "var(--font-mono)", fontSize: 11, marginLeft: 6 }}>{h.court}</span>}
                              {h.docketUrl && <span style={{ marginLeft: 10, fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--ink-soft)" }}>CourtListener →</span>}
                              {h.snippet && (
                                <div style={{ fontSize: 12, color: "var(--ink-soft)", lineHeight: 1.45, marginTop: 4 }}>
                                  …{h.snippet}…
                                </div>
                              )}
                            </Row>
                          );
                        })}
                      </>
                    )}
                  </>
                )}
              </div>
            )}
          </div>

          {/* Sidebar — recent dossiers + token controls */}
          <div>
            <div className="rs-card">
              <div className="rs-card-h">Recent dossiers</div>
              {recent.length === 0 ? (
                <div style={{ color: "var(--ink-soft)", fontSize: 13 }}>No dossiers yet — your first lookup will be saved here.</div>
              ) : (
                recent.map(r => (
                  <div key={r.id} className="rs-recent-row">
                    <span className="rs-recent-name">{r.filer_name || r.company}</span>
                    <span className="rs-recent-when">{(r.generated_at || "").slice(0, 10)}</span>
                  </div>
                ))
              )}
            </div>
            <div style={{ marginTop: 16 }}>
              <button className="rs-clear" onClick={clearToken}>Clear token from this browser</button>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
}

window.ResearchPage = ResearchPage;
