// Dashboard — primary post-login screen
const EP_COLORS = ["var(--green)", "var(--cyan)", "var(--amber)", "var(--violet)", "var(--red)"];

// Reveal stays on for this long, then collapses back to the masked form so
// a forgotten browser tab on a shared screen does not leave the key exposed.
const REVEAL_AUTO_HIDE_MS = 15000;

// The five GET endpoints + a tiny example query that's guaranteed to work
// against the top-100 allowlist. Used to render the "Your endpoints" card
// where every URL is a complete, paste-and-run example with the user's key.
const ENDPOINT_EXAMPLES = [
  { name: "expirations", desc: "All expiration dates for a ticker",
    query: "underlying=AAPL" },
  { name: "strikes",     desc: "All strike prices, optionally for one expiration",
    query: "underlying=AAPL&expiration=2026-06-18" },
  { name: "lookup",      desc: "Resolve a human-readable contract string",
    query: "q=AAPL%202026-06-18%20200%20call" },
  { name: "chain",       desc: "Full options chain for an expiration",
    query: "underlying=AAPL&expiration=2026-06-18" },
  { name: "quotes",      desc: "Bid / ask / last on a specific contract",
    query: "symbol=AAPL260601C00305000" },
];

/** One row of the "Your endpoints" card — full URL + a click-to-copy button. */
function EndpointUrlRow({ method, name, desc, url, hasKey }) {
  const [copied, setCopied] = useState(false);
  const copy = () => {
    copyToClipboard(url)
      .then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); })
      .catch(() => { /* fail silently — user can still select manually */ });
  };
  return (
    <div className="row" style={{
      alignItems: "flex-start", gap: 12, padding: "10px 0",
      borderTop: "1px solid var(--border-soft)",
    }}>
      <div style={{ minWidth: 86, paddingTop: 1 }}>
        <Tag kind="get">{method}</Tag>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div className="mono text-xs" style={{ color: "var(--text-dim)", marginBottom: 3 }}>
          /options/{name}
          <span style={{ marginLeft: 8, color: "var(--muted)", fontFamily: "var(--ff-sans)" }}>{desc}</span>
        </div>
        <code style={{
          display: "block", fontFamily: "var(--ff-mono)", fontSize: 12,
          color: "var(--text)", wordBreak: "break-all", lineHeight: 1.5,
          opacity: hasKey ? 1 : 0.7,
        }}>{url}</code>
      </div>
      <button className="icon-btn"
        title={copied ? "Copied" : "Copy URL"}
        onClick={copy}>
        {copied
          ? <Icon.Check style={{ width: 13, height: 13, color: "var(--green)" }} />
          : <Icon.Copy style={{ width: 13, height: 13 }} />}
      </button>
    </div>
  );
}

function Dashboard({ onNavigate }) {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [user, setUser] = useState(null);
  const [primaryKey, setPrimaryKey] = useState(null);
  const [usage, setUsage] = useState(null);
  const [isRevealed, setIsRevealed] = useState(false);
  const [copied, setCopied] = useState(false);
  const [keyNotice, setKeyNotice] = useState("");  // inline help / errors near the key card
  const [rotating, setRotating] = useState(false);
  const [verifyState, setVerifyState] = useState("");  // "" | "sending" | "sent" | "error: <msg>"
  const { guard, reauthModal } = useReauthGuard();

  // Auto-hide the revealed key after a short window. Cancels cleanly if the
  // user navigates away or hides the key manually first.
  useEffect(() => {
    if (!isRevealed) return undefined;
    const t = setTimeout(() => setIsRevealed(false), REVEAL_AUTO_HIDE_MS);
    return () => clearTimeout(t);
  }, [isRevealed]);

  // Hit the cache (10-60s TTLs) so navigating away and back is instant. The
  // initial visit pays the network round-trip; subsequent ones render from
  // the in-memory cache and refresh in the background.
  //
  // The mountedRef guards against setState after unmount — if the user
  // navigates away during the initial fetch, the resolution is ignored
  // instead of triggering a React warning and a stale render.
  const mountedRef = useRef(true);
  useEffect(() => () => { mountedRef.current = false; }, []);
  const load = () => {
    if (!user) setLoading(true);
    setError("");
    Promise.all([
      API.cached("/auth/me", 5 * 60 * 1000),  // user info — rarely changes
      API.cached("/keys", 30 * 1000),         // key list
      API.cached("/usage", 15 * 1000),        // usage counters
    ])
      .then((res) => {
        if (!mountedRef.current) return;
        setUser(res[0].user);
        const keys = res[1].keys || [];
        setPrimaryKey(keys.filter((k) => !k.revoked)[0] || keys[0] || null);
        setUsage(res[2]);
        setLoading(false);
      })
      .catch((err) => {
        if (!mountedRef.current) return;
        setError(err.message || "Could not load your dashboard.");
        setLoading(false);
      });
  };

  useEffect(load, []);

  if (loading) return <LoadingScreen crumbs={["Console", "Dashboard"]} />;
  if (error) return <ErrorScreen crumbs={["Console", "Dashboard"]} message={error} onRetry={load} />;

  const firstName = user && user.name
    ? user.name.split(" ")[0]
    : (user ? user.email.split("@")[0] : "there");
  const totals = usage.totals;
  const rl = usage.rate_limit;
  // `rl.limit === null` means the user is on an unlimited-daily tier (Pro or
  // a Free-tier user inside the 3-day trial). Used everywhere below to swap
  // the daily quota meter for an "unlimited daily" callout that points at
  // the per-minute burst as the real ceiling.
  const isUnlimitedDaily = rl.limit === null || rl.limit === undefined;
  // The plaintext key is stored alongside the masked fragments now, so reveal
  // and copy work for every key the server returns. A handful of legacy rows
  // (created during the brief hash-only window) have api_key = null — those
  // fall back to masked-only display with a "rotate" hint.
  const fullKey = primaryKey && typeof primaryKey.api_key === "string" ? primaryKey.api_key : "";
  const masked = primaryKey && primaryKey.key_masked
    ? primaryKey.key_masked
    : (primaryKey ? "sk_live_••••" : "—");
  const dailyTotals = sparkData((usage.daily || []).map((d) => d.total));
  const latencySeries = sparkData((usage.latency_daily || []).map((d) => d.p95));
  const rlPct = isUnlimitedDaily ? 0 : (rl.limit > 0 ? Math.min(100, Math.round((rl.used / rl.limit) * 100)) : 0);
  const epMax = Math.max(1, ...usage.by_endpoint.map((e) => e.count));

  // Trial banner — surfaces the 3-day Pro grace window for new accounts.
  // The server-side rate limiter already grants Pro daily_limit during this
  // window; the banner is purely informational + an "upgrade now" nudge.
  const trialMsRemaining = user && user.trial_ends_at
    ? new Date(user.trial_ends_at).getTime() - Date.now()
    : -1;
  const inTrial = trialMsRemaining > 0 && (user.plan === "free" || !user.plan);
  const trialDaysLeft = inTrial ? Math.max(1, Math.ceil(trialMsRemaining / 86400000)) : 0;

  // Email-verification banner — shown only when the server says the address
  // is still unverified. Lets the user resend the verification mail (the
  // first one was sent automatically at register time, but it may have
  // bounced / landed in spam, or SMTP was not configured yet then).
  const needsVerify = !!(user && user.email_verified === false);
  const resendVerification = () => {
    if (verifyState === "sending") return;
    setVerifyState("sending");
    API.sendVerification()
      .then(() => setVerifyState("sent"))
      .catch((err) => setVerifyState("error: " + ((err && err.message) || "could not send")));
  };

  // Origin + key fragment used to render the "Your endpoints" card. The
  // placeholder kicks in when the user has no plaintext key on record (a
  // brand-new account, or the rare legacy hash-only row) so the URLs are
  // still readable end-to-end.
  const apiBase = window.location.origin + "/api/v1";
  const exampleKey = fullKey || "YOUR_API_KEY";

  /**
   * Reveal button. When the row has plaintext (the common case for new and
   * rotated keys) it flips state — the masked form swaps for the real key,
   * the auto-hide effect collapses it back after REVEAL_AUTO_HIDE_MS.
   * When the row has no plaintext (the handful of legacy hash-only rows
   * left over from the migration window) the click shows an inline help
   * line pointing the user at Rotate instead of silently doing nothing.
   */
  const onReveal = () => {
    setKeyNotice("");
    if (!fullKey) {
      setKeyNotice("This key was issued before display was enabled. Click Rotate to get a fresh key you can copy.");
      return;
    }
    setIsRevealed((v) => !v);
  };

  /** Copy button — same fallback path as Reveal. */
  const copyKey = () => {
    setKeyNotice("");
    if (!fullKey) {
      setKeyNotice("This key was issued before display was enabled. Click Rotate to get a fresh key you can copy.");
      return;
    }
    copyToClipboard(fullKey)
      .then(() => {
        setCopied(true);
        setTimeout(() => setCopied(false), 1500);
      })
      .catch(() => {
        setKeyNotice("Could not copy automatically — select the key text above and copy manually.");
      });
  };

  const rotate = () => {
    if (!primaryKey || rotating) return;
    setRotating(true);
    API.post("/keys/" + primaryKey.id)
      .then((res) => {
        // Cached /keys is now stale; drop it so the Keys page reflects the
        // new value on the next visit instead of showing the previous one.
        API.invalidate("/keys");
        setPrimaryKey(res.key);
        setIsRevealed(true);
        setKeyNotice("");
        setRotating(false);
      })
      .catch((err) => {
        setError(err.message || "Could not rotate the key.");
        setRotating(false);
      });
  };

  // Each step is a (label, isDone) pair. Every "done" flag is derived from
  // real state we can observe — no hard-coded false / placeholder steps.
  const chainHits = (usage.by_endpoint || []).find((e) => e.endpoint === "options/chain");
  const steps = [
    ["Generate your first API key", !!primaryKey],
    ["Make your first call to the gateway", totals.total > 0],
    ["Authenticate a call with your key", totals.total > 0],
    ["Try the chain endpoint", !!(chainHits && chainHits.count > 0)],
    ["Review your usage analytics", totals.total > 10],
  ];
  const stepsDone = steps.filter((s) => s[1]).length;

  return (
    <>
      <TopBar crumbs={["Console", "Production", "Dashboard"]} />
      <div className="page">
        <div className="page-head row" style={{ alignItems: "flex-end", justifyContent: "space-between" }}>
          <div>
            <span className="eyebrow">Production environment</span>
            <h1 className="page-title">Welcome back, {firstName}.</h1>
            <p className="page-sub">
              {totals.total > 0
                ? "Your API is healthy. " + Fmt.num(totals.total) + " calls served all-time."
                : "Your account is ready. Send your first call to get started."}
            </p>
          </div>
          <div className="row gap-10">
            <button className="btn" onClick={() => onNavigate("docs")}>
              <Icon.Docs style={{ width: 14, height: 14 }} /> View docs
            </button>
            <button className="btn btn-primary" onClick={() => onNavigate("docs")}>
              <Icon.Bolt style={{ width: 14, height: 14 }} /> Test an endpoint
            </button>
          </div>
        </div>

        {/* Verify-your-email banner — shown only while email_verified=FALSE.
            The first verification mail is sent automatically at register
            time (fire-and-forget); this banner is the recovery surface for
            users whose first mail bounced or got filed in spam. */}
        {needsVerify && (
          <div className="card" style={{
            marginBottom: 20, padding: "12px 18px",
            border: "1px solid var(--amber)",
            background: "var(--amber-soft, rgba(245,158,11,0.08))",
            display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
          }}>
            <Icon.Shield style={{ width: 18, height: 18, color: "var(--amber)", flex: "0 0 18px" }} />
            <div style={{ flex: 1, minWidth: 220 }}>
              <div style={{ fontWeight: 500, fontSize: 14 }}>
                Verify your email
              </div>
              <div className="text-xs muted" style={{ marginTop: 2, lineHeight: 1.45 }}>
                {verifyState === "sent"
                  ? `A new verification link was sent to ${user.email}. Click it within 15 minutes.`
                  : verifyState.startsWith("error:")
                  ? verifyState.replace(/^error:\s*/, "")
                  : `We sent a verification link to ${user.email}. If you didn't receive it, check spam or resend.`}
              </div>
            </div>
            <button className="btn"
              onClick={resendVerification}
              disabled={verifyState === "sending" || verifyState === "sent"}>
              {verifyState === "sending" ? "Sending…" : verifyState === "sent" ? "Sent ✓" : "Resend email"}
            </button>
          </div>
        )}

        {/* Trial banner — shown only while the 3-day Pro trial window is
            active and the user has not yet subscribed to Pro. Server-side
            already grants Pro limits during this window; the banner is the
            visible "you're in trial, upgrade to keep it" signal. */}
        {inTrial && (
          <div className="card" style={{
            marginBottom: 20, padding: "14px 18px",
            border: "1px solid var(--green)",
            background: "var(--green-soft, rgba(63,185,80,0.08))",
            display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
          }}>
            <Icon.Bolt style={{ width: 18, height: 18, color: "var(--green)", flex: "0 0 18px" }} />
            <div style={{ flex: 1, minWidth: 220 }}>
              <div style={{ fontWeight: 500, fontSize: 14 }}>
                Pro trial — {trialDaysLeft} {trialDaysLeft === 1 ? "day" : "days"} remaining
              </div>
              <div className="text-xs muted" style={{ marginTop: 2, lineHeight: 1.45 }}>
                You have full Pro limits (10,000 credits / minute, unlimited daily) until
                {" "}{Fmt.date(user.trial_ends_at)}. No card needed during the trial.
              </div>
            </div>
            <button className="btn btn-primary" onClick={() => onNavigate("billing")}>
              Upgrade to keep Pro
            </button>
          </div>
        )}

        {/* API key hero card */}
        <div className="card" style={{ marginBottom: 20, position: "relative", overflow: "hidden" }}>
          <div style={{ position: "absolute", inset: 0, background: "radial-gradient(circle at 100% 0%, oklch(0.85 0.14 85 / 0.10), transparent 50%)", pointerEvents: "none" }} />
          <div className="card-body" style={{ position: "relative" }}>
            <div className="row" style={{ justifyContent: "space-between", marginBottom: 14 }}>
              <div>
                <div className="row gap-6">
                  <Tag kind="get">Live</Tag>
                  <Tag kind="muted">{primaryKey ? primaryKey.name : "No key"}</Tag>
                </div>
                <div style={{ fontSize: 18, fontWeight: 500, marginTop: 8, letterSpacing: "-0.01em" }}>Your active API key</div>
                <div className="mono text-xs muted" style={{ marginTop: 2 }}>
                  {primaryKey
                    ? "Created " + Fmt.date(primaryKey.created_at) + " · Last used " + Fmt.ago(primaryKey.last_used_at)
                    : "Create a key to start making calls"}
                </div>
              </div>
              <div className="row gap-6">
                <button className="btn" disabled={rotating || !primaryKey}
                  onClick={rotate}>
                  <Icon.Refresh style={{ width: 14, height: 14 }} /> {rotating ? "Rotating…" : "Rotate"}
                </button>
                <button className="btn" onClick={() => onNavigate("keys")}>
                  <Icon.Plus style={{ width: 14, height: 14 }} /> New key
                </button>
              </div>
            </div>
            <div className="row gap-10" style={{ background: "oklch(0.13 0.011 240)", border: "1px solid var(--border-soft)", borderRadius: 8, padding: "12px 14px" }}>
              <span style={{ color: "var(--amber)" }}>
                <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.4">
                  <rect x="3.5" y="7" width="9" height="6.5" rx="1" />
                  <path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2" />
                </svg>
              </span>
              <code style={{ flex: 1, fontFamily: "var(--ff-mono)", fontSize: 13.5, color: "var(--text)", letterSpacing: "0.02em", wordBreak: "break-all" }}>
                {isRevealed && fullKey ? fullKey : masked}
              </code>
              {/* Reveal + copy are always clickable when there's a primary
                  key. If the row has plaintext they do what they say; if it
                  is a legacy hash-only row, they post an inline help line
                  pointing the user at Rotate — so the buttons never feel
                  silently broken. */}
              <button className="icon-btn"
                onClick={onReveal}
                title={fullKey ? (isRevealed ? "Hide" : "Reveal") : "Rotate to issue a new visible key"}
                disabled={!primaryKey}>
                {isRevealed
                  ? <Icon.EyeOff style={{ width: 14, height: 14 }} />
                  : <Icon.Eye style={{ width: 14, height: 14 }} />}
              </button>
              <button className="icon-btn"
                onClick={copyKey}
                title={fullKey ? "Copy" : "Rotate to issue a new visible key"}
                disabled={!primaryKey}>
                {copied
                  ? <Icon.Check style={{ width: 14, height: 14, color: "var(--green)" }} />
                  : <Icon.Copy style={{ width: 14, height: 14 }} />}
              </button>
            </div>
            <div className="row mono text-xs muted" style={{ marginTop: 12, gap: 16, flexWrap: "wrap" }}>
              <span>
                <span style={{ color: primaryKey && !primaryKey.revoked ? "var(--green)" : "var(--red)" }}>●</span>
                {" "}{primaryKey && !primaryKey.revoked ? "Active" : "Revoked"}
              </span>
              <span style={{ color: "var(--faint)" }}>|</span>
              <span>Plan: {user && user.plan ? user.plan : "free"}</span>
              <span style={{ color: "var(--faint)" }}>|</span>
              <span>Quota: {Fmt.num(rl.used)} / {isUnlimitedDaily ? "unlimited" : Fmt.num(rl.limit)} credits today</span>
            </div>
            {/* Inline status line under the key — covers three cases:
                 - revealed: countdown reminder so the user knows it'll hide
                 - copy ok: green confirmation
                 - any keyNotice (legacy-key help, copy failure, etc.) */}
            {(isRevealed || copied || keyNotice) && (
              <div className="mono text-xs"
                style={{
                  marginTop: 10, lineHeight: 1.5,
                  color: keyNotice ? "var(--amber)" : copied ? "var(--green)" : "var(--muted)",
                }}>
                {copied && "Copied to clipboard."}
                {!copied && isRevealed && !keyNotice && `Hidden automatically in ${Math.round(REVEAL_AUTO_HIDE_MS / 1000)} seconds — copy it now if you need it.`}
                {keyNotice}
              </div>
            )}
          </div>
        </div>

        {/* Your endpoints — complete, paste-and-run URLs */}
        <div className="card" style={{ marginBottom: 20 }}>
          <div className="card-head">
            <span>Your endpoints</span>
            <span className="mono text-xs muted" style={{ marginLeft: "auto" }}>
              5 GET endpoints · click to copy
            </span>
          </div>
          <div className="card-body" style={{ paddingTop: 6 }}>
            <p className="text-sm muted" style={{ margin: "0 0 6px" }}>
              Every URL below is complete — paste it into a browser tab, curl,
              or any HTTP client and it will run against your account
              {fullKey ? " using your live key." : " (your key is inserted automatically once it's visible — rotate to issue a new one)."}
            </p>
            {ENDPOINT_EXAMPLES.map((ep) => (
              <EndpointUrlRow
                key={ep.name}
                method="GET"
                name={ep.name}
                desc={ep.desc}
                hasKey={!!fullKey}
                url={`${apiBase}/options/${ep.name}?${ep.query}&apiKey=${exampleKey}`}
              />
            ))}
            <div className="mono text-xs muted" style={{ marginTop: 12 }}>
              Need code samples (curl / node / python)? See{" "}
              <span style={{ color: "var(--green)", cursor: "pointer" }} onClick={() => onNavigate("docs")}>
                API Reference →
              </span>
            </div>
          </div>
        </div>

        {/* Stats row */}
        <div className="grid g-4" style={{ marginBottom: 20 }}>
          <StatTile label="Calls · today" value={Fmt.num(totals.today)}
            trend={isUnlimitedDaily ? "Unlimited daily · 10,000/min cap" : Fmt.num(rl.remaining) + " credits left today"} tone="up" data={dailyTotals} />
          <StatTile label="Calls · this month" value={Fmt.num(totals.month)}
            trend={Fmt.num(totals.total) + " all-time"} tone="flat" data={dailyTotals} />
          <StatTile label="p95 latency" value={totals.p95_latency_ms + "ms"}
            trend={totals.avg_latency_ms + "ms average"} tone="up" data={latencySeries} />
          <StatTile label="Error rate" value={(totals.error_rate * 100).toFixed(2) + "%"}
            trend={Fmt.num(totals.errors) + " errors all-time"} tone={totals.error_rate > 0.05 ? "down" : "flat"} data={dailyTotals} />
        </div>

        {/* Two-up: rate limit + recent requests */}
        <div className="grid g-2" style={{ gridTemplateColumns: "1.1fr 1.4fr" }}>
          <div className="card">
            <div className="card-head">
              <span>Rate limit · today</span>
              <span className="mono text-xs muted" style={{ marginLeft: "auto" }}>resets {rl.reset_date}</span>
            </div>
            <div className="card-body">
              {isUnlimitedDaily ? (
                // Pro / trial — no daily ceiling. Show today's count as an
                // informational number and surface the per-minute ceiling
                // (10,000/min) which is the actual cap that can be hit.
                <>
                  <div className="row" style={{ alignItems: "baseline", gap: 6, marginBottom: 6 }}>
                    <span className="mono" style={{ fontSize: 32, letterSpacing: "-0.02em", fontWeight: 500 }}>{Fmt.num(rl.used)}</span>
                    <span className="mono text-xs muted">credits today · unlimited daily</span>
                    <span style={{ marginLeft: "auto" }} className="status-pill">
                      <span className="dot" />
                      Pro tier
                    </span>
                  </div>
                  <div className="text-xs muted" style={{ marginTop: 4, lineHeight: 1.5 }}>
                    Pro has no daily cap. The active ceiling is the
                    {" "}<span className="mono" style={{ color: "var(--green)" }}>10,000 credits / minute</span>{" "}
                    burst limit — exceed it and the gateway returns
                    {" "}<code className="mono">429 burst_limited</code>{" "}
                    with a 60-second <code className="mono">Retry-After</code>.
                  </div>
                </>
              ) : (
                <>
                  <div className="row" style={{ alignItems: "baseline", gap: 6, marginBottom: 6 }}>
                    <span className="mono" style={{ fontSize: 32, letterSpacing: "-0.02em", fontWeight: 500 }}>{Fmt.num(rl.used)}</span>
                    <span className="mono text-xs muted">/ {Fmt.num(rl.limit)} credits / day</span>
                    <span style={{ marginLeft: "auto" }} className="status-pill">
                      <span className={"dot " + (rlPct >= 90 ? "red" : rlPct >= 70 ? "amber" : "")} />
                      {rlPct >= 100 ? "throttled" : rlPct >= 70 ? "near limit" : "healthy"}
                    </span>
                  </div>
                  <div className="meter"><i style={{ width: rlPct + "%" }} /></div>
                  <div className="row mono text-xs muted" style={{ marginTop: 8, justifyContent: "space-between" }}>
                    <span>0</span><span>{Fmt.num(Math.round(rl.limit / 2))}</span><span>{Fmt.num(rl.limit)} hard cap</span>
                  </div>
                  <div className="text-xs muted" style={{ marginTop: 8, lineHeight: 1.5 }}>
                    Free tier also has a <span className="mono">60 credits / minute</span> burst limit.
                    Upgrade to Pro for unlimited daily + 10,000 / minute.
                  </div>
                </>
              )}

              <div className="divider" />

              <div className="mono text-xs muted" style={{ marginBottom: 10 }}>BY ENDPOINT — all-time</div>
              {usage.by_endpoint.length === 0 && (
                <div className="text-sm muted" style={{ padding: "8px 0" }}>No calls yet.</div>
              )}
              {usage.by_endpoint.map((r, i) => (
                <div key={r.endpoint} style={{ marginBottom: 10 }}>
                  <div className="row" style={{ justifyContent: "space-between", marginBottom: 4, fontSize: 12.5 }}>
                    <span className="mono">GET /{r.endpoint}</span>
                    <span className="mono muted">{Fmt.num(r.count)} req</span>
                  </div>
                  <div className="meter">
                    <i style={{ width: ((r.count / epMax) * 100) + "%", background: EP_COLORS[i % EP_COLORS.length] }} />
                  </div>
                </div>
              ))}
            </div>
          </div>

          <div className="card">
            <div className="card-head">
              <span>Recent calls</span>
              <button className="btn btn-ghost text-xs mono" style={{ marginLeft: "auto", padding: "4px 8px" }} onClick={load}>
                Refresh
              </button>
            </div>
            <div className="scroll-x">
              <table className="tbl">
                <thead>
                  <tr>
                    <th>Time</th>
                    <th>Method</th>
                    <th>Endpoint</th>
                    <th>Status</th>
                    <th>Latency</th>
                  </tr>
                </thead>
                <tbody>
                  {usage.recent.length === 0 && (
                    <tr><td colSpan="5" className="text-sm muted" style={{ padding: "16px 12px" }}>No calls yet — try the API Reference.</td></tr>
                  )}
                  {usage.recent.map((r, i) => (
                    <tr key={i}>
                      <td className="mono text-xs muted">{Fmt.time(r.time)}</td>
                      <td><Tag kind="get">{r.method}</Tag></td>
                      <td className="mono text-xs" style={{ color: "var(--text-dim)" }}>/v1/{r.endpoint}</td>
                      <td>
                        <span className="mono text-xs" style={{
                          color: r.status_code < 300 ? "var(--green)" : r.status_code < 500 ? "var(--amber)" : "var(--red)",
                        }}>{r.status_code}</span>
                      </td>
                      <td className="mono text-xs muted">{r.latency_ms == null ? "—" : r.latency_ms + "ms"}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </div>

        {/* Onboarding checklist */}
        <div style={{ marginTop: 20 }}>
          <div className="card">
            <div className="card-head">
              <span>Quickstart</span>
              <span className="mono text-xs muted" style={{ marginLeft: "auto" }}>{stepsDone} of {steps.length} complete</span>
            </div>
            <div className="card-body" style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              {steps.map((t, i) => (
                <div key={i} className="row" style={{ gap: 12 }}>
                  <span style={{
                    width: 22, height: 22, borderRadius: "50%",
                    border: `1.5px solid ${t[1] ? "var(--green)" : "var(--border)"}`,
                    background: t[1] ? "var(--green-soft)" : "transparent",
                    color: "var(--green)", display: "grid", placeItems: "center",
                  }}>
                    {t[1] && <Icon.Check style={{ width: 12, height: 12 }} />}
                  </span>
                  <span style={{ flex: 1, fontSize: 13.5, color: t[1] ? "var(--muted)" : "var(--text)", textDecoration: t[1] ? "line-through" : "none" }}>
                    {t[0]}
                  </span>
                  {!t[1] && <button className="btn text-xs mono" style={{ padding: "4px 10px" }} onClick={() => onNavigate("docs")}>Start</button>}
                </div>
              ))}
            </div>
          </div>

        </div>
      </div>
      {reauthModal}
    </>
  );
}

function StatTile({ label, value, trend, tone = "up", data }) {
  return (
    <div className="stat">
      <div className="row" style={{ justifyContent: "space-between", alignItems: "flex-start" }}>
        <span className="label">{label}</span>
      </div>
      <div className="value">{value}</div>
      <div className={`trend ${tone}`}>
        {tone === "up" && <span style={{ color: "var(--green)" }}>▲</span>}
        {tone === "down" && <span style={{ color: "var(--red)" }}>▼</span>}
        {trend}
      </div>
      <div style={{ marginTop: 12, marginBottom: -4 }}>
        <Sparkline data={data} color={tone === "down" ? "var(--red)" : "var(--green)"} />
      </div>
    </div>
  );
}

Object.assign(window, { Dashboard });
