/**
 * # cli/commands/mcp — the `kestrel mcp` verb: the MCP face over stdio (kestrel-0f7i)
 *
 * `kestrel mcp` serves the EXISTING MCP dispatcher ({@link createKestrelMcpServer} — the one source of
 * the tool/resource surface, the same projection the hosted MCP server exposes) over stdin/stdout as
 * newline-delimited JSON-RPC 2.0, so an MCP client configures the package as a local stdio server:
 *
 *     { "command": "npx", "args": ["kestrel.markets", "mcp"] }
 *
 * It is a THIN COMPOSITION: transport choice + `serveStdio(createKestrelMcpServer({ transport }))`.
 * No MCP semantics live here — the dispatcher (`src/mcp/server.ts`) is the contract; this verb is only
 * the launch wiring the published bin was missing (the face previously ran solely via
 * `bun src/mcp/stdio.ts`, unreachable through `npx` under plain node).
 *
 * ── Transport choice (the ONE knob, mirroring `agent`'s vocabulary) ──
 *  - DEFAULT: the REMOTE transport at {@link DEFAULT_API} (`--api <url>` / `KESTREL_API` select another
 *    base; bare `--api` / `--api default` ⇒ the canonical base). Pure `fetch` — ZERO bun dependency —
 *    so `npx kestrel.markets mcp` is node-runnable end-to-end, and the tool surface it serves is the
 *    hosted platform's. The default deliberately DIFFERS from `agent`'s (local): an MCP client
 *    configures this verb as a drop-in stdio server on hosts where only node exists, so the
 *    node-runnable transport must be the zero-config path (the kestrel-33yr.4 direction).
 *  - `--local`: the in-process engine (`localTransport()`). HEAVY — the router gates this arm through
 *    `loadHeavy`, so under a bun-less node it refuses LOUD (exit 4, RUNTIME_UNAVAILABLE), never a raw
 *    `Bun is not defined` mid-handshake.
 *  - `--local` + `--api` is a CONTRADICTION → fail closed (USAGE), never silently honour one.
 *
 * stdout is the MCP wire ONLY (serveStdio writes one JSON-RPC response per REQUEST; a NOTIFICATION —
 * an id-less frame such as the handshake's `notifications/initialized` — elicits nothing, as JSON-RPC 2.0
 * and MCP require); this module writes nothing else to it. The process serves until stdin ends — exactly
 * the lifecycle an MCP client drives.
 */

import { parseArgs } from "../args.ts";
import { DEFAULT_API } from "../backend/remote.ts";
import type { GlobalFlags } from "../context.ts";
import { CliError, EXIT } from "../errors.ts";
import { createKestrelMcpServer, serveStdio } from "../../mcp/index.ts";
import { localTransport, remoteTransport, type Transport } from "../../sdk/index.ts";

/** The web `fetch` shape the remote transport speaks (structurally the client's `FetchLike`). */
type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;

/** Environment shape (for `KESTREL_API`); default `process.env`. Injectable for the hermetic shape tests. */
interface Env {
  readonly [k: string]: string | undefined;
}

function usage(message: string): CliError {
  return new CliError({
    code: "USAGE",
    exit: EXIT.USAGE,
    message,
    hint: "usage: kestrel mcp [--local | --api <url>]  (see `kestrel mcp --help`)",
  });
}

/** Resolve the ONE transport choice from the verb's args + the global `--api` flag + `KESTREL_API`.
 *  Remote-base precedence is `--api` > `KESTREL_API` env > {@link DEFAULT_API} — the SAME ladder
 *  `backend/select.ts` pins for every other verb, so the copy that says "also KESTREL_API" is TRUE here
 *  (an MCP client's config `env` block is the natural way to point this verb at another base). Only the
 *  EXPLICIT `--api` flag contradicts `--local`: an exported KESTREL_API is ambient, so `--local` simply
 *  outranks it (flag > env) rather than turning every such shell into a refusal. Exported for the
 *  in-process contradiction/shape tests; the spawn e2e proves the WIRING through the real bin. */
export function resolveMcpTransport(args: readonly string[], api: string | undefined, env: Env = process.env): Transport {
  // The one verb-local flag; parseArgs rejects any unknown flag uniformly (kestrel-gbv, fail-closed).
  const parsed = parseArgs(args, new Set(["local"]));
  if (parsed.positionals.length > 0) {
    // A stray positional must never be silently absorbed by a verb whose stdout is a machine wire.
    throw usage(`unexpected \`mcp\` argument ${JSON.stringify(parsed.positionals[0])}`);
  }
  const local = parsed.bools.has("local");
  if (local && api !== undefined) {
    throw usage("`--local` contradicts `--api` — the MCP server speaks exactly one transport");
  }
  if (local) return localTransport();
  const raw = api ?? env.KESTREL_API;
  const baseUrl = raw === undefined || raw === "" || raw === "default" ? DEFAULT_API : raw;
  return remoteTransport({ baseUrl, fetch: globalThis.fetch as FetchLike });
}

/**
 * Serve the MCP face over stdio until stdin ends. The router gates the `--local` arm through
 * `loadHeavy` BEFORE this module runs its local transport, so reaching `localTransport()` here
 * implies a usable bun.
 */
export async function mcpCommand(args: readonly string[], globals: GlobalFlags): Promise<number> {
  const transport = resolveMcpTransport(args, globals.api);
  await serveStdio(createKestrelMcpServer({ transport }));
  return EXIT.OK;
}
