#!/usr/bin/env bun

import { render } from "ink";
import { App } from "./app.tsx";
import { loadConfig, createDefaultConfig, findConfigPath } from "./config/loader.ts";
import { initDb, closeDb } from "./db/index.ts";
import {
  COMMANDS,
  findCommand,
  validateFlags,
  renderHelp,
  allLongFlagNames,
  allShortFlagNames,
  allFlagsTakingValue,
} from "./cli/registry.ts";
import type { GitforestConfig } from "./types/index.ts";

let isShuttingDown = false;

async function gracefulShutdown(signal: string, exitCode: number): Promise<void> {
  if (isShuttingDown) return;
  isShuttingDown = true;

  try {
    closeDb();
  } catch (error) {
    console.error('Error during shutdown:', error);
  }

  process.exit(exitCode);
}

// Register signal handlers — clean exits on signals, non-zero on crashes so
// callers/CI can distinguish "user quit" from "we crashed".
process.on('SIGINT', () => gracefulShutdown('SIGINT', 0));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM', 0));
process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  gracefulShutdown('uncaughtException', 1);
});
process.on('unhandledRejection', (reason) => {
  console.error('Unhandled Rejection:', reason);
  gracefulShutdown('unhandledRejection', 1);
});

/**
 * Parse argv into (command, flags, positional). Syntactic checks only:
 * unknown flags are rejected against the registry's union of declared
 * flags. Per-command flag validation runs after command lookup via
 * validateFlags() — that's where "--target on `list`" becomes an error.
 *
 * The special pre-command flag `--init` (create default config) is
 * accepted here without per-command validation since it bypasses the
 * registry entirely.
 */
function parseArgs(args: string[]): {
  command: string | null;
  flags: Record<string, boolean | string>;
  positional: string[];
  errors: string[];
} {
  const flags: Record<string, boolean | string> = {};
  const positional: string[] = [];
  const errors: string[] = [];
  let command: string | null = null;

  const knownLong = new Set<string>([...allLongFlagNames(), "init"]);
  const shortMap = allShortFlagNames();
  const valueFlags = allFlagsTakingValue();

  for (let i = 0; i < args.length; i++) {
    const arg = args[i]!;

    if (arg.startsWith("--")) {
      const key = arg.slice(2);

      if (!knownLong.has(key)) {
        errors.push(`Unknown flag: --${key}`);
        continue;
      }

      if (valueFlags.has(key)) {
        const nextArg = args[i + 1];
        if (nextArg && !nextArg.startsWith("-")) {
          flags[key] = nextArg;
          i++;
        } else {
          errors.push(`Flag --${key} requires a value`);
        }
      } else {
        flags[key] = true;
      }
    } else if (arg.startsWith("-") && arg.length === 2) {
      const shortKey = arg.slice(1);
      const longKey = shortMap.get(shortKey);

      if (!longKey) {
        errors.push(`Unknown flag: -${shortKey}`);
        continue;
      }

      if (valueFlags.has(longKey)) {
        const nextArg = args[i + 1];
        if (nextArg && !nextArg.startsWith("-")) {
          flags[longKey] = nextArg;
          i++;
        } else {
          errors.push(`Flag -${shortKey} requires a value`);
        }
      } else {
        flags[longKey] = true;
      }
    } else if (!command) {
      command = arg;
    } else {
      positional.push(arg);
    }
  }

  return { command, flags, positional, errors };
}

/**
 * Run the onboarding wizard for first-time users
 */
async function runOnboardingWizard(): Promise<void> {
  const { OnboardingWizard } = await import("./components/onboarding/OnboardingWizard.tsx");
  let completedConfig: GitforestConfig | null = null;
  let cancelled = false;

  const handleComplete = (config: GitforestConfig) => {
    completedConfig = config;
  };

  const handleCancel = () => {
    cancelled = true;
  };

  // Render onboarding wizard
  const instance = render(
    <OnboardingWizard
      onComplete={handleComplete}
      onCancel={handleCancel}
      onUnmount={() => instance.unmount()}
    />
  );

  // Wait for completion or cancellation
  await instance.waitUntilExit();

  if (cancelled) {
    console.log("\nOnboarding cancelled. Run 'gitforest --init' to create a default config.");
    process.exit(0);
  }

  if (completedConfig) {
    // Start main TUI app
    await initDb();
    const { waitUntilExit: waitAppExit } = render(<App config={completedConfig} />);
    await waitAppExit();
    closeDb();
  }
}

async function main() {
  const args = process.argv.slice(2);
  const { command, flags, positional, errors } = parseArgs(args);

  if (errors.length > 0) {
    for (const error of errors) {
      console.error(`Error: ${error}`);
    }
    console.log("\nRun 'gitforest --help' for usage information.");
    process.exit(1);
  }

  // --init creates a default config and exits — bypasses the registry.
  if (flags["init"]) {
    const existingConfig = findConfigPath();
    if (existingConfig) {
      console.log(`Config already exists at: ${existingConfig}`);
      process.exit(1);
    }
    const configPath = await createDefaultConfig();
    console.log(`Created default config at: ${configPath}`);
    console.log("\nEdit the config to add your project directories, then run 'gitforest' again.");
    process.exit(0);
  }

  // --help prints help and exits, with or without a command.
  if (flags["help"]) {
    console.log(renderHelp());
    process.exit(0);
  }

  // First-time-run path: no config and no command → onboarding wizard.
  const configPath = findConfigPath();
  if (!configPath && !command) {
    await runOnboardingWizard();
    return;
  }

  try {
    const config = await loadConfig();

    if (command) {
      const spec = findCommand(command);
      if (!spec) {
        console.error(`Unknown command: ${command}`);
        console.error("\nRun 'gitforest --help' for usage information.");
        process.exit(1);
      }

      const flagErrors = validateFlags(spec, flags);
      if (flagErrors.length > 0) {
        for (const error of flagErrors) {
          console.error(`Error: ${error}`);
        }
        process.exit(1);
      }

      await spec.run({ config, flags, positional });
      process.exit(0);
    }

    // TUI mode — no command specified.
    await initDb();
    const { waitUntilExit } = render(<App config={config} />);
    await waitUntilExit();
    closeDb();
  } catch (error) {
    console.error("Error:", error instanceof Error ? error.message : error);

    if (error instanceof Error && error.message.includes("No config file found")) {
      console.log("\nRun 'gitforest' to start the onboarding wizard,");
      console.log("or 'gitforest --init' to create a default config file.");
    }

    process.exit(1);
  }
}

// Tree-shaking guard: keep a reference to COMMANDS so the registry module is
// not dropped by an aggressive bundler. The registry's run() callbacks are
// what actually exercise the CLI handlers.
void COMMANDS;

main();
