/**
 * Thin orchestrator for `bin/ethercalc`. Ties parse → map → spawn together
 * and keeps IO at the edge so the logic modules stay pure.
 *
 * `main()` is the single entry point; it takes its IO sinks as parameters
 * so tests can stub them. `bin/ethercalc` passes real `process.stdout`,
 * `process.stderr`, and a `spawnSync`-based exec.
 */
import { parseFlags, CliError } from './parse.ts';
import { buildLaunchPlan } from './map.ts';
import { HELP_TEXT } from './help.ts';

/**
 * Dependencies injected into `main()`. Explicit so tests can stub stdout/
 * stderr/exec without touching the real process. `exec` returns the exit
 * code the caller should propagate; implementations that `exec`-replace
 * the current process never return, but the return type is kept so the
 * test stub can still report what-would-have-happened.
 */
export interface MainDeps {
  stdout: (s: string) => void;
  stderr: (s: string) => void;
  exec: (cmd: string, args: string[], env: Record<string, string>) => number;
  env?: Record<string, string | undefined>;
}

/**
 * Entry point. Returns the exit code the caller should exit with.
 *
 * Behavior:
 *   - `--help` / `-h`: print help to stdout, return 0.
 *   - Parse error:     print to stderr, return 2 (conventional EX_USAGE).
 *   - Otherwise:       map to launch plan, forward warnings to stderr,
 *                      exec wrangler, and return its exit code.
 */
export function main(argv: readonly string[], deps: MainDeps): number {
  let flags;
  try {
    flags = parseFlags(argv);
  } catch (err) {
    // parseFlags only ever raises CliError by design — we catch only that
    // subtype and let any other synchronous error propagate (an
    // engineering bug should not be swallowed).
    if (!(err instanceof CliError)) throw err;
    deps.stderr(`${err.message}\n`);
    deps.stderr('Run `ethercalc --help` for usage.\n');
    return 2;
  }
  if (flags.help === true) {
    deps.stdout(`${HELP_TEXT}\n`);
    return 0;
  }
  const plan = buildLaunchPlan(flags, deps.env);
  for (const w of plan.warnings) {
    deps.stderr(`${w}\n`);
  }
  // `bunx wrangler` (not `npx wrangler`) — bin/ethercalc already has a
  // `#!/usr/bin/env bun` shebang, so bun is a hard prerequisite. Using
  // bunx avoids needing nodejs+npm on top of bun, which matters for
  // self-hosting in a minimal container (e.g. Sandstorm grains bundle
  // only what the app touches at runtime; extra Node toolchain doubles
  // the package size).
  return deps.exec('bunx', ['wrangler', ...plan.wranglerArgs], plan.env);
}
