// API Documentation — 5 options endpoints with sidebar nav + code panel.
// Examples below are the REAL responses the Market-Options gateway returns —
// a columnar JSON format.
const ENDPOINTS = [
  {
    id: "expirations",
    method: "GET",
    name: "Expirations",
    path: "/v1/options/expirations",
    summary: "List every expiration date that currently has listed options for an underlying.",
    description: "Returns an ordered array of ISO-8601 expiration dates for the underlying's option chain, plus a Unix timestamp of when the data was last refreshed.",
    params: [
      { name: "underlying", type: "string", req: true, desc: "Ticker symbol of the underlying. e.g. AAPL." },
      { name: "strike", type: "number", req: false, desc: "Only return expirations that list this strike price." },
      { name: "date", type: "ISO-8601 date", req: false, desc: "Return the expirations that were available on a given historical date." },
    ],
    response: `{
  "s": "ok",
  "expirations": [
    "2026-05-22",
    "2026-05-26",
    "2026-06-19",
    "2026-09-18",
    "2026-12-18",
    "2027-06-17"
  ],
  "updated": 1779393600
}`,
    curl: `curl "${window.location.origin}/api/v1/options/expirations?underlying=AAPL&apiKey=sk_live_•••"`,
    js: `const KEY = process.env.MARKET_OPTIONS_KEY;
const res = await fetch(
  "${window.location.origin}/api/v1/options/expirations?underlying=AAPL&apiKey=" + KEY
);
const data = await res.json();
console.log(data.expirations);`,
    py: `import os, requests

res = requests.get(
    "${window.location.origin}/api/v1/options/expirations",
    params={"underlying": "AAPL", "apiKey": os.environ["MARKET_OPTIONS_KEY"]},
)
print(res.json()["expirations"])`,
  },
  {
    id: "strikes",
    method: "GET",
    name: "Strikes",
    path: "/v1/options/strikes",
    summary: "List the strike prices available for an underlying's option chain.",
    description: "Returns the set of strike prices, grouped by expiration. Useful to render strike pickers and to validate a strike before requesting /chain or /quotes.",
    params: [
      { name: "underlying", type: "string", req: true, desc: "Ticker symbol of the underlying. e.g. AAPL." },
      { name: "expiration", type: "ISO-8601 date", req: false, desc: "Only return strikes for this single expiration." },
      { name: "date", type: "ISO-8601 date", req: false, desc: "Return the strikes that were available on a given historical date." },
    ],
    response: `{
  "s": "ok",
  "updated": 1779393600,
  "2026-06-19": [
    150, 155, 160, 165, 170, 175, 180,
    185, 190, 195, 200, 205, 210, 215,
    220, 225, 230, 235, 240, 245, 250
  ]
}`,
    curl: `curl "${window.location.origin}/api/v1/options/strikes?underlying=AAPL&expiration=2026-06-19&apiKey=sk_live_•••"`,
    js: `const KEY = process.env.MARKET_OPTIONS_KEY;
const res = await fetch(
  "${window.location.origin}/api/v1/options/strikes" +
    "?underlying=AAPL&expiration=2026-06-19&apiKey=" + KEY
);
const data = await res.json();
console.log(data["2026-06-19"]); // → [150, 155, 160, ...]`,
    py: `import os, requests

res = requests.get(
    "${window.location.origin}/api/v1/options/strikes",
    params={
        "underlying": "AAPL",
        "expiration": "2026-06-19",
        "apiKey": os.environ["MARKET_OPTIONS_KEY"],
    },
)
data = res.json()
print(data["2026-06-19"])  # -> [150, 155, 160, ...]`,
  },
  {
    id: "lookup",
    method: "GET",
    name: "Lookup",
    path: "/v1/options/lookup",
    summary: "Resolve a plain-language option description into its canonical OSI symbol.",
    description: "Accepts a human-readable description of an option contract and returns the standard 21-character OSI option symbol. Use it to turn user input into a symbol you can pass to /quotes.",
    params: [
      { name: "q", type: "string", req: true, desc: "The option to resolve. e.g. \"AAPL 2026-06-19 200 call\"." },
    ],
    response: `{
  "s": "ok",
  "optionSymbol": "AAPL260618C00200000"
}`,
    curl: `curl "${window.location.origin}/api/v1/options/lookup?q=AAPL%202026-06-19%20200%20call&apiKey=sk_live_•••"`,
    js: `const KEY = process.env.MARKET_OPTIONS_KEY;
const q = encodeURIComponent("AAPL 2026-06-19 200 call");
const res = await fetch(
  "${window.location.origin}/api/v1/options/lookup?q=" + q + "&apiKey=" + KEY
);
const data = await res.json();
console.log(data.optionSymbol); // → AAPL260618C00200000`,
    py: `import os, requests

res = requests.get(
    "${window.location.origin}/api/v1/options/lookup",
    params={"q": "AAPL 2026-06-19 200 call", "apiKey": os.environ["MARKET_OPTIONS_KEY"]},
)
print(res.json()["optionSymbol"])  # -> AAPL260618C00200000`,
  },
  {
    id: "chain",
    method: "GET",
    name: "Chain",
    path: "/v1/options/chain",
    summary: "Full option chain for an underlying — every contract with quotes and greeks.",
    description: "Returns the chain in a columnar layout: every field is a parallel array, and the values at index i across all arrays describe one contract. Narrow the result with the filters below.",
    params: [
      { name: "underlying", type: "string", req: true, desc: "Underlying ticker. e.g. AAPL." },
      { name: "expiration", type: "ISO-8601 date", req: false, desc: "Limit to a single expiration. Use /expirations to list available dates." },
      { name: "from", type: "ISO-8601 date", req: false, desc: "Lower bound on expiration date." },
      { name: "to", type: "ISO-8601 date", req: false, desc: "Upper bound on expiration date." },
      { name: "dte", type: "string", req: false, desc: "Days-to-expiration filter. Accepts a single value, range, list, or comparison — e.g. 30, 30-60, >=30." },
      { name: "month", type: "integer (1-12)", req: false, desc: "Limit to expirations in this calendar month." },
      { name: "year", type: "integer", req: false, desc: "Limit to expirations in this calendar year." },
      { name: "weekly", type: "bool", req: false, desc: "true = weekly expirations only, false = exclude weeklies." },
      { name: "monthly", type: "bool", req: false, desc: "true = monthly expirations only." },
      { name: "quarterly", type: "bool", req: false, desc: "true = quarterly expirations only." },
      { name: "nonstandard", type: "bool", req: false, desc: "true = include non-standard expirations (e.g. adjusted contracts)." },
      { name: "side", type: "enum", req: false, desc: "Filter to call or put. Returns both when omitted." },
      { name: "strike", type: "strike expression", req: false, desc: "A single strike (400), list (400,410,420), range (400-410), or comparison (>=400)." },
      { name: "strikeLimit", type: "integer (1-500)", req: false, desc: "Return only N strikes nearest the money." },
      { name: "range", type: "enum", req: false, desc: "Filter by moneyness: itm, otm, atm, etm, all, otmin, itmout." },
      { name: "delta", type: "string", req: false, desc: "Delta filter. Accepts value, range, list, or comparison — e.g. .30, .30-.60." },
      { name: "minBid", type: "number", req: false, desc: "Exclude contracts whose bid is below this value." },
      { name: "maxBid", type: "number", req: false, desc: "Exclude contracts whose bid is above this value." },
      { name: "minAsk", type: "number", req: false, desc: "Exclude contracts whose ask is below this value." },
      { name: "maxAsk", type: "number", req: false, desc: "Exclude contracts whose ask is above this value." },
      { name: "maxBidAskSpread", type: "number", req: false, desc: "Exclude contracts whose bid/ask spread exceeds this dollar amount." },
      { name: "maxBidAskSpreadPct", type: "number (0-100)", req: false, desc: "Exclude contracts whose bid/ask spread exceeds this percentage of mid." },
      { name: "minOpenInterest", type: "integer", req: false, desc: "Exclude contracts with open interest below this value." },
      { name: "minVolume", type: "integer", req: false, desc: "Exclude contracts with traded volume below this value." },
      { name: "am", type: "bool", req: false, desc: "Index options only — limit to AM-settled contracts." },
      { name: "pm", type: "bool", req: false, desc: "Index options only — limit to PM-settled contracts." },
    ],
    response: `{
  "s": "ok",
  "optionSymbol": ["AAPL260619C00305000", "AAPL260619P00305000"],
  "underlying": ["AAPL", "AAPL"],
  "expiration": [1781812800, 1781812800],
  "side": ["call", "put"],
  "strike": [305, 305],
  "firstTraded": [1779111000, 1779111000],
  "dte": [28, 28],
  "updated": [1779393600, 1779393600],
  "bid": [3.85, 3.30],
  "bidSize": [31, 6],
  "mid": [4.075, 3.65],
  "ask": [4.30, 4.00],
  "askSize": [5, 23],
  "last": [4.11, 3.75],
  "openInterest": [1175, 5],
  "volume": [2102, 124],
  "inTheMoney": [false, true],
  "intrinsicValue": [0, 0.28],
  "extrinsicValue": [4.075, 3.37],
  "underlyingPrice": [304.72, 304.72],
  "iv": [0.1927, 0.1734],
  "delta": [0.5073, -0.4932],
  "gamma": [0.0391, 0.0435],
  "theta": [-0.1980, -0.1525],
  "vega": [0.2110, 0.2110]
}`,
    curl: `curl "${window.location.origin}/api/v1/options/chain?underlying=AAPL&expiration=2026-06-19&apiKey=sk_live_•••"`,
    js: `const KEY = process.env.MARKET_OPTIONS_KEY;
const res = await fetch(
  "${window.location.origin}/api/v1/options/chain" +
    "?underlying=AAPL&expiration=2026-06-19&apiKey=" + KEY
);
const c = await res.json();

// Columnar: index i across the arrays is one contract.
for (let i = 0; i < c.optionSymbol.length; i++) {
  console.log(c.optionSymbol[i], c.side[i], c.bid[i], c.ask[i]);
}`,
    py: `import os, requests

res = requests.get(
    "${window.location.origin}/api/v1/options/chain",
    params={
        "underlying": "AAPL",
        "expiration": "2026-06-19",
        "apiKey": os.environ["MARKET_OPTIONS_KEY"],
    },
)
c = res.json()

# Columnar: index i across the arrays is one contract.
for i in range(len(c["optionSymbol"])):
    print(c["optionSymbol"][i], c["side"][i], c["bid"][i], c["ask"][i])`,
  },
  {
    id: "quotes",
    method: "GET",
    name: "Quotes",
    path: "/v1/options/quotes",
    summary: "Quote and greek data for one or more specific option contracts.",
    description: "Returns a quote for each OSI option symbol you pass, in the same columnar layout as /chain — every field is a parallel array indexed per contract.",
    params: [
      { name: "symbol", type: "string", req: true, desc: "An OSI option symbol. e.g. AAPL260601C00305000. Comma-separate for several." },
      { name: "date", type: "ISO-8601 date", req: false, desc: "Return the quote as of a given historical date instead of the latest." },
      { name: "from", type: "ISO-8601 date", req: false, desc: "Start date for a historical time-series of quotes." },
      { name: "to", type: "ISO-8601 date", req: false, desc: "End date for a historical time-series of quotes." },
    ],
    response: `{
  "s": "ok",
  "optionSymbol": ["AAPL260601C00305000"],
  "underlying": ["AAPL"],
  "expiration": [1780344000],
  "side": ["call"],
  "strike": [305],
  "firstTraded": [1779111000],
  "dte": [10],
  "updated": [1779393600],
  "bid": [3.85],
  "bidSize": [31],
  "mid": [4.075],
  "ask": [4.30],
  "askSize": [5],
  "last": [4.11],
  "openInterest": [1175],
  "volume": [2102],
  "inTheMoney": [false],
  "intrinsicValue": [0],
  "extrinsicValue": [4.075],
  "underlyingPrice": [304.72],
  "iv": [0.1927],
  "delta": [0.5073],
  "gamma": [0.0391],
  "theta": [-0.1980],
  "vega": [0.2110]
}`,
    curl: `curl "${window.location.origin}/api/v1/options/quotes?symbol=AAPL260601C00305000&apiKey=sk_live_•••"`,
    js: `const KEY = process.env.MARKET_OPTIONS_KEY;
const res = await fetch(
  "${window.location.origin}/api/v1/options/quotes" +
    "?symbol=AAPL260601C00305000&apiKey=" + KEY
);
const q = await res.json();
console.log(q.bid[0], q.ask[0], q.last[0]);`,
    py: `import os, requests

res = requests.get(
    "${window.location.origin}/api/v1/options/quotes",
    params={"symbol": "AAPL260601C00305000", "apiKey": os.environ["MARKET_OPTIONS_KEY"]},
)
q = res.json()
print(q["bid"][0], q["ask"][0], q["last"][0])`,
  },
];

function DocsPage({ onNavigate }) {
  const [active, setActive] = useState("chain");
  const ep = ENDPOINTS.find((e) => e.id === active);
  return (
    <>
      <TopBar crumbs={["Console", "Docs", "API Reference", `options/${ep.id}`]} />
      <div className="docs-shell">
        <DocsSidebar active={active} setActive={setActive} />
        <DocsMain ep={ep} onNavigate={onNavigate} />
        <DocsAside ep={ep} onNavigate={onNavigate} />
      </div>
    </>
  );
}

function DocsSidebar({ active, setActive }) {
  return (
    <aside className="docs-side">
      <div className="section-label">Options</div>
      {ENDPOINTS.map((e) => (
        <div
          key={e.id}
          className={`ep-item ${active === e.id ? "active" : ""}`}
          onClick={() => setActive(e.id)}
        >
          <Tag kind="get">{e.method}</Tag>
          <span style={{ fontFamily: "var(--ff-mono)", fontSize: 12 }}>{e.name}</span>
        </div>
      ))}
    </aside>
  );
}

function DocsMain({ ep, onNavigate }) {
  const responseFields =
    ep.id === "expirations" ? [
      ["s", "string", "Response status — \"ok\" on success."],
      ["expirations", "array<string>", "Ordered ISO-8601 expiration dates."],
      ["updated", "integer", "Unix timestamp of when the data was last refreshed."],
    ] : ep.id === "strikes" ? [
      ["s", "string", "Response status — \"ok\" on success."],
      ["<YYYY-MM-DD>", "array<number>", "One field per expiration date — the ordered list of strikes listed for that expiration."],
      ["updated", "integer", "Unix timestamp of when the data was last refreshed."],
    ] : ep.id === "lookup" ? [
      ["s", "string", "Response status — \"ok\" on success."],
      ["optionSymbol", "string", "The resolved 21-character OSI option symbol."],
    ] : ep.id === "chain" ? [
      ["s", "string", "Response status — \"ok\" on success."],
      ["optionSymbol · underlying", "array<string>", "Columnar — index i across every array is one contract."],
      ["strike · side · dte", "array", "Contract terms: strike price, call/put, days to expiration."],
      ["expiration · firstTraded · updated", "array<integer>", "Unix-epoch-second timestamps."],
      ["bid · ask · mid · last", "array<number>", "Bid, ask, mid, and last-traded prices."],
      ["bidSize · askSize · openInterest · volume", "array<integer>", "Quote sizes, open interest, and traded volume."],
      ["inTheMoney · intrinsicValue · extrinsicValue · underlyingPrice", "array", "Moneyness flag, intrinsic/extrinsic value, and underlying price."],
      ["iv · delta · gamma · theta · vega", "array<number>", "Implied volatility and the greeks."],
    ] : [
      ["s", "string", "Response status — \"ok\" on success."],
      ["optionSymbol · underlying", "array<string>", "Columnar — one index per requested symbol."],
      ["strike · side · dte", "array", "Contract terms: strike price, call/put, days to expiration."],
      ["bid · ask · mid · last", "array<number>", "Bid, ask, mid, and last-traded prices."],
      ["bidSize · askSize · openInterest · volume", "array<integer>", "Quote sizes, open interest, and traded volume."],
      ["iv · delta · gamma · theta · vega", "array<number>", "Implied volatility and the greeks."],
    ];

  return (
    <main className="docs-main">
      <div className="row gap-10">
        <Tag kind="get">{ep.method}</Tag>
        <span className="mono text-xs muted">v1 · stable</span>
      </div>
      <h1>Options {ep.name}</h1>
      <div className="endpoint-url">
        <span style={{ color: "var(--green)" }}>{ep.method}</span>
        <span style={{ color: "var(--text-dim)" }}>{window.location.origin}/api</span>
        <span style={{ color: "var(--text)" }}>{ep.path}</span>
      </div>
      <p style={{ marginTop: 16 }}>{ep.summary}</p>
      <p>{ep.description}</p>

      <h2>Authentication</h2>
      <p>
        Every request must include your personal API key. Three transports are
        supported (in order of preference):
      </p>
      <ul style={{ paddingInlineStart: 20, lineHeight: 1.8, color: "var(--text-dim)" }}>
        <li><code>x-api-key</code> request header — recommended for production. Keeps the key out of URLs and access logs.</li>
        <li><code>Authorization: Bearer &lt;key&gt;</code> header — for clients that already speak Bearer auth.</li>
        <li><code>?apiKey=…</code> query parameter — convenient for browsers, curl, and quick testing (the examples below use this). Note that this puts the key in server access logs and browser history.</li>
      </ul>
      <p>
        Create, rotate, and revoke keys from the{" "}
        <a
          style={{ color: "var(--green)", cursor: "pointer" }}
          onClick={() => onNavigate && onNavigate("keys")}
        >API Keys</a>{" "}page.
      </p>

      <h2>Supported underlyings</h2>
      <p>
        Market-Options serves options on a curated set of US tickers — the
        names that drive the bulk of US options volume. A request for any
        other ticker returns <code>unsupported_underlying</code> (HTTP 403).
        Programmatic clients can fetch the live list from{" "}
        <code>GET /api/v1/underlyings</code> (no auth required).
      </p>
      <SupportedUnderlyingsList />
      <p style={{ fontSize: 13, color: "var(--muted)", marginTop: 8 }}>
        The list is maintained by Market-Options admins and can grow over
        time. Need a ticker that isn't here? Email{" "}
        <a href="mailto:support@market-options.app">support</a> with the
        ticker and we'll consider adding it.
      </p>

      <h2>Filter your chain — narrow the response, lower the cost</h2>
      <p>
        The chain endpoint accepts a long list of filters that <em>narrow the
        response server-side</em> before it crosses the network. Use them
        always — unfiltered chains can return tens of thousands of contracts.
        Because chain billing is per contract, every filter you add directly
        reduces the credits you spend.
      </p>
      <table className="params-tbl" style={{ marginBottom: 14 }}>
        <thead>
          <tr>
            <th style={{ width: "32%" }}>Request</th>
            <th>Approximate rows returned</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><code className="mono" style={{ color: "var(--red)" }}>/options/chain?underlying=SPY</code></td>
            <td className="param-desc" style={{ color: "var(--red)" }}>~22,000 — every strike × every expiration. Don't.</td>
          </tr>
          <tr>
            <td><code className="mono">/options/chain?underlying=SPY&amp;expiration=2026-06-19</code></td>
            <td className="param-desc">~600 — one expiration, all strikes.</td>
          </tr>
          <tr>
            <td><code className="mono">/options/chain?underlying=SPY&amp;dte=30&amp;range=atm&amp;strikeLimit=20</code></td>
            <td className="param-desc" style={{ color: "var(--green)" }}>~20 — ATM strikes around 30 DTE. The sweet spot for most strategies.</td>
          </tr>
          <tr>
            <td><code className="mono">/options/chain?underlying=SPY&amp;dte=30&amp;side=call&amp;minOpenInterest=1000&amp;minVolume=100</code></td>
            <td className="param-desc" style={{ color: "var(--green)" }}>~30 — liquid calls only at 30 DTE.</td>
          </tr>
        </tbody>
      </table>
      <p style={{ fontSize: 13, color: "var(--text-dim)", lineHeight: 1.6 }}>
        Full filter list per endpoint is in the table below. Useful combinations:
        <br />
        <code>dte</code> + <code>range=atm</code> + <code>strikeLimit</code> — research / scanning ATM,&nbsp;
        <code>minOpenInterest</code> + <code>minVolume</code> — liquid contracts only,&nbsp;
        <code>maxBidAskSpread</code> — tradable spreads only,&nbsp;
        <code>delta=.30-.60</code> — directional bets,&nbsp;
        <code>weekly=true</code> — short-dated only,&nbsp;
        <code>strike=400-410</code> — range,&nbsp;
        <code>strike=&gt;=400</code> — comparison.
      </p>

      <h2>Data delay</h2>
      <p>
        Market-Options serves <strong>delayed</strong> options data. The
        delay per endpoint reflects how quickly the underlying data actually
        changes — quotes move every second, expiration lists are stable for
        the whole day:
      </p>
      <ul style={{ paddingInlineStart: 20, lineHeight: 1.8, color: "var(--text-dim)" }}>
        <li><code>/expirations</code> — up to 10 minutes (expirations don't move intraday).</li>
        <li><code>/strikes</code> — up to 10 minutes (strike sets rarely change intraday).</li>
        <li><code>/chain</code> — up to 45 seconds.</li>
        <li><code>/quotes</code> — up to 10 seconds.</li>
        <li><code>/lookup</code> — up to 24 hours (symbol resolution is stable).</li>
      </ul>
      <p>
        If your strategy depends on sub-second quotes, this product is not
        the right fit — try one of the big providers direct.
      </p>

      <h2>Credit billing</h2>
      <p>
        Every successful call costs <strong>credits</strong>. Most endpoints
        cost a flat 1 credit; the chain endpoint can cost more when it
        returns many contracts with quote columns.
      </p>
      <ul style={{ paddingInlineStart: 20, lineHeight: 1.8, color: "var(--text-dim)" }}>
        <li><code>/expirations</code>, <code>/strikes</code>, <code>/lookup</code>, <code>/quotes</code> — always 1 credit.</li>
        <li><code>/chain</code> without quote columns (<code>bid</code>, <code>ask</code>, <code>mid</code>, <code>last</code>) — 1 credit, regardless of contract count.</li>
        <li><code>/chain</code> with quote columns — 1 credit per contract returned. Filters (<code>strikeLimit</code>, <code>dte</code>, <code>side</code>, <code>strike</code>, <code>delta</code>, <code>range</code>) directly reduce the credit cost.</li>
        <li>Failures (4xx, 5xx) are not billed.</li>
      </ul>
      <p>
        Every response carries an{" "}
        <code className="mono" style={{ color: "var(--amber)" }}>X-Credits-Used</code>{" "}
        header with the actual cost of that call. Use it to verify cost during
        development and to monitor quota burn in production.
      </p>

      <h2>Query parameters</h2>
      <table className="params-tbl">
        <thead>
          <tr>
            <th style={{ width: "30%" }}>Name</th>
            <th style={{ width: "20%" }}>Type</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          {ep.params.map((p, i) => (
            <tr key={i}>
              <td>
                <div className="param-name">{p.name}</div>
                <div className={p.req ? "param-req" : "param-opt"}>{p.req ? "required" : "optional"}</div>
              </td>
              <td><span className="param-type">{p.type}</span></td>
              <td className="param-desc">{p.desc}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <h2>Response</h2>
      <p>
        On success the gateway returns a JSON object with a status field <code>s</code> set
        to <code>"ok"</code> alongside the data. The exact example below is what your client
        receives. Timestamps are Unix epoch seconds.
      </p>

      <h2>Response fields</h2>
      <table className="params-tbl">
        <thead>
          <tr>
            <th style={{ width: "30%" }}>Field</th>
            <th style={{ width: "20%" }}>Type</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          {responseFields.map((r, i) => (
            <tr key={i}>
              <td><div className="param-name">{r[0]}</div></td>
              <td><span className="param-type">{r[1]}</span></td>
              <td className="param-desc">{r[2]}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <h2>Errors</h2>
      <p>
        Errors return a JSON body with a stable <code>code</code>, a human <code>message</code>,
        and an actionable <code>hint</code>. The gateway emits these:
      </p>
      <table className="params-tbl">
        <thead>
          <tr>
            <th style={{ width: "30%" }}>Code</th>
            <th style={{ width: "12%" }}>Status</th>
            <th>When</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><div className="param-name">missing_api_key</div></td>
            <td><span className="param-type">401</span></td>
            <td className="param-desc">No API key was provided on the request.</td>
          </tr>
          <tr>
            <td><div className="param-name">invalid_api_key</div></td>
            <td><span className="param-type">401</span></td>
            <td className="param-desc">The API key is unknown or has been revoked.</td>
          </tr>
          <tr>
            <td><div className="param-name">missing_parameter</div></td>
            <td><span className="param-type">400</span></td>
            <td className="param-desc">A required query parameter for this endpoint was not provided.</td>
          </tr>
          <tr>
            <td><div className="param-name">unknown_parameter</div></td>
            <td><span className="param-type">400</span></td>
            <td className="param-desc">A parameter was sent that this endpoint does not accept (likely a typo).</td>
          </tr>
          <tr>
            <td><div className="param-name">invalid_parameter</div></td>
            <td><span className="param-type">400</span></td>
            <td className="param-desc">A parameter value failed type / range / format validation.</td>
          </tr>
          <tr>
            <td><div className="param-name">invalid_path_symbol</div></td>
            <td><span className="param-type">400</span></td>
            <td className="param-desc">The symbol in the URL path is not a valid ticker (or OSI symbol for /quotes).</td>
          </tr>
          <tr>
            <td><div className="param-name">unsupported_endpoint</div></td>
            <td><span className="param-type">404</span></td>
            <td className="param-desc">The endpoint name in the path is not one of the five supported endpoints.</td>
          </tr>
          <tr>
            <td><div className="param-name">unsupported_underlying</div></td>
            <td><span className="param-type">403</span></td>
            <td className="param-desc">The requested underlying is not in the curated allowlist. See <code>/api/v1/underlyings</code> for the live list.</td>
          </tr>
          <tr>
            <td><div className="param-name">burst_limited</div></td>
            <td><span className="param-type">429</span></td>
            <td className="param-desc">Per-minute credit limit reached. Free: 60/min. Pro: 10,000/min. The counter resets each minute.</td>
          </tr>
          <tr>
            <td><div className="param-name">rate_limited</div></td>
            <td><span className="param-type">429</span></td>
            <td className="param-desc">You exceeded your daily credit quota (Free tier: 1,000/day). Pro has no daily ceiling. A <code>Retry-After</code> header is included; quota resets at 00:00 UTC.</td>
          </tr>
          <tr>
            <td><div className="param-name">data_unavailable</div></td>
            <td><span className="param-type">502</span></td>
            <td className="param-desc">The requested data could not be retrieved from upstream. Retry shortly.</td>
          </tr>
          <tr>
            <td><div className="param-name">momentarily_unavailable</div></td>
            <td><span className="param-type">503</span></td>
            <td className="param-desc">Data is being refreshed for this query. Retry in a moment.</td>
          </tr>
        </tbody>
      </table>
    </main>
  );
}

// Loads the live underlyings list from the public /api/v1/underlyings
// endpoint and renders it as a compact chip grid. No auth required — the
// list is advertising content. The fetch is in-memory cached per session
// via API.cached so the docs page does not re-fetch on every navigation.
function SupportedUnderlyingsList() {
  const [state, setState] = useState({ loading: true, items: [], error: "" });
  useEffect(() => {
    let cancelled = false;
    // Direct fetch (no auth) — keeps prospective visitors who aren't signed
    // in able to see the list when this section eventually renders for them.
    fetch("/api/v1/underlyings")
      .then((r) => r.json())
      .then((data) => {
        if (cancelled) return;
        const items = Array.isArray(data && data.underlyings) ? data.underlyings : [];
        setState({ loading: false, items, error: "" });
      })
      .catch((err) => {
        if (cancelled) return;
        setState({ loading: false, items: [], error: (err && err.message) || "Could not load the list." });
      });
    return () => { cancelled = true; };
  }, []);

  if (state.loading) {
    return <div className="mono text-xs muted" style={{ padding: "6px 0" }}>Loading supported underlyings…</div>;
  }
  if (state.error) {
    return <div className="text-xs" style={{ color: "var(--amber)", padding: "6px 0" }}>{state.error}</div>;
  }
  if (state.items.length === 0) {
    return <div className="mono text-xs muted" style={{ padding: "6px 0" }}>No underlyings configured.</div>;
  }
  return (
    <div style={{
      display: "flex", flexWrap: "wrap", gap: 6,
      padding: "10px 12px", borderRadius: 8,
      border: "1px solid var(--border-soft)", background: "var(--panel)",
      marginTop: 8,
    }}>
      {state.items.map((t) => (
        <span key={t} className="mono text-xs" style={{
          padding: "3px 8px", borderRadius: 4,
          background: "var(--panel-2)", color: "var(--text-dim)",
        }}>{t}</span>
      ))}
      <span className="mono text-xs muted" style={{ width: "100%", marginTop: 4, opacity: 0.7 }}>
        {state.items.length} tickers · refreshed on page load
      </span>
    </div>
  );
}

function tryItDefault(name) {
  if (name === "underlying") return "AAPL";
  if (name === "expiration") return "2026-06-19";
  if (name === "q") return "AAPL 2026-06-19 200 call";
  if (name === "symbol") return "AAPL260601C00305000";
  return "";
}

function prettyResult(result) {
  let text;
  if (result.data && typeof result.data === "object") {
    text = JSON.stringify(result.data, null, 2);
  } else {
    text = result.raw || String(result.data || "");
  }
  if (text.length > 6000) text = text.slice(0, 6000) + "\n… (response truncated)";
  return text;
}

function DocsAside({ ep, onNavigate }) {
  // "Try it" parameter inputs — capped at 2 so the form stays compact and
  // the secondary `side` filter (rarely-used) doesn't clutter the panel.
  const tryParams = ep.params.slice(0, 2);
  const [values, setValues] = useState({});
  const [apiKey, setApiKey] = useState("");
  const [keyLoading, setKeyLoading] = useState(true);
  const [result, setResult] = useState(null);
  const [sending, setSending] = useState(false);

  // Reset the parameter inputs when the active endpoint changes.
  useEffect(() => {
    const defaults = {};
    ep.params.slice(0, 2).forEach((p) => { defaults[p.name] = tryItDefault(p.name); });
    setValues(defaults);
    setResult(null);
  }, [ep.id]);

  // Auto-fill the API key from the signed-in user's first usable key. The
  // /keys call is already cached by api.js, so opening the docs page does
  // not pay an extra round-trip when the dashboard has already loaded it.
  // The input stays editable — the user can swap in any other key for ad-hoc
  // testing without losing the auto-filled default.
  const signedIn = !!(window.Auth && Auth.isAuthed());
  useEffect(() => {
    if (!signedIn) { setKeyLoading(false); return; }
    let cancelled = false;
    API.cached("/keys", 30 * 1000)
      .then((res) => {
        if (cancelled) return;
        const list = (res && res.keys) || [];
        // Prefer an active key that actually has plaintext (every new /
        // rotated key does). Fall back to any active key if only legacy
        // hash-only rows are present — the user will see an empty field
        // and the explanatory hint below.
        const usable = list.find((k) => !k.revoked && typeof k.api_key === "string" && k.api_key);
        if (usable) setApiKey(usable.api_key);
        setKeyLoading(false);
      })
      .catch(() => { if (!cancelled) setKeyLoading(false); });
    return () => { cancelled = true; };
  }, [signedIn]);

  const send = () => {
    if (!apiKey || sending) return;
    setSending(true);
    setResult(null);
    const qs = tryParams
      .filter((p) => values[p.name] && String(values[p.name]).trim() !== "")
      .map((p) => encodeURIComponent(p.name) + "=" + encodeURIComponent(String(values[p.name]).trim()))
      .join("&");
    const path = "options/" + ep.id + (qs ? "?" + qs : "");
    API.gateway(apiKey, path)
      .then((res) => { setResult(res); setSending(false); })
      .catch((err) => {
        setResult({ status: 0, ok: false, latency: 0, raw: String((err && err.message) || err) });
        setSending(false);
      });
  };

  return (
    <aside className="docs-aside">
      <div className="ep-label">Request</div>
      <CodeBlock
        tabs={[
          { id: "curl", label: "curl", code: H.curl(ep.curl) },
          { id: "js", label: "node.js", code: H.js(ep.js) },
          { id: "py", label: "python", code: H.py(ep.py) },
        ]}
      />

      <div style={{ marginTop: 22 }}>
        <div className="row" style={{ justifyContent: "space-between", marginBottom: 10 }}>
          <span className="ep-label" style={{ marginBottom: 0 }}>Response · 200</span>
          <Tag kind="get">application/json</Tag>
        </div>
        <CodeBlock
          tabs={[
            { id: "json", label: "JSON", code: H.json(ep.response) },
          ]}
        />
      </div>

      <div style={{ marginTop: 22 }}>
        <div className="ep-label">Try it · live</div>
        <div className="card" style={{ background: "var(--panel)" }}>
          <div className="card-body" style={{ padding: 14 }}>
            {tryParams.map((p) => (
              <div key={p.name} className="field" style={{ marginBottom: 10 }}>
                <label className="mono text-xs muted" style={{ textTransform: "uppercase", letterSpacing: "0.06em" }}>
                  {p.name}{p.req ? " *" : ""}
                </label>
                <input className="input mono" style={{ padding: "7px 10px" }}
                  value={values[p.name] || ""}
                  onChange={(e) => setValues({ ...values, [p.name]: e.target.value })} />
              </div>
            ))}

            {/* Signed-out users see a sign-in CTA — there's no key to use. */}
            {!signedIn && (
              <button className="btn btn-primary" style={{ width: "100%", justifyContent: "center", marginTop: 6 }}
                onClick={() => onNavigate && onNavigate("login")}>
                Sign in to run live calls
              </button>
            )}

            {/* Signed-in users get the key auto-filled from /keys. Editable
                in case they want to test a specific key (e.g. one they just
                created and haven't refreshed the list for). */}
            {signedIn && (
              <>
                <div className="field" style={{ marginBottom: 10 }}>
                  <label className="mono text-xs muted" style={{ textTransform: "uppercase", letterSpacing: "0.06em" }}>
                    API key
                  </label>
                  <input className="input mono" style={{ padding: "7px 10px" }}
                    placeholder={keyLoading ? "Loading your key…" : "sk_live_…"}
                    value={apiKey}
                    onChange={(e) => setApiKey(e.target.value)} />
                </div>
                <button className="btn btn-primary" style={{ width: "100%", justifyContent: "center", marginTop: 6 }}
                  onClick={send} disabled={sending || !apiKey}>
                  <Icon.Bolt style={{ width: 13, height: 13 }} />
                  {sending ? "Sending…" : "Send call"}
                </button>
              </>
            )}

            <div className="row mono text-xs muted" style={{ marginTop: 10, justifyContent: "space-between" }}>
              <span>
                {!signedIn ? "No key — sign in first"
                  : keyLoading ? "Fetching your key…"
                  : apiKey ? "Using your live key"
                  : "No usable key — rotate one from API Keys"}
              </span>
              <span>credits billed per response shape</span>
            </div>

            {result && (
              <div style={{ marginTop: 12 }}>
                <div className="row" style={{ justifyContent: "space-between", marginBottom: 6 }}>
                  <span className="mono text-xs" style={{ color: result.ok ? "var(--green)" : "var(--red)" }}>
                    {result.status ? result.status : "ERR"} {result.ok ? "OK" : "Error"}
                  </span>
                  <span className="mono text-xs muted">{result.latency}ms</span>
                </div>
                <pre className="code-body" style={{ maxHeight: 280, margin: 0, borderRadius: 8, border: "1px solid var(--border-soft)" }}>
                  <code dangerouslySetInnerHTML={{ __html: H.json(prettyResult(result)) }} />
                </pre>
              </div>
            )}
          </div>
        </div>
      </div>
    </aside>
  );
}

Object.assign(window, { DocsPage });
