export type VerifyOptions = {
  scopes: string[];
  configPath?: string;
  outputJson: boolean;
  seed?: string;
  verbose: boolean;
  hook: boolean;
  changedPaths: string[];
  report: boolean;
  reportMode?: "interactive" | "static";
  noOpen: boolean;
  lane?: "focused" | "full" | "deep";
};

export type DiscoverOptions = {
  configPath?: string;
  outputJson: boolean;
  verbose: boolean;
  report: boolean;
  reportMode?: "interactive" | "static";
  noOpen: boolean;
};

export type DoctorOptions = {
  configPath?: string;
  outputJson: boolean;
  verbose: boolean;
};

export type DecisionsExportOptions = {
  reportId: string;
  configPath?: string;
  outputJson: boolean;
};

export type DecisionsImportOptions = {
  bundlePath: string;
  configPath?: string;
  outputJson: boolean;
  confirmedBundleHash?: string;
  reviewer?: string;
};

export type DecisionsRevokeOptions = {
  decisionId: string;
  configPath?: string;
  outputJson: boolean;
};

export type ContractsListOptions = { configPath?: string; outputJson: boolean };
export type ContractsShowOptions = ContractsListOptions & { contractId: string };
export type ContractsCalibrateOptions = ContractsShowOptions;
export type ContractsPromoteOptions = ContractsShowOptions;
export type ContractsRetireOptions = ContractsShowOptions & { reason: string };
export type BaselineOptions = { scopes: string[]; configPath?: string; outputJson: boolean };
export type ReportOptions = { runId: string; instance?: string; configPath?: string; outputJson: boolean; reportMode?: "interactive" | "static"; noOpen: boolean };
export type FinalizeOptions = { reportId: string; configPath?: string; outputJson: boolean };
export type ServeOptions = { reportDir: string; port?: number; noOpen: boolean };

export type RegisteredCommand =
  | "report"
  | "decisions-export"
  | "decisions-import"
  | "decisions-revoke"
  | "contracts-list"
  | "contracts-show"
  | "contracts-calibrate"
  | "contracts-promote"
  | "contracts-retire"
  | "baseline-update"
  | "baseline-diff"
  | "finalize";

export type ParsedInvocation =
  | { ok: true; command: "verify"; options: VerifyOptions }
  | { ok: true; command: "discover"; options: DiscoverOptions }
  | { ok: true; command: "doctor"; options: DoctorOptions }
  | { ok: true; command: "init" }
  | { ok: true; command: "decisions-export"; options: DecisionsExportOptions }
  | { ok: true; command: "decisions-import"; options: DecisionsImportOptions }
  | { ok: true; command: "decisions-revoke"; options: DecisionsRevokeOptions }
  | { ok: true; command: "contracts-list"; options: ContractsListOptions }
  | { ok: true; command: "contracts-show"; options: ContractsShowOptions }
  | { ok: true; command: "contracts-calibrate"; options: ContractsCalibrateOptions }
  | { ok: true; command: "contracts-promote"; options: ContractsPromoteOptions }
  | { ok: true; command: "contracts-retire"; options: ContractsRetireOptions }
  | { ok: true; command: "baseline-update"; options: BaselineOptions }
  | { ok: true; command: "baseline-diff"; options: BaselineOptions }
  | { ok: true; command: "report"; options: ReportOptions }
  | { ok: true; command: "serve"; options: ServeOptions }
  | { ok: true; command: "finalize"; options: FinalizeOptions }
  | { ok: false; message: string };

const ROOT_USAGE = "usage: invariantum <init|discover|verify|report|serve|decisions|contracts|baseline|finalize|doctor>";
const VERIFY_USAGE = [
  "usage: invariantum verify [--scope=universal]... [--config=<path>] [--output=json]",
  "                      [--seed=<s>] [--verbose] [--hook --changed=<path>]... [--lane=focused|full|deep] [--report] [--report-mode=interactive|static] [--no-open]",
].join("\n");
const DISCOVER_USAGE = "usage: invariantum discover [--config=<path>] [--output=json] [--verbose] [--report] [--report-mode=interactive|static] [--no-open]";
const REPORT_USAGE = "usage: invariantum report <run-id> [--instance=<content-id>] [--config=<path>] [--output=json] [--report-mode=interactive|static] [--no-open]";
const FINALIZE_USAGE = "usage: invariantum finalize <report-id> [--config=<path>] [--output=json]";
const SERVE_USAGE = "usage: invariantum serve <report-dir> [--port=<port>] [--no-open]";
const DOCTOR_USAGE = "usage: invariantum doctor [--config=<path>] [--output=json] [--verbose]";
const DECISIONS_EXPORT_USAGE = "usage: invariantum decisions export <report-id> [--config=<path>] [--output=json]";
const DECISIONS_IMPORT_USAGE = "usage: invariantum decisions import <bundle> [--config=<path>] [--output=json] [--confirm-trusted-local-import=<bundleHash> --reviewer=<local-identity>]";
const DECISIONS_REVOKE_USAGE = "usage: invariantum decisions revoke <decision-id> [--config=<path>] [--output=json]";
const CONTRACTS_LIST_USAGE = "usage: invariantum contracts list [--config=<path>] [--output=json]";
const CONTRACTS_SHOW_USAGE = "usage: invariantum contracts show <contract-id> [--config=<path>] [--output=json]";
const CONTRACTS_CALIBRATE_USAGE = "usage: invariantum contracts calibrate <contract-id> [--config=<path>] [--output=json]";
const CONTRACTS_PROMOTE_USAGE = "usage: invariantum contracts promote <contract-id> [--config=<path>] [--output=json]";
const CONTRACTS_RETIRE_USAGE = "usage: invariantum contracts retire <contract-id> --reason=<reason> [--config=<path>] [--output=json]";
const BASELINE_USAGE = "usage: invariantum baseline update|diff [--scope=<scope>] [--config=<path>] [--output=json]";

function usageError(detail: string, usage = ROOT_USAGE): ParsedInvocation {
  return { ok: false, message: `${usage}\nerror: ${detail}` };
}

function exactCommand<C extends RegisteredCommand | "init">(
  rest: string[],
  command: C,
  usage: string,
): Extract<ParsedInvocation, { ok: true; command: C }> | { ok: false; message: string } {
  if (rest.length !== 0) return usageError("unexpected argument", usage) as { ok: false; message: string };
  return { ok: true, command } as Extract<ParsedInvocation, { ok: true; command: C }>;
}

function parseScopeValue(raw: string): { ok: false; message: string } | { ok: true; scope: string } {
  const value = raw.trim();
  if (value.length === 0) return { ok: false, message: "scope value must not be empty" };
  return value === "universal" || value === "feature" || value === "ui" ? { ok: true, scope: value } : { ok: false, message: "scope must be universal, feature, or ui" };
}

function parseEqualsFlag(arg: string, name: string): { ok: true; value: string } | { ok: false; message: string } {
  const prefix = `--${name}=`;
  if (!arg.startsWith(prefix)) return { ok: false, message: `expected ${prefix}<value>` };
  const value = arg.slice(prefix.length);
  return value.length === 0 ? { ok: false, message: `${name} value must not be empty` } : { ok: true, value };
}

function parseVerify(rest: string[]): ParsedInvocation {
  const options: VerifyOptions = { scopes: [], outputJson: false, verbose: false, hook: false, changedPaths: [], report: false, noOpen: false };
  for (const arg of rest) {
    if (arg === "--verbose") { options.verbose = true; continue; }
    if (arg === "--hook") { options.hook = true; continue; }
    if (arg === "--report") { options.report = true; continue; }
    if (arg === "--no-open") { options.noOpen = true; continue; }
    if (arg.startsWith("--lane=")) {
      const parsed = parseEqualsFlag(arg, "lane");
      if (!parsed.ok || (parsed.value !== "focused" && parsed.value !== "full" && parsed.value !== "deep")) return usageError("lane must be focused, full, or deep", VERIFY_USAGE);
      if (options.lane !== undefined) return usageError("duplicate --lane flag", VERIFY_USAGE);
      options.lane = parsed.value; continue;
    }
    if (arg.startsWith("--report-mode=")) {
      const parsed = parseEqualsFlag(arg, "report-mode");
      if (!parsed.ok || (parsed.value !== "interactive" && parsed.value !== "static")) return usageError("report-mode must be interactive or static", VERIFY_USAGE);
      if (options.reportMode !== undefined) return usageError("duplicate --report-mode flag", VERIFY_USAGE);
      options.reportMode = parsed.value; continue;
    }
    if (arg.startsWith("--scope=")) {
      const parsed = parseEqualsFlag(arg, "scope");
      if (!parsed.ok) return usageError(parsed.message, VERIFY_USAGE);
      const scope = parseScopeValue(parsed.value);
      if (!scope.ok) return usageError(scope.message, VERIFY_USAGE);
      options.scopes.push(scope.scope); continue;
    }
    if (arg.startsWith("--config=")) {
      const parsed = parseEqualsFlag(arg, "config");
      if (!parsed.ok) return usageError(parsed.message, VERIFY_USAGE);
      if (options.configPath !== undefined) return usageError("duplicate --config flag", VERIFY_USAGE);
      options.configPath = parsed.value; continue;
    }
    if (arg.startsWith("--seed=")) {
      const parsed = parseEqualsFlag(arg, "seed");
      if (!parsed.ok) return usageError(parsed.message, VERIFY_USAGE);
      if (options.seed !== undefined) return usageError("duplicate --seed flag", VERIFY_USAGE);
      options.seed = parsed.value; continue;
    }
    if (arg.startsWith("--changed=")) {
      const parsed = parseEqualsFlag(arg, "changed");
      if (!parsed.ok) return usageError(parsed.message, VERIFY_USAGE);
      options.changedPaths.push(parsed.value); continue;
    }
    if (arg === "--output=json") { options.outputJson = true; continue; }
    if (arg.startsWith("--output=")) return usageError(`unsupported output format: ${arg.slice("--output=".length)}`, VERIFY_USAGE);
    return usageError(arg.startsWith("-") ? `unknown flag: ${arg}` : `unexpected argument: ${arg}`, VERIFY_USAGE);
  }
  if (options.hook && options.changedPaths.length === 0) return usageError("--hook requires at least one --changed=<path>", VERIFY_USAGE);
  if (!options.report && (options.reportMode !== undefined || options.noOpen)) return usageError("--report-mode and --no-open require --report", VERIFY_USAGE);
  return { ok: true, command: "verify", options };
}

function parseDiscover(rest: string[]): ParsedInvocation {
  const options: DiscoverOptions = { outputJson: false, verbose: false, report: false, noOpen: false };
  for (const arg of rest) {
    if (arg === "--verbose") { options.verbose = true; continue; }
    if (arg === "--report") { options.report = true; continue; }
    if (arg === "--no-open") { options.noOpen = true; continue; }
    if (arg.startsWith("--report-mode=")) {
      const parsed = parseEqualsFlag(arg, "report-mode");
      if (!parsed.ok || (parsed.value !== "interactive" && parsed.value !== "static")) return usageError("report-mode must be interactive or static", DISCOVER_USAGE);
      if (options.reportMode !== undefined) return usageError("duplicate --report-mode flag", DISCOVER_USAGE);
      options.reportMode = parsed.value; continue;
    }
    if (arg === "--output=json") { options.outputJson = true; continue; }
    if (arg.startsWith("--output=")) return usageError(`unsupported output format: ${arg.slice("--output=".length)}`, DISCOVER_USAGE);
    if (arg.startsWith("--config=")) {
      const parsed = parseEqualsFlag(arg, "config");
      if (!parsed.ok) return usageError(parsed.message, DISCOVER_USAGE);
      if (options.configPath !== undefined) return usageError("duplicate --config flag", DISCOVER_USAGE);
      options.configPath = parsed.value;
      continue;
    }
    return usageError(arg.startsWith("-") ? `unknown flag: ${arg}` : `unexpected argument: ${arg}`, DISCOVER_USAGE);
  }
  if (!options.report && (options.reportMode !== undefined || options.noOpen)) return usageError("--report-mode and --no-open require --report", DISCOVER_USAGE);
  return { ok: true, command: "discover", options };
}

function parseReport(args: string[]): ParsedInvocation {
  const [runId, ...rest] = args;
  if (runId === undefined || runId.startsWith("-")) return usageError("expected run id", REPORT_USAGE);
  const options: ReportOptions = { runId, outputJson: false, noOpen: false };
  for (const arg of rest) {
    if (arg === "--output=json") { options.outputJson = true; continue; }
    if (arg === "--no-open") { options.noOpen = true; continue; }
    if (arg.startsWith("--config=")) { const parsed = parseEqualsFlag(arg, "config"); if (!parsed.ok) return usageError(parsed.message, REPORT_USAGE); if (options.configPath !== undefined) return usageError("duplicate --config flag", REPORT_USAGE); options.configPath = parsed.value; continue; }
    if (arg.startsWith("--report-mode=")) { const parsed = parseEqualsFlag(arg, "report-mode"); if (!parsed.ok || (parsed.value !== "interactive" && parsed.value !== "static")) return usageError("report-mode must be interactive or static", REPORT_USAGE); if (options.reportMode !== undefined) return usageError("duplicate --report-mode flag", REPORT_USAGE); options.reportMode = parsed.value; continue; }
    if (arg.startsWith("--instance=")) { const parsed = parseEqualsFlag(arg, "instance"); if (!parsed.ok) return usageError(parsed.message, REPORT_USAGE); if (options.instance !== undefined) return usageError("duplicate --instance flag", REPORT_USAGE); options.instance = parsed.value; continue; }
    return usageError(arg.startsWith("-") ? `unknown flag: ${arg}` : `unexpected argument: ${arg}`, REPORT_USAGE);
  }
  return { ok: true, command: "report", options };
}

function parseServe(args: string[]): ParsedInvocation {
  const [reportDir, ...rest] = args;
  if (reportDir === undefined || reportDir.startsWith("-")) return usageError("expected report directory", SERVE_USAGE);
  const options: ServeOptions = { reportDir, noOpen: false };
  for (const arg of rest) {
    if (arg === "--no-open") { options.noOpen = true; continue; }
    if (arg.startsWith("--port=")) {
      const parsed = parseEqualsFlag(arg, "port");
      if (!parsed.ok) return usageError(parsed.message, SERVE_USAGE);
      if (options.port !== undefined) return usageError("duplicate --port flag", SERVE_USAGE);
      const port = Number(parsed.value);
      if (!Number.isInteger(port) || port < 1 || port > 65535) return usageError("port must be an integer between 1 and 65535", SERVE_USAGE);
      options.port = port;
      continue;
    }
    return usageError(arg.startsWith("-") ? `unknown flag: ${arg}` : `unexpected argument: ${arg}`, SERVE_USAGE);
  }
  return { ok: true, command: "serve", options };
}

function parseFinalize(args: string[]): ParsedInvocation {
  const [reportId, ...rest] = args;
  if (reportId === undefined || reportId.startsWith("-")) return usageError("expected report id", FINALIZE_USAGE);
  const options: FinalizeOptions = { reportId, outputJson: false };
  const error = parseDecisionOptions(rest, FINALIZE_USAGE, options);
  return error === undefined ? { ok: true, command: "finalize", options } : usageError(error, FINALIZE_USAGE);
}

function parseDoctor(rest: string[]): ParsedInvocation {
  const options: DoctorOptions = { outputJson: false, verbose: false };
  for (const arg of rest) {
    if (arg === "--verbose") { options.verbose = true; continue; }
    if (arg === "--output=json") { options.outputJson = true; continue; }
    if (arg.startsWith("--output=")) return usageError(`unsupported output format: ${arg.slice("--output=".length)}`, DOCTOR_USAGE);
    if (arg.startsWith("--config=")) {
      const parsed = parseEqualsFlag(arg, "config");
      if (!parsed.ok) return usageError(parsed.message, DOCTOR_USAGE);
      if (options.configPath !== undefined) return usageError("duplicate --config flag", DOCTOR_USAGE);
      options.configPath = parsed.value;
      continue;
    }
    return usageError(arg.startsWith("-") ? `unknown flag: ${arg}` : `unexpected argument: ${arg}`, DOCTOR_USAGE);
  }
  return { ok: true, command: "doctor", options };
}

function parseDecisionOptions(
  rest: string[],
  usage: string,
  options: { configPath?: string; outputJson: boolean },
  extra?: (arg: string) => string | undefined,
): string | undefined {
  for (const arg of rest) {
    if (arg === "--output=json") { options.outputJson = true; continue; }
    if (arg.startsWith("--output=")) return `unsupported output format: ${arg.slice("--output=".length)}`;
    if (arg.startsWith("--config=")) {
      const parsed = parseEqualsFlag(arg, "config");
      if (!parsed.ok) return parsed.message;
      if (options.configPath !== undefined) return "duplicate --config flag";
      options.configPath = parsed.value;
      continue;
    }
    const extraError = extra?.(arg);
    if (extraError === undefined && extra !== undefined) continue;
    return arg.startsWith("-") ? `unknown flag: ${arg}` : `unexpected argument: ${arg}`;
  }
  void usage;
  return undefined;
}

function parseDecisionsExport(args: string[]): ParsedInvocation {
  const [reportId, ...rest] = args;
  if (reportId === undefined || reportId.startsWith("-")) return usageError("expected report id", DECISIONS_EXPORT_USAGE);
  const options: DecisionsExportOptions = { reportId, outputJson: false };
  const error = parseDecisionOptions(rest, DECISIONS_EXPORT_USAGE, options);
  return error === undefined ? { ok: true, command: "decisions-export", options } : usageError(error, DECISIONS_EXPORT_USAGE);
}

function parseDecisionsImport(args: string[]): ParsedInvocation {
  const [bundlePath, ...rest] = args;
  if (bundlePath === undefined || bundlePath.startsWith("-")) return usageError("expected bundle path", DECISIONS_IMPORT_USAGE);
  const options: DecisionsImportOptions = { bundlePath, outputJson: false };
  const error = parseDecisionOptions(rest, DECISIONS_IMPORT_USAGE, options, (arg) => {
    if (arg.startsWith("--confirm-trusted-local-import=")) {
      const parsed = parseEqualsFlag(arg, "confirm-trusted-local-import");
      if (!parsed.ok) return parsed.message;
      if (options.confirmedBundleHash !== undefined) return "duplicate --confirm-trusted-local-import flag";
      options.confirmedBundleHash = parsed.value;
      return undefined;
    }
    if (arg.startsWith("--reviewer=")) {
      const parsed = parseEqualsFlag(arg, "reviewer");
      if (!parsed.ok) return parsed.message;
      if (options.reviewer !== undefined) return "duplicate --reviewer flag";
      options.reviewer = parsed.value;
      return undefined;
    }
    return `unknown flag: ${arg}`;
  });
  if (error !== undefined) return usageError(error, DECISIONS_IMPORT_USAGE);
  if ((options.confirmedBundleHash === undefined) !== (options.reviewer === undefined)) {
    return usageError("noninteractive import requires both --confirm-trusted-local-import and --reviewer", DECISIONS_IMPORT_USAGE);
  }
  return { ok: true, command: "decisions-import", options };
}

function parseDecisionsRevoke(args: string[]): ParsedInvocation {
  const [decisionId, ...rest] = args;
  if (decisionId === undefined || decisionId.startsWith("-")) return usageError("expected decision id", DECISIONS_REVOKE_USAGE);
  const options: DecisionsRevokeOptions = { decisionId, outputJson: false };
  const error = parseDecisionOptions(rest, DECISIONS_REVOKE_USAGE, options);
  return error === undefined ? { ok: true, command: "decisions-revoke", options } : usageError(error, DECISIONS_REVOKE_USAGE);
}

function parseContractOptions(rest: string[], usage: string, options: ContractsListOptions, allowReason = false): string | undefined {
  for (const arg of rest) {
    if (arg === "--output=json") { options.outputJson = true; continue; }
    if (arg.startsWith("--output=")) return `unsupported output format: ${arg.slice("--output=".length)}`;
    if (arg.startsWith("--config=")) {
      const parsed = parseEqualsFlag(arg, "config");
      if (!parsed.ok) return parsed.message;
      if (options.configPath !== undefined) return "duplicate --config flag";
      options.configPath = parsed.value;
      continue;
    }
    if (allowReason && arg.startsWith("--reason=")) {
      const parsed = parseEqualsFlag(arg, "reason");
      if (!parsed.ok) return parsed.message;
      const retire = options as ContractsRetireOptions;
      if (retire.reason.length > 0) return "duplicate --reason flag";
      retire.reason = parsed.value;
      continue;
    }
    return arg.startsWith("-") ? `unknown flag: ${arg}` : `unexpected argument: ${arg}`;
  }
  void usage;
  return undefined;
}

function parseContractList(args: string[]): ParsedInvocation {
  const options: ContractsListOptions = { outputJson: false };
  const error = parseContractOptions(args, CONTRACTS_LIST_USAGE, options);
  return error === undefined ? { ok: true, command: "contracts-list", options } : usageError(error, CONTRACTS_LIST_USAGE);
}

function parseContractWithId(
  args: string[], command: "contracts-show" | "contracts-calibrate" | "contracts-promote", usage: string,
): ParsedInvocation {
  const [contractId, ...rest] = args;
  if (contractId === undefined || contractId.startsWith("-")) return usageError("expected contract id", usage);
  const options: ContractsShowOptions = { contractId, outputJson: false };
  const error = parseContractOptions(rest, usage, options);
  return error === undefined ? { ok: true, command, options } : usageError(error, usage);
}

function parseContractRetire(args: string[]): ParsedInvocation {
  const [contractId, ...rest] = args;
  if (contractId === undefined || contractId.startsWith("-")) return usageError("expected contract id", CONTRACTS_RETIRE_USAGE);
  const options: ContractsRetireOptions = { contractId, reason: "", outputJson: false };
  const error = parseContractOptions(rest, CONTRACTS_RETIRE_USAGE, options, true);
  if (error !== undefined) return usageError(error, CONTRACTS_RETIRE_USAGE);
  if (options.reason.trim().length === 0) return usageError("--reason is required", CONTRACTS_RETIRE_USAGE);
  return { ok: true, command: "contracts-retire", options };
}

function parseBaseline(subcommand: string | undefined, args: string[]): ParsedInvocation {
  if (subcommand !== "update" && subcommand !== "diff") {
    return usageError("expected baseline update|diff", BASELINE_USAGE);
  }
  const options: BaselineOptions = { scopes: [], outputJson: false };
  for (const arg of args) {
    if (arg.startsWith("--scope=")) {
      const parsed = parseEqualsFlag(arg, "scope");
      if (!parsed.ok) return usageError(parsed.message, BASELINE_USAGE);
      const scope = parseScopeValue(parsed.value);
      if (!scope.ok) return usageError(scope.message, BASELINE_USAGE);
      options.scopes.push(scope.scope); continue;
    }
    const error = parseDecisionOptions([arg], BASELINE_USAGE, options);
    if (error !== undefined) return usageError(error, BASELINE_USAGE);
  }
  return { ok: true, command: subcommand === "update" ? "baseline-update" : "baseline-diff", options };
}

export function parseArgs(argv: string[]): ParsedInvocation {
  const [command, ...rest] = argv;
  if (command === undefined || command.startsWith("-")) return usageError("missing command");
  if (command === "verify") return parseVerify(rest);
  if (command === "init") return exactCommand(rest, "init", "usage: invariantum init");
  if (command === "discover") return parseDiscover(rest);
  if (command === "report") return parseReport(rest);
  if (command === "serve") return parseServe(rest);
  if (command === "finalize") return parseFinalize(rest);
  if (command === "doctor") return parseDoctor(rest);
  if (command === "decisions") {
    const [subcommand, ...args] = rest;
    if (subcommand === "export") return parseDecisionsExport(args);
    if (subcommand === "import") return parseDecisionsImport(args);
    if (subcommand === "revoke") return parseDecisionsRevoke(args);
    return usageError("expected decisions export|import|revoke", "usage: invariantum decisions export|import|revoke <id>");
  }
  if (command === "contracts") {
    const [subcommand, ...args] = rest;
    if (subcommand === "list") return parseContractList(args);
    if (subcommand === "show") return parseContractWithId(args, "contracts-show", CONTRACTS_SHOW_USAGE);
    if (subcommand === "calibrate") return parseContractWithId(args, "contracts-calibrate", CONTRACTS_CALIBRATE_USAGE);
    if (subcommand === "promote") return parseContractWithId(args, "contracts-promote", CONTRACTS_PROMOTE_USAGE);
    if (subcommand === "retire") return parseContractRetire(args);
    return usageError("expected contracts list|show|calibrate|promote|retire", "usage: invariantum contracts <subcommand>");
  }
  if (command === "baseline") {
    const [subcommand, ...args] = rest;
    return parseBaseline(subcommand, args);
  }
  return usageError(`unknown command: ${command}`);
}

export function formatUsageError(message: string): string { return message; }
