#!/usr/bin/env node

// Suppress ExperimentalWarning noise from Node/npm internals (v3.25.0+).
// On Node 22+ npm emits a CommonJS/ESM ExperimentalWarning on every spawn —
// not actionable for our users, repeated on every invocation, illegible.
// Setting NODE_NO_WARNINGS in env BEFORE any spawn propagates to all
// child processes we launch (git, npx, etc). Cannot silence the warning
// emitted by `npx` itself BEFORE our code loads — for that, the
// recommended install is `npm i -g baldart` (then `baldart` direct).
// Escape hatch: BALDART_VERBOSE_WARNINGS=1 keeps them visible.
if (process.env.BALDART_VERBOSE_WARNINGS !== '1' && !process.env.NODE_NO_WARNINGS) {
  process.env.NODE_NO_WARNINGS = '1';
}

const { Command } = require('commander');
const chalk = require('chalk');
const fs = require('fs');
const path = require('path');
const packageJson = require('../package.json');
const updateNotifier = require('../src/utils/update-notifier');

// Source of truth for the running version is the VERSION file at the repo
// root. package.json.version is only synced at publish time (see scripts/
// sync-version), so in dev / local-source / pre-publish contexts it lags.
// Reading VERSION here keeps `baldart --version` honest in every scenario.
function resolveVersion() {
  try {
    const versionFile = path.join(__dirname, '..', 'VERSION');
    const fromFile = fs.readFileSync(versionFile, 'utf8').trim();
    if (fromFile) return fromFile;
  } catch (_) { /* fall through */ }
  // Fall back to package.json (already required above). Avoids the TDZ trap
  // of self-referencing `CLI_VERSION` before its initialiser runs.
  return packageJson.version;
}
const CLI_VERSION = resolveVersion();

// CLI self-update notifier (v3.13.0+). Best-effort; non-blocking; suppressed
// in CI / non-TTY / via BALDART_NO_UPDATE_CHECK=1 / when --offline is passed.
// Prints a one-shot hint at the top of the run if a newer npm version was
// detected on a previous invocation, then kicks off a background refresh.
// First-ever run shows nothing — that is intentional; we never block the
// command on a network call.
const rawArgsForNotifier = process.argv.slice(2);
const offlineForNotifier = rawArgsForNotifier.includes('--offline');
// In --json mode STDOUT must carry exactly one JSON object — the notifier hint
// would pollute it. Suppress the announce entirely (the refresh is harmless but
// the announce prints to stdout). (v3.32.0+)
const jsonForNotifier = rawArgsForNotifier.includes('--json');
if (!jsonForNotifier) {
  updateNotifier.announceAndRefresh({
    currentVersion: CLI_VERSION,
    offline: offlineForNotifier,
  });
}

const program = new Command();

program
  .name('baldart')
  .description('Claude Agent Framework - AI agent coordination for software projects')
  .version(CLI_VERSION);

program
  .command('add [repo]')
  .description('Install the framework in your project')
  .option('-b, --branch <branch>', 'Branch to use', 'main')
  .option('-y, --yes', 'Non-interactive install (CI / agents): auto-confirm all optional prompts and skip the interactive configure wizard. Refuses to reinstall over an existing .framework/ — use `update --reset` for that.')
  .action(async (repo, options) => {
    const addCommand = require('../src/commands/add');
    await addCommand(repo || 'antbald/BALDART', options);
  });

program
  .command('update')
  .description('Update the framework to the latest version')
  .option('--no-commit', 'Skip the post-update auto-commit prompt')
  .option('-y, --yes', 'Auto-confirm all prompts (CI / scripting). Implies --auto-stash.')
  .option('--auto-stash', 'When the working tree is dirty, stash automatically instead of prompting.')
  .option('--reset', 'Nuclear option: rm -rf .framework/ + fresh install, preserving baldart.config.yml + .baldart/ + custom .claude/{agents,skills,commands}/. Requires clean working tree.')
  .option('--i-know', 'Acknowledges --reset will wipe untracked/ignored files inside .framework/ (required to use --reset --yes).')
  .option('--on-divergence <strategy>', 'Non-interactive resolution when the consumer has local commits on .framework/ (for agents / CI): "scaffold-overlays" (auto-create overlay skeletons, then stop), "pull" (keep commits + merge — non-destructive), or "abort".')
  .option('--json', 'Machine-readable output (agents / CI): emit a single JSON result object on stdout, route all human output to stderr. Requires --yes (non-interactive); rejects --reset.')
  // Parse permissively so an UNKNOWN flag does not crash commander before the
  // CLI self-upgrade can kick in (v3.35.2+). The whole point of `update` is to
  // be the self-healing entrypoint: when a flag introduced in a newer release
  // reaches an older-but-still-this-or-later CLI, commander used to throw
  // `error: unknown option` during parse — i.e. BEFORE the action handler runs,
  // so the auto-relaunch under `npx baldart@latest` (which lives in the action)
  // never got a chance. With both guards set, unknown tokens land in
  // `command.args` instead of throwing; the action forwards them to a newer CLI
  // (or emits a helpful error). Scoped to `update` only — every other command
  // keeps strict validation.
  .allowUnknownOption()
  .allowExcessArguments()
  .action(async (options, command) => {
    const updateCommand = require('../src/commands/update');
    // command.args holds any tokens commander could not match to a known
    // option/positional — for `update` (no positionals) that means unknown
    // flags + their values. Forward them so update() can self-upgrade.
    await updateCommand(options, command.args || []);
  });

program
  .command('push')
  .description('Push your improvements back to the framework')
  .action(async () => {
    const pushCommand = require('../src/commands/push');
    await pushCommand();
  });

program
  .command('version')
  .description('Show installed framework version + drift from remote + last-push info')
  .option('--offline', 'Skip the upstream fetch (no remote drift report)')
  .option('--json', 'Machine-readable output: emit a single JSON object on stdout, route human output to stderr (for agents / CI).')
  .action(async (options) => {
    const versionCommand = require('../src/commands/version');
    await versionCommand({ offline: !!options.offline, json: !!options.json });
  });

program
  .command('status')
  .description('Show framework installation status')
  .action(async () => {
    const statusCommand = require('../src/commands/status');
    await statusCommand();
  });

program
  .command('migrate')
  .description('Migrate an older installation to the current layout (v2.1.1+ per-skill merge, legacy backup recovery)')
  .action(async () => {
    const migrateCommand = require('../src/commands/migrate');
    await migrateCommand();
  });

program
  .command('configure')
  .description('Generate / update baldart.config.yml (project paths, identity, stack, feature flags)')
  .option('--non-interactive', 'Write autodetected values without prompts')
  .action(async (options) => {
    const configureCommand = require('../src/commands/configure');
    await configureCommand({ nonInteractive: options.nonInteractive });
  });

program
  .command('doctor')
  .description('Smart diagnostic — detects what state the install is in and proposes the next action (also invoked when running `baldart` with no arguments)')
  .option('--auto', 'Run without confirmation prompts; errors out if multiple actions are ambiguous')
  .option('--offline', 'Skip the upstream fetch (no remote drift check)')
  .action(async (options) => {
    const doctorCommand = require('../src/commands/doctor');
    await doctorCommand({ auto: !!options.auto, offline: !!options.offline });
  });

const overlayGroup = program
  .command('overlay')
  .description('Author and check .baldart/overlays/ — scaffolds, validates, and detects drift on skill/agent/command overlays');

overlayGroup
  .command('scaffold <target>')
  .description('Create a new overlay file (e.g. `scaffold agents/coder`, `scaffold bug`)')
  .option('--mode <mode>', 'Overlay mode: extend (default) or override (skill-only)', 'extend')
  .action(async (target, options) => {
    const overlay = require('../src/commands/overlay');
    await overlay.scaffold(target, options);
  });

overlayGroup
  .command('validate <path>')
  .description('Dry-run the merger on an overlay and report any errors / warnings')
  .action(async (overlayPath) => {
    const overlay = require('../src/commands/overlay');
    await overlay.validate(overlayPath);
  });

overlayGroup
  .command('drift [path]')
  .description('Check whether the base file changed since an overlay was authored. Without `path`, checks all overlays.')
  .option('--all', 'Check every overlay under .baldart/overlays/ (default when no path is given)')
  .action(async (overlayPath, options) => {
    const overlay = require('../src/commands/overlay');
    await overlay.drift(overlayPath, options);
  });

const routinesGroup = program
  .command('routines')
  .description('Manage scheduled routines (wiki-review, doc-review, code-review, skill-improve, ds-drift, full-sweep)');

routinesGroup
  .command('list')
  .description('List all routines with status')
  .action(async () => {
    const routines = require('../src/commands/routines');
    await routines.list();
  });

routinesGroup
  .command('install <name>')
  .description('Install a routine (interactive backend picker)')
  .action(async (name) => {
    const routines = require('../src/commands/routines');
    await routines.install(name);
  });

routinesGroup
  .command('disable <name>')
  .description('Disable an installed routine')
  .action(async (name) => {
    const routines = require('../src/commands/routines');
    await routines.disable(name);
  });

routinesGroup
  .command('doctor')
  .description('Verify installed routines are still configured correctly')
  .action(async () => {
    const routines = require('../src/commands/routines');
    await routines.doctor();
  });

// Error handling
program.exitOverride();

// When called with no arguments (or only the doctor flags), run `doctor`
// directly. This is the v3.2.0+ default: `npx baldart` figures out what to do
// for you, and `npx baldart --auto` / `--offline` work without typing
// `doctor` first.
const rawArgs = process.argv.slice(2);
const isDoctorShortcut =
  rawArgs.length === 0 ||
  rawArgs.every((a) => a === '--auto' || a === '--offline');

if (isDoctorShortcut) {
  const doctorCommand = require('../src/commands/doctor');
  doctorCommand({
    auto: rawArgs.includes('--auto'),
    offline: rawArgs.includes('--offline'),
  }).catch((err) => {
    console.error(err && err.message ? err.message : err);
    process.exit(1);
  });
} else {
  try {
    program.parse(process.argv);
  } catch (err) {
    // Commander throws CommanderError(exitCode: 0) for --version / --help.
    // That's a normal exit, not a crash — swallow the throw and exit cleanly.
    if (err && err.code === 'commander.version') process.exit(0);
    if (err && err.code === 'commander.help') process.exit(0);
    if (err && err.code === 'commander.helpDisplayed') process.exit(0);
    // Real error: log and exit non-zero like before.
    console.error(err && err.message ? err.message : err);
    process.exit(err && Number.isInteger(err.exitCode) ? err.exitCode : 1);
  }
}
