/**
 * # cli/args — argv parsing + global-flag extraction (Kestrel CLI v1 §2)
 *
 * `parseArgs` reads leading positionals, then `--flag value` pairs, against a per-verb known set:
 * `booleanFlags` are consumed value-lessly, `valueFlags` each take the following token as a value.
 * A `--flag` in **neither** set is an UNKNOWN flag and a loud {@link CliError} (exit 2) **regardless
 * of position** — mid-argv or trailing — so an agent-authored command line with a typo (e.g.
 * `--r-usdd` for `--r-usd`) can never be silently swallowed and masquerade as an intentional run
 * (AGENTS.md fail-closed non-negotiable; kestrel-gbv). Anything else malformed is likewise exit 2.
 * `extractGlobals` peels the render flags (`--json`/`--format`/`--agent`/`--color`/`--no-color`/
 * `-h`/`--help`/`-V`/`--version`) off the raw argv **before** the router runs, so every command sees
 * a clean argv (with only its own flags) and the resolver sees the global flags.
 */

import type { GlobalFlags } from "./context.ts";
import { CliError, EXIT } from "./errors.ts";

function usageErr(message: string): CliError {
  return new CliError({ code: "USAGE", exit: EXIT.USAGE, message });
}

/** A parsed argv: leading positionals, then `--flag value` pairs, plus consumed boolean flags. */
export interface ParsedArgs {
  readonly positionals: readonly string[];
  readonly flags: Map<string, string>;
  readonly bools: ReadonlySet<string>;
}

/**
 * Positionals-then-flags, against a per-verb known set. `booleanFlags` are value-less; `valueFlags`
 * each consume the following token. A `--flag` in neither set is UNKNOWN → loud USAGE error, checked
 * **before** value consumption so it fails identically mid-argv or trailing (§6, fail-closed). A
 * recognized value flag with no following token is the distinct "missing value" USAGE error.
 */
export function parseArgs(
  argv: readonly string[],
  booleanFlags: ReadonlySet<string> = new Set(),
  valueFlags: ReadonlySet<string> = new Set(),
): ParsedArgs {
  const positionals: string[] = [];
  const flags = new Map<string, string>();
  const bools = new Set<string>();
  let i = 0;
  while (i < argv.length && !argv[i]!.startsWith("--")) {
    positionals.push(argv[i]!);
    i += 1;
  }
  for (; i < argv.length; i += 1) {
    const key = argv[i]!;
    if (!key.startsWith("--")) {
      throw usageErr(`bad argument ${JSON.stringify(key)} — expected a --flag here`);
    }
    const name = key.slice(2);
    if (booleanFlags.has(name)) {
      bools.add(name);
      continue;
    }
    // Reject unknown flags uniformly, BEFORE any value is consumed — so a typo fails closed the same
    // way whether or not a token follows it (kestrel-gbv), never swallowing the next token silently.
    if (!valueFlags.has(name)) throw usageErr(`unknown flag --${name}`);
    const val = argv[i + 1];
    if (val === undefined) throw usageErr(`missing value for --${name}`);
    flags.set(name, val);
    i += 1;
  }
  return { positionals, flags, bools };
}

/** Required flag or a loud USAGE error. */
export function required(flags: Map<string, string>, name: string): string {
  const v = flags.get(name);
  if (v === undefined) throw usageErr(`missing required flag --${name}`);
  return v;
}

/** The result of stripping the global render flags off a raw argv. */
export interface GlobalScan {
  readonly globals: GlobalFlags;
  /** The remaining argv with global flags removed (command + its own args). */
  readonly rest: string[];
  /** True if `-h`/`--help`/`help` requested (fast-path before the router). */
  readonly wantHelp: boolean;
  /** True if `-V`/`--version`/`version` requested (fast-path before the router). */
  readonly wantVersion: boolean;
}

/**
 * Peel the global render + meta flags off a raw argv. Recognizes `--json`, `--format <v>`,
 * `--agent`, `--color <v>`, `--no-color`, `-h`/`--help`, `-V`/`--version`. Everything else passes
 * through in `rest` in order. An unknown `--flag` is **not** rejected here — it is left in `rest`
 * for the command's own parser to accept or reject (so `--fromat` errors at the command, not
 * silently swallowed). Fast-path meta words (`help`, `version`) as the FIRST token are detected.
 */
export function extractGlobals(argv: readonly string[]): GlobalScan {
  const rest: string[] = [];
  const globals: {
    json?: boolean;
    format?: string;
    agent?: boolean;
    color?: string;
    noColor?: boolean;
    api?: string;
  } = {};
  let wantHelp = false;
  let wantVersion = false;

  for (let i = 0; i < argv.length; i += 1) {
    const a = argv[i]!;
    switch (a) {
      case "--json":
        globals.json = true;
        break;
      case "--agent":
        globals.agent = true;
        break;
      case "--no-color":
        globals.noColor = true;
        break;
      case "-h":
      case "--help":
        wantHelp = true;
        break;
      case "-V":
      case "--version":
        wantVersion = true;
        break;
      case "--format": {
        const v = argv[i + 1];
        if (v === undefined) throw usageErr("missing value for --format");
        globals.format = v;
        i += 1;
        break;
      }
      case "--color": {
        const v = argv[i + 1];
        if (v === undefined) throw usageErr("missing value for --color");
        globals.color = v;
        i += 1;
        break;
      }
      case "--api": {
        const v = argv[i + 1];
        if (v === undefined) throw usageErr("missing value for --api");
        globals.api = v;
        i += 1;
        break;
      }
      default:
        rest.push(a);
    }
  }

  // Bare `help`/`version` as the leading command word are meta fast-paths too.
  if (rest[0] === "help") wantHelp = true;
  if (rest[0] === "version") wantVersion = true;

  return { globals, rest, wantHelp, wantVersion };
}
