// SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-split
// Moved mechanically from scripts/src/gh.ts:614-978.

import { BOARD_GITHUB_STATUSES, BOARD_MUTATION_SECTIONS, BOARD_ROW_FIELDS, BODY_UPDATE_MODES, CODE_QUEUE_BOARD_TARGET_ISSUE, DEFAULT_REPO, DEFAULT_STALE_CLOSE_INACTIVE_HOURS, GH_FLAG_OPTIONS, GH_VALUE_OPTIONS, ISSUE_LIST_JSON_FIELDS, ISSUE_LIST_STATES, ISSUE_VIEW_JSON_FIELDS, MAX_ISSUE_LIST_LIMIT, MAX_PR_FILES_LIMIT, MAX_STALE_CLOSE_INACTIVE_HOURS, PR_CLOSEOUT_JSON_FIELDS, PR_CLOSEOUT_VIEW_JSON, PR_LIST_JSON_FIELDS, PR_READ_JSON_FIELDS } from "./types";
import type { BoardGithubStatus, BoardMutationSection, BoardRowField, BoardRowUpsertValues, BodyUpdateMode, GitHubOptions, IssueBodyProfileOption, IssueListJsonField, IssueListState, IssueViewJsonField, PrListJsonField, PrListState, PrReadJsonField, PullRequestMergeMethod, RepoVisibility } from "./types";

export function optionValue(args: string[], name: string): string | undefined {
  const index = args.indexOf(name);
  if (index === -1) return undefined;
  const value = args[index + 1];
  if (value === undefined || value.length === 0 || value.startsWith("--")) throw new Error(`${name} requires a value`);
  return value;
}

export function optionValues(args: string[], name: string): string[] {
  const values: string[] = [];
  for (let index = 0; index < args.length; index += 1) {
    if (args[index] !== name) continue;
    const value = args[index + 1];
    if (value === undefined || value.length === 0 || value.startsWith("--")) throw new Error(`${name} requires a value`);
    values.push(value);
    index += 1;
  }
  return values;
}

export function hasFlag(args: string[], name: string): boolean {
  return args.includes(name);
}

export function optionWasProvided(args: string[], name: string): boolean {
  return args.includes(name);
}

export function commaListOption(args: string[], name: string): string[] | undefined {
  const raw = optionValue(args, name);
  if (raw === undefined) return undefined;
  const values = raw.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
  if (values.length === 0) throw new Error(`${name} requires at least one comma-separated field`);
  return values;
}

export function labelsOption(args: string[]): string[] {
  const rawValues = optionValues(args, "--label");
  const labels: string[] = [];
  const seen = new Set<string>();
  for (const raw of rawValues) {
    const parts = raw.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
    if (parts.length === 0) throw new Error("--label requires at least one non-empty label");
    for (const label of parts) {
      if (seen.has(label)) continue;
      labels.push(label);
      seen.add(label);
    }
  }
  return labels;
}

export function positiveIntegerValuesOption(args: string[], name: string): number[] {
  const values: number[] = [];
  const seen = new Set<number>();
  for (const raw of optionValues(args, name)) {
    const parts = raw.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
    if (parts.length === 0) throw new Error(`${name} requires at least one positive integer`);
    for (const part of parts) {
      const value = Number(part.replace(/^#/u, ""));
      if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} values must be positive issue numbers`);
      if (seen.has(value)) continue;
      values.push(value);
      seen.add(value);
    }
  }
  return values;
}

export function positiveIntegerSingleOption(args: string[], name: string, defaultValue: number): number {
  const raw = optionValue(args, name);
  if (raw === undefined) return defaultValue;
  const value = Number(raw.replace(/^#/u, ""));
  if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive issue number`);
  return value;
}

export function positiveIntegerOption(args: string[], name: string, defaultValue: number, maxValue: number): number {
  const raw = optionValue(args, name);
  if (raw === undefined) return defaultValue;
  const value = Number(raw);
  if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
  return Math.min(value, maxValue);
}

export function positiveNumberOption(args: string[], name: string, defaultValue: number, maxValue: number): number {
  const raw = optionValue(args, name);
  if (raw === undefined) return defaultValue;
  const value = Number(raw);
  if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive number`);
  return Math.min(value, maxValue);
}

export function validateEnumValue<T extends string>(name: string, raw: string, allowedValues: readonly T[]): T {
  if ((allowedValues as readonly string[]).includes(raw)) return raw as T;
  throw new Error(`unsupported ${name} ${raw}; supported values: ${allowedValues.join(",")}`);
}

export function validateJsonFields<T extends string>(command: string, requested: string[] | undefined, allowedFields: readonly T[]): T[] | undefined {
  if (requested === undefined) return undefined;
  const allowed = new Set<string>(allowedFields);
  const unsupported = requested.filter((field) => !allowed.has(field));
  if (unsupported.length > 0) {
    throw new Error(`unsupported ${command} --json field(s): ${unsupported.join(", ")}; supported fields: ${allowedFields.join(",")}`);
  }
  return requested as T[];
}

export function parseIssueViewJsonFields(requested: string[] | undefined): IssueViewJsonField[] | undefined {
  return validateJsonFields("gh issue read/view", requested, ISSUE_VIEW_JSON_FIELDS);
}

export function parseIssueListJsonFields(requested: string[] | undefined): IssueListJsonField[] | undefined {
  return validateJsonFields("gh issue list", requested, ISSUE_LIST_JSON_FIELDS);
}

export function parsePrListJsonFields(requested: string[] | undefined): PrListJsonField[] | undefined {
  if (requested !== undefined) {
    const listFields = new Set<string>(PR_LIST_JSON_FIELDS);
    const closeoutFields = requested.filter((field) => (PR_CLOSEOUT_JSON_FIELDS as readonly string[]).includes(field));
    const unsupported = requested.filter((field) => !listFields.has(field));
    if (closeoutFields.length > 0) {
      throw new Error(`unsupported gh pr list --json closeout field(s): ${closeoutFields.join(", ")}; use gh pr view <number> --json ${PR_CLOSEOUT_VIEW_JSON} for mergeability/statusCheckRollup metadata; pr list supported fields: ${PR_LIST_JSON_FIELDS.join(",")}`);
    }
    if (unsupported.length > 0) {
      throw new Error(`unsupported gh pr list --json field(s): ${unsupported.join(", ")}; supported fields: ${PR_LIST_JSON_FIELDS.join(",")}; closeout metadata requires gh pr view <number> --json ${PR_CLOSEOUT_VIEW_JSON}`);
    }
  }
  return validateJsonFields("gh pr list", requested, PR_LIST_JSON_FIELDS);
}

export function parsePrReadJsonFields(requested: string[] | undefined): PrReadJsonField[] | undefined {
  return validateJsonFields("gh pr read/view", requested, PR_READ_JSON_FIELDS);
}

export function isIssueReadCommand(sub: string | undefined): boolean {
  return sub === "read" || sub === "view";
}

export function isPrReadCommand(sub: string | undefined): boolean {
  return sub === "read" || sub === "view";
}

export function allowsNumberTargetAlias(top: string | undefined, sub: string | undefined, third: string | undefined): boolean {
  if (top === "preflight") return true;
  if (top === "issue") {
    if (sub === "read" || sub === "view" || sub === "edit" || sub === "update" || sub === "patch" || sub === "close" || sub === "reopen" || sub === "delete") return true;
    if (sub === "comment") return true;
    if (sub === "attachment" && (third === "list" || third === "download")) return true;
    if (sub === "board-row" && ["get", "update", "add", "move", "delete", "upsert"].includes(third ?? "")) return true;
    return false;
  }
  if (top === "pr") {
    if (sub === "read" || sub === "view" || sub === "files" || sub === "diff" || sub === "preflight" || sub === "closeout" || sub === "edit" || sub === "update" || sub === "close" || sub === "reopen" || sub === "merge" || sub === "delete") return true;
    if (sub === "comment") return true;
  }
  return false;
}

export function parseIssueListState(args: string[]): IssueListState {
  const raw = optionValue(args, "--state") ?? "open";
  if ((ISSUE_LIST_STATES as readonly string[]).includes(raw)) return raw as IssueListState;
  throw new Error(`unsupported gh issue list --state ${raw}; supported states: ${ISSUE_LIST_STATES.join(",")}`);
}

export function parsePrListState(args: string[]): PrListState {
  const raw = optionValue(args, "--state") ?? "all";
  if ((ISSUE_LIST_STATES as readonly string[]).includes(raw)) return raw as PrListState;
  throw new Error(`unsupported gh pr list --state ${raw}; supported states: ${ISSUE_LIST_STATES.join(",")}`);
}

export function parseBodyUpdateMode(args: string[]): BodyUpdateMode {
  const raw = optionValue(args, "--mode") ?? "replace";
  if ((BODY_UPDATE_MODES as readonly string[]).includes(raw)) return raw as BodyUpdateMode;
  throw new Error(`unsupported --mode ${raw}; supported modes: ${BODY_UPDATE_MODES.join(",")}`);
}

export function parsePullRequestMergeMethod(args: string[]): PullRequestMergeMethod {
  const selected = ["merge", "squash", "rebase"].filter((method) => hasFlag(args, `--${method}`));
  if (selected.length > 1) throw new Error("choose only one PR merge method flag: --merge, --squash, or --rebase");
  return (selected[0] ?? "merge") as PullRequestMergeMethod;
}

export function parseIssueBodyProfile(args: string[]): IssueBodyProfileOption {
  const raw = optionValue(args, "--body-profile") ?? "auto";
  if (raw === "auto" || raw === "code-queue-board" || raw === "commander-brief") return raw;
  throw new Error(`unsupported --body-profile ${raw}; supported profiles: auto, code-queue-board, commander-brief`);
}

export function parseBoardMutationSection(args: string[], name: "--section" | "--to"): BoardMutationSection | undefined {
  const raw = optionValue(args, name);
  if (raw === undefined) return undefined;
  return validateEnumValue(name, raw, BOARD_MUTATION_SECTIONS);
}

export function parseBoardGithubStatus(args: string[]): BoardGithubStatus | undefined {
  const raw = optionValue(args, "--status");
  if (raw === undefined) return undefined;
  return validateEnumValue("--status", raw, BOARD_GITHUB_STATUSES);
}

export function parseBoardRowField(args: string[]): BoardRowField | undefined {
  const raw = optionValue(args, "--field");
  if (raw === undefined) return undefined;
  return validateEnumValue("--field", raw, BOARD_ROW_FIELDS);
}

export function parseBoardRowUpsertValues(args: string[]): BoardRowUpsertValues {
  return {
    category: optionValue(args, "--category"),
    branch: optionValue(args, "--branch"),
    tasks: optionValue(args, "--tasks"),
    summary: optionValue(args, "--summary"),
    focus: optionValue(args, "--focus"),
    validation: optionValue(args, "--validation"),
    progress: optionValue(args, "--progress"),
    status: parseBoardGithubStatus(args),
  };
}

export function parseRepoVisibility(args: string[]): RepoVisibility | undefined {
  const privateRequested = hasFlag(args, "--private");
  const publicRequested = hasFlag(args, "--public");
  if (privateRequested && publicRequested) throw new Error("gh repo create accepts either --private or --public, not both");
  if (privateRequested) return "private";
  if (publicRequested) return "public";
  return undefined;
}

export function validateKnownOptions(args: string[]): void {
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index];
    if (!arg.startsWith("--")) continue;
    if (GH_FLAG_OPTIONS.has(arg)) continue;
    if (GH_VALUE_OPTIONS.has(arg)) {
      index += 1;
      continue;
    }
    throw new Error(`unknown gh option: ${arg}`);
  }
}

export function positionalArgs(args: string[]): string[] {
  const positionals: string[] = [];
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index] ?? "";
    if (arg.startsWith("--")) {
      if (GH_VALUE_OPTIONS.has(arg)) index += 1;
      continue;
    }
    positionals.push(arg);
  }
  return positionals;
}

export function isOwnerRepo(value: string): boolean {
  return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(value);
}

export function resolveRepoOption(args: string[]): string {
  const [top, sub] = args;
  const explicitRepo = optionValue(args, "--repo");
  if (top === "repo" && (sub === "view" || sub === "create")) {
    const positionals = positionalArgs(args.slice(2));
    if (positionals.length > 1) {
      throw new Error(`gh repo ${sub} accepts at most one positional owner/repo target`);
    }
    const positionalRepo = positionals[0];
    if (positionalRepo !== undefined) {
      if (!isOwnerRepo(positionalRepo)) {
        throw new Error(`gh repo ${sub} positional argument must be owner/repo`);
      }
      if (explicitRepo !== undefined && explicitRepo !== positionalRepo) {
        throw new Error(`gh repo ${sub} received positional repo ${positionalRepo} and --repo ${explicitRepo}; use one repo target`);
      }
      return positionalRepo;
    }
  }
  if ((top === "issue" || top === "pr") && sub === "list") {
    const positionals = positionalArgs(args.slice(2));
    if (positionals.length > 1) {
      throw new Error(`gh ${top} list accepts at most one positional owner/repo; use --repo owner/name`);
    }
    const positionalRepo = positionals[0];
    if (positionalRepo !== undefined) {
      if (!isOwnerRepo(positionalRepo)) {
        throw new Error(`gh ${top} list positional argument must be owner/repo; use --repo owner/name`);
      }
      if (explicitRepo !== undefined && explicitRepo !== positionalRepo) {
        throw new Error(`gh ${top} list received positional repo ${positionalRepo} and --repo ${explicitRepo}; use one repo target, for example --repo ${positionalRepo}`);
      }
      return positionalRepo;
    }
  }
  return explicitRepo ?? DEFAULT_REPO;
}

export function stdinAliasFileOption(args: string[], fileOption: string, stdinFlag: string): string | undefined {
  const file = optionValue(args, fileOption);
  const stdin = hasFlag(args, stdinFlag);
  if (file !== undefined && stdin) {
    throw new Error(`${fileOption} and ${stdinFlag} are mutually exclusive; use one body source`);
  }
  return stdin ? "-" : file;
}

export function parseOptions(args: string[]): GitHubOptions {
  validateKnownOptions(args);
  const [top, sub] = args;
  const requestedJsonFields = commaListOption(args, "--json");
  const limitMax = top === "pr" && (sub === "files" || sub === "diff")
    ? MAX_PR_FILES_LIMIT
    : top === "issue" && (sub === "list" || sub === "stale-close" || sub === "scan-escape" || sub === "cleanup-plan")
      ? MAX_ISSUE_LIST_LIMIT
      : 100;
  return {
    repo: resolveRepoOption(args),
    dryRun: hasFlag(args, "--dry-run"),
    raw: hasFlag(args, "--raw"),
    full: hasFlag(args, "--full"),
    limit: positiveIntegerOption(args, "--limit", top === "issue" && sub === "board-audit" ? 100 : top === "issue" && sub === "stale-close" ? MAX_ISSUE_LIST_LIMIT : 30, limitMax),
    inactiveHours: positiveNumberOption(args, "--inactive-hours", DEFAULT_STALE_CLOSE_INACTIVE_HOURS, MAX_STALE_CLOSE_INACTIVE_HOURS),
    boardIssue: positiveIntegerSingleOption(args, "--board-issue", CODE_QUEUE_BOARD_TARGET_ISSUE),
    knownMetaIssues: positiveIntegerValuesOption(args, "--known-meta-issue"),
    ignoredIssues: positiveIntegerValuesOption(args, "--ignore-issue"),
    draft: hasFlag(args, "--draft"),
    notifyClaudeQqBriefDiff: hasFlag(args, "--notify-claudeqq-brief-diff"),
    allowShortBody: hasFlag(args, "--allow-short-body"),
    labels: labelsOption(args),
    search: optionValue(args, "--search"),
    titlePrefix: optionValue(args, "--title-prefix")?.trim(),
    title: optionValue(args, "--title"),
    body: optionValue(args, "--body"),
    bodyFile: stdinAliasFileOption(args, "--body-file", "--body-stdin"),
    bodyPatchFile: stdinAliasFileOption(args, "--body-patch-file", "--body-patch-stdin"),
    comment: optionValue(args, "--comment"),
    commentFile: stdinAliasFileOption(args, "--comment-file", "--comment-stdin"),
    repoDescription: optionValue(args, "--description"),
    repoVisibility: parseRepoVisibility(args),
    repoAutoInit: hasFlag(args, "--auto-init"),
    base: optionValue(args, "--base"),
    head: optionValue(args, "--head"),
    jsonFields: top === "issue" && isIssueReadCommand(sub) ? parseIssueViewJsonFields(requestedJsonFields) : undefined,
    issueListJsonFields: top === "issue" && sub === "list" ? parseIssueListJsonFields(requestedJsonFields) : undefined,
    prListJsonFields: top === "pr" && sub === "list" ? parsePrListJsonFields(requestedJsonFields) : undefined,
    prJsonFields: top === "pr" && isPrReadCommand(sub) ? parsePrReadJsonFields(requestedJsonFields) : undefined,
    listState: top === "issue" && (sub === "list" || sub === "board-row" && args[2] === "list") ? parseIssueListState(args) : "open",
    prListState: top === "pr" && sub === "list" ? parsePrListState(args) : "all",
    mode: parseBodyUpdateMode(args),
    expectUpdatedAt: optionValue(args, "--expect-updated-at"),
    expectBodySha: optionValue(args, "--expect-body-sha"),
    bodyProfile: parseIssueBodyProfile(args),
    boardRowField: parseBoardRowField(args),
    boardRowValue: optionValue(args, "--value"),
    boardRowFile: optionValue(args, "--row-file"),
    boardSection: parseBoardMutationSection(args, "--section"),
    boardMoveTo: parseBoardMutationSection(args, "--to"),
    boardGithubStatus: parseBoardGithubStatus(args),
    boardRowUpsertValues: parseBoardRowUpsertValues(args),
    mergeMethod: parsePullRequestMergeMethod(args),
    deleteBranch: hasFlag(args, "--delete-branch"),
    attachmentSelector: optionValue(args, "--attachment"),
    outputPath: optionValue(args, "--output"),
  };
}
