/**
 * # cli/commands/card — `card` (LIGHT, node-runnable, zero-network)
 *
 * The authoring on-ramp (kestrel-nde6). The published package SHIPS the complete agent reference —
 * `docs/public/card.md` (the compact, version-coupled agent language card), its machine twin
 * `card.json`, and the first-steps walkthroughs (`first-plan.md`, `first-wake.md`, …) — but nothing
 * in the CLI surfaced them: `kestrel card` was an unknown command and no help pointed at the plan
 * syntax. An agent with the teaching material ALREADY on disk could not find it. This verb prints
 * that shipped material from the INSTALLED package, offline, from any cwd:
 *
 *  - `kestrel card`            → `docs/public/card.md` (the agent language card).
 *  - `kestrel card --json`     → `docs/public/card.json` (the machine twin) verbatim.
 *  - `kestrel card <topic>`    → `docs/public/<topic>.md` (a shipped walkthrough / reference).
 *  - `kestrel card <topic> --json` → a JSON envelope carrying that markdown.
 *  - an unknown topic          → a typed USAGE refusal (exit 2) that LISTS the valid topics.
 *
 * Imports only node built-ins (`fs`/`path`/`url`) — no `bun:*`, no network — so it runs under plain
 * node and works with zero connectivity in any directory. The valid-topic set is ENUMERATED from the
 * shipped `docs/public/` directory (every `*.md`), never a hand-kept list that could drift from what
 * the package actually ships; the refusal names exactly what a real install carries.
 *
 * PACKAGING RESOLUTION — two layouts, one ascending search. Running from a repo checkout the module
 * lives at `src/cli/commands/card.ts` (root three levels up); in the published package the whole CLI
 * is bundled into `dist/cli.js` (root one level up). `resolveDocsPublicDir` ascends from this
 * module's own directory until it finds a `docs/public/card.md`, so BOTH layouts resolve without a
 * hard-coded depth. `docs/public` is in package.json `files`, so the directory is present on a real
 * install (a `bun test` guard asserts that, so this verb can never 404 on a shipped tree).
 */

import { existsSync, readFileSync, readdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

import type { OutputCtx } from "../context.ts";
import { parseArgs } from "../args.ts";
import { CliError, EXIT } from "../errors.ts";

/** The default document `kestrel card` (no topic) prints — the compact agent language card. */
const CARD_MD = "card.md";
/** The machine twin `kestrel card --json` (no topic) emits verbatim. */
const CARD_JSON = "card.json";

/**
 * Resolve the shipped `docs/public/` directory by ascending from THIS module's own location until a
 * `docs/public/card.md` is found. Handles both the repo checkout (`src/cli/commands/`, three up) and
 * the bundled install (`dist/cli.js`, one up) without a hard-coded depth. Fails closed (a loud
 * NOT_FOUND, never a raw ENOENT stack) if the shipped docs are somehow absent.
 */
function resolveDocsPublicDir(): string {
  let dir = dirname(fileURLToPath(import.meta.url));
  // Six levels is comfortably deeper than either real layout (3 for source, 1 for the bundle).
  for (let i = 0; i < 6; i += 1) {
    const candidate = join(dir, "docs", "public");
    if (existsSync(join(candidate, CARD_MD))) return candidate;
    const parent = dirname(dir);
    if (parent === dir) break; // reached the filesystem root
    dir = parent;
  }
  throw new CliError({
    code: "NOT_FOUND",
    exit: EXIT.NOT_FOUND,
    message: "the shipped agent card (docs/public/card.md) was not found next to the installed package",
    hint: "reinstall kestrel.markets — `docs/public` is part of the published files allowlist",
  });
}

/**
 * The valid topic set: every shipped `docs/public/*.md`, basename without the extension, sorted. The
 * default `card` is one of them (`kestrel card card` === `kestrel card`). Enumerated from disk so the
 * unknown-topic refusal names exactly what the package ships — the list cannot drift from reality.
 */
function shippedTopics(dir: string): string[] {
  return readdirSync(dir)
    .filter((f) => f.endsWith(".md"))
    .map((f) => f.slice(0, -".md".length))
    .sort();
}

/**
 * `card` — print the shipped agent reference from the installed package. Exit 0 on success; a USAGE
 * refusal (exit 2) that lists the valid topics on an unknown topic; a NOT_FOUND (exit 3) only if the
 * shipped docs are missing entirely.
 */
export function cardCommand(argv: readonly string[], ctx: OutputCtx): number {
  const { positionals } = parseArgs(argv);
  const topic = positionals[0];
  const dir = resolveDocsPublicDir();

  // Bare `card`: the compact agent language card, or its machine twin under --json.
  if (topic === undefined) {
    if (ctx.mode === "json") {
      // Emit card.json verbatim — it is already canonical machine JSON (the version-coupling guard
      // owns its content; this verb only SURFACES it, never regenerates it).
      process.stdout.write(readFileSync(join(dir, CARD_JSON), "utf8"));
    } else {
      process.stdout.write(readFileSync(join(dir, CARD_MD), "utf8"));
    }
    return 0;
  }

  // A named topic: validate against the shipped set, then print it.
  const topics = shippedTopics(dir);
  if (!topics.includes(topic)) {
    throw new CliError({
      code: "USAGE",
      exit: EXIT.USAGE,
      message: `unknown card topic ${JSON.stringify(topic)}`,
      hint: `valid topics: ${topics.join(", ")}`,
    });
  }

  const markdown = readFileSync(join(dir, `${topic}.md`), "utf8");
  if (ctx.mode === "json") {
    process.stdout.write(JSON.stringify({ schema: "kestrel.card.topic/v1", topic, markdown }) + "\n");
  } else {
    process.stdout.write(markdown);
  }
  return 0;
}
