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

import { validationError } from "./client";
import { optionValue, optionWasProvided, positionalArgs } from "./options";
import { ISSUE_VIEW_JSON_FIELDS, PR_READ_JSON_FIELDS } from "./types";
import type { GitHubCommandResult, GitHubOptions, GitHubResolvedNumberReference, GitHubShorthandReference, IssueViewJsonField, PrReadJsonField } from "./types";

export function parseNumber(raw: string | undefined, label: string): number {
  if (raw === undefined) throw new Error(`${label} requires a number`);
  const value = Number(raw);
  if (!Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
  return value;
}

export function parseNumberForCommand(repo: string, raw: string | undefined, label: string): number | GitHubCommandResult {
  try {
    return parseNumber(raw, label);
  } catch (error) {
    return validationError(label, repo, error instanceof Error ? error.message : String(error));
  }
}

export function parsePositionalNumberForCommand(repo: string, args: string[], startIndex: number, label: string): number | GitHubCommandResult {
  const targets = positionalArgs(args.slice(startIndex));
  if (targets.length !== 1) {
    return validationError(label, repo, `${label} requires exactly one positive integer positional argument`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${label} <number> --repo ${repo}`,
        `bun scripts/cli.ts gh ${label} --repo ${repo} <number>`,
      ],
    });
  }
  return parseNumberForCommand(repo, targets[0], label);
}

export function resolvedNumberOptionReference(kind: "issue" | "pr", command: string, repo: string, raw: string): GitHubResolvedNumberReference | GitHubCommandResult {
  const number = parseNumberForCommand(repo, raw, command);
  if (typeof number !== "number") return number;
  return {
    repo,
    number,
    shorthand: {
      input: `--number ${number}`,
      repo,
      number,
      source: "number-option",
      standardCommand: `bun scripts/cli.ts gh ${command} ${number} --repo ${repo}`,
      urlKind: kind,
    },
  };
}

export function resolvePositionalNumberReference(kind: "issue" | "pr", args: string[], startIndex: number, label: string, options: GitHubOptions): GitHubResolvedNumberReference | GitHubCommandResult {
  const targets = positionalArgs(args.slice(startIndex));
  const numberOption = optionValue(args, "--number");
  if (targets.length > 0 && numberOption !== undefined) {
    return validationError(label, options.repo, `${label} accepts either a positional numeric target or --number, not both`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${label} <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} --number <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} ${options.repo}#<number>`,
      ],
    });
  }
  if (numberOption !== undefined) return resolvedNumberOptionReference(kind, label, options.repo, numberOption);
  if (targets.length !== 1) {
    return validationError(label, options.repo, `${label} requires exactly one positive integer or owner/repo#number positional argument`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${label} <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} --number <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} ${options.repo}#<number>`,
      ],
    });
  }
  const shorthand = parseOwnerRepoNumberShorthand(targets[0]);
  if (shorthand !== null) {
    const explicitRepo = optionValue(args, "--repo");
    if (explicitRepo !== undefined && explicitRepo !== shorthand.repo) {
      return validationError(label, explicitRepo, `${label} target ${shorthand.input} resolves to repo ${shorthand.repo}, but --repo ${explicitRepo} was also provided.`, {
        shorthand,
        explicitRepo,
      });
    }
    return { repo: shorthand.repo, number: shorthand.number, shorthand };
  }
  const number = parseNumberForCommand(options.repo, targets[0], label);
  if (typeof number !== "number") return number;
  return { repo: options.repo, number };
}

export function resolvePositionalPrReference(args: string[], startIndex: number, label: string, options: GitHubOptions): GitHubResolvedNumberReference | GitHubCommandResult {
  const targets = positionalArgs(args.slice(startIndex));
  const prOption = optionValue(args, "--pr");
  const numberOption = optionValue(args, "--number");
  if (prOption !== undefined && numberOption !== undefined) {
    return validationError(label, options.repo, `${label} accepts either --pr or --number as a compatibility alias, not both`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${label} <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} --number <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} ${options.repo}#<number>`,
      ],
    });
  }
  if (targets.length > 0 && (prOption !== undefined || numberOption !== undefined)) {
    return validationError(label, options.repo, `${label} accepts either a positional PR target or a compatibility number option, not both`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${label} <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} --pr <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} --number <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} ${options.repo}#<number>`,
      ],
    });
  }
  if (numberOption !== undefined) return resolvedNumberOptionReference("pr", label, options.repo, numberOption);
  const effectiveTargets = prOption !== undefined ? [prOption] : targets;
  if (effectiveTargets.length !== 1) {
    return validationError(label, options.repo, `${label} requires exactly one positive integer or owner/repo#number positional argument`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${label} <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} --pr <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} --number <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} ${options.repo}#<number>`,
      ],
    });
  }
  const shorthand = parseOwnerRepoNumberShorthand(effectiveTargets[0]);
  if (shorthand !== null) {
    const explicitRepo = optionValue(args, "--repo");
    if (explicitRepo !== undefined && explicitRepo !== shorthand.repo) {
      return validationError(label, explicitRepo, `${label} target ${shorthand.input} resolves to repo ${shorthand.repo}, but --repo ${explicitRepo} was also provided.`, {
        shorthand,
        explicitRepo,
      });
    }
    return { repo: shorthand.repo, number: shorthand.number, shorthand };
  }
  const number = parseNumberForCommand(options.repo, effectiveTargets[0], label);
  if (typeof number !== "number") return number;
  return { repo: options.repo, number };
}

export function resolvePositionalIssueReference(args: string[], startIndex: number, label: string, options: GitHubOptions, allowNumberOption = true): GitHubResolvedNumberReference | GitHubCommandResult {
  const targets = positionalArgs(args.slice(startIndex));
  const numberOption = allowNumberOption ? optionValue(args, "--number") : undefined;
  if (targets.length > 0 && numberOption !== undefined) {
    return validationError(label, options.repo, `${label} accepts either a positional issue target or --number, not both`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${label} <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} --number <number> --repo ${options.repo}`,
        `bun scripts/cli.ts gh ${label} ${options.repo}#<number>`,
      ],
    });
  }
  if (numberOption !== undefined) return resolvedNumberOptionReference("issue", label, options.repo, numberOption);
  if (targets.length !== 1) {
    return validationError(label, options.repo, `${label} requires exactly one positive integer or owner/repo#number positional argument`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${label} <number> --repo ${options.repo}`,
        ...(allowNumberOption ? [`bun scripts/cli.ts gh ${label} --number <number> --repo ${options.repo}`] : []),
        `bun scripts/cli.ts gh ${label} ${options.repo}#<number>`,
      ],
    });
  }
  const shorthand = parseOwnerRepoNumberShorthand(targets[0]);
  if (shorthand !== null) {
    const explicitRepo = optionValue(args, "--repo");
    if (explicitRepo !== undefined && explicitRepo !== shorthand.repo) {
      return validationError(label, explicitRepo, `${label} target ${shorthand.input} resolves to repo ${shorthand.repo}, but --repo ${explicitRepo} was also provided.`, {
        shorthand,
        explicitRepo,
      });
    }
    return { repo: shorthand.repo, number: shorthand.number, shorthand };
  }
  const number = parseNumberForCommand(options.repo, targets[0], label);
  if (typeof number !== "number") return number;
  return { repo: options.repo, number };
}

export function parseOwnerRepoNumberShorthand(raw: string | undefined): GitHubShorthandReference | null {
  if (raw === undefined) return null;
  const match = /^([^/#\s]+)\/([^/#\s]+)#([1-9]\d*)$/u.exec(raw);
  if (match === null) return null;
  return {
    input: raw,
    repo: `${match[1]}/${match[2]}`,
    number: Number(match[3]),
    source: "owner-repo-number",
  };
}

export function parseGitHubIssueOrPrUrl(raw: string | undefined): GitHubShorthandReference | null {
  if (raw === undefined) return null;
  let parsed: URL;
  try {
    parsed = new URL(raw);
  } catch {
    return null;
  }
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
  if (parsed.hostname.toLowerCase() !== "github.com" && parsed.hostname.toLowerCase() !== "www.github.com") return null;
  const parts = parsed.pathname.split("/").filter((part) => part.length > 0);
  if (parts.length < 4) return null;
  const [owner, repoName, kind, numberRaw] = parts;
  if (!owner || !repoName || (kind !== "issues" && kind !== "pull")) return null;
  if (!/^[1-9]\d*$/u.test(numberRaw ?? "")) return null;
  return {
    input: raw,
    repo: `${owner}/${repoName}`,
    number: Number(numberRaw),
    source: "github-url",
    urlKind: kind === "issues" ? "issue" : "pr",
  };
}

export function parseReadViewTarget(raw: string | undefined): GitHubShorthandReference | null {
  return parseOwnerRepoNumberShorthand(raw) ?? parseGitHubIssueOrPrUrl(raw);
}

export function readViewSupportedCommands(kind: "issue" | "pr", repo: string, number: number): string[] {
  return [
    `bun scripts/cli.ts gh ${kind} view ${number} --repo ${repo} --json ${readViewSupportedJsonFields(kind)}`,
    `bun scripts/cli.ts gh ${kind} view ${repo}#${number} --raw`,
    `bun scripts/cli.ts gh ${kind} read ${number} --repo ${repo} --json ${readViewSupportedJsonFields(kind)} [compatibility alias]`,
  ];
}

export function readViewSupportedJsonFields(kind: "issue" | "pr"): string {
  return kind === "issue"
    ? ISSUE_VIEW_JSON_FIELDS.join(",")
    : "body,title,state,stateDetail,closed,closedAt,merged,mergedAt,mergeCommit,head,base,draft,headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup";
}

export function resolveReadViewNumberReference(kind: "issue" | "pr", sub: "read" | "view", _raw: string | undefined, options: GitHubOptions, args: string[]): GitHubResolvedNumberReference | GitHubCommandResult {
  const command = `${kind} ${sub}`;
  const targets = positionalArgs(args.slice(2));
  if (targets.length > 1) {
    return validationError(command, options.repo, `${command} accepts one positional target: number, GitHub ${kind} URL, or owner/repo#number`, {
      supportedCommands: [
        `bun scripts/cli.ts gh ${kind} view <number> --repo owner/name --json ${readViewSupportedJsonFields(kind)}`,
        `bun scripts/cli.ts gh ${kind} view https://github.com/owner/name/${kind === "issue" ? "issues" : "pull"}/<number> --raw`,
        `bun scripts/cli.ts gh ${kind} view owner/name#<number> --raw`,
      ],
    });
  }
  const raw = targets[0];
  if (optionWasProvided(args, "--number")) {
    const numberAliasRaw = optionValue(args, "--number");
    const aliasNumber = parseNumberForCommand(options.repo, numberAliasRaw, command);
    if (typeof aliasNumber !== "number") return aliasNumber;
    if (raw !== undefined) {
      const parsedRaw = parseNumberForCommand(options.repo, raw, command);
      if (typeof parsedRaw !== "number") return parsedRaw;
      if (parsedRaw !== aliasNumber) {
        return validationError(command, options.repo, `${command} positional number ${parsedRaw} conflicts with --number ${aliasNumber}; use one target number.`, {
          positionalNumber: parsedRaw,
          numberAlias: aliasNumber,
          supportedCommands: readViewSupportedCommands(kind, options.repo, aliasNumber),
        });
      }
    }
    return {
      repo: options.repo,
      number: aliasNumber,
      shorthand: {
        input: `--number ${aliasNumber}`,
        repo: options.repo,
        number: aliasNumber,
        source: "number-option",
        standardCommand: `bun scripts/cli.ts gh ${kind} view ${aliasNumber} --repo ${options.repo}`,
      },
    };
  }
  const shorthand = parseReadViewTarget(raw);
  if (shorthand !== null) {
    if (shorthand.urlKind !== undefined && shorthand.urlKind !== kind) {
      return validationError(command, shorthand.repo, `${command} target ${shorthand.input} is a GitHub ${shorthand.urlKind} URL, not a ${kind} URL.`, {
        shorthand,
        supportedCommands: readViewSupportedCommands(shorthand.urlKind, shorthand.repo, shorthand.number),
      });
    }
    const explicitRepo = optionValue(args, "--repo");
    if (explicitRepo !== undefined && explicitRepo !== shorthand.repo) {
      const message = `${command} target ${shorthand.input} resolves to repo ${shorthand.repo}, but --repo ${explicitRepo} was also provided. Use either the shorthand or a matching --repo, not both.`;
      return validationError(command, explicitRepo, message, {
        message,
        shorthand,
        explicitRepo,
        supportedCommands: readViewSupportedCommands(kind, shorthand.repo, shorthand.number),
      });
    }
    return { repo: shorthand.repo, number: shorthand.number, shorthand };
  }
  const parsed = parseNumberForCommand(options.repo, raw, command);
  if (typeof parsed !== "number") {
    return {
      ...parsed,
      supportedCommands: [
        `bun scripts/cli.ts gh ${kind} view <number> --repo owner/name --json ${readViewSupportedJsonFields(kind)}`,
        `bun scripts/cli.ts gh ${kind} view https://github.com/owner/name/${kind === "issue" ? "issues" : "pull"}/<number> --raw`,
        `bun scripts/cli.ts gh ${kind} view owner/name#<number> --raw`,
      ],
    };
  }
  return { repo: options.repo, number: parsed };
}

export function issueReadJsonFields(options: GitHubOptions): IssueViewJsonField[] | undefined {
  return options.jsonFields ?? (options.raw || options.full ? ISSUE_VIEW_JSON_FIELDS.slice() : undefined);
}

export function prReadJsonFields(options: GitHubOptions): PrReadJsonField[] | undefined {
  return options.prJsonFields ?? (options.raw || options.full ? PR_READ_JSON_FIELDS.slice() : undefined);
}

export function readDisclosureOptions(options: GitHubOptions, shorthand: GitHubShorthandReference | undefined): Record<string, unknown> | null {
  if (!options.raw && !options.full && shorthand === undefined) return null;
  return {
    ...(options.raw ? { raw: true } : {}),
    ...(options.full ? { full: true } : {}),
    fullDisclosure: options.raw || options.full,
    shorthand: shorthand ?? null,
    ...(shorthand?.source === "number-option" ? {
      compatibilityHint: "--number is accepted for low-friction compatibility; standard gh syntax uses a positional number or URL.",
      standardCommand: shorthand.standardCommand,
    } : {}),
  };
}

export function numberOptionCompatibilityHint(resolved: GitHubResolvedNumberReference): Record<string, unknown> | null {
  if (resolved.shorthand?.source !== "number-option") return null;
  return {
    acceptedOption: "--number",
    compatibility: true,
    message: "--number is accepted for low-friction compatibility; prefer the standard positional number target shown in standardCommand.",
    standardCommand: resolved.shorthand.standardCommand ?? null,
  };
}

export async function withNumberOptionHint(result: GitHubCommandResult | Promise<GitHubCommandResult>, resolved: GitHubResolvedNumberReference): Promise<GitHubCommandResult> {
  const output = await result;
  const hint = numberOptionCompatibilityHint(resolved);
  if (hint === null) return output;
  return {
    ...output,
    standardSyntaxHint: hint,
  };
}

export function isGitHubCommandResult(value: unknown): value is GitHubCommandResult {
  return typeof (value as { ok?: unknown }).ok === "boolean";
}

export function unknownGhOptionDetails(args: string[], option: string): Record<string, unknown> {
  const [top, sub, third] = args;
  const details: Record<string, unknown> = {
    unsupportedOption: option,
    helpCommand: "bun scripts/cli.ts gh help",
  };
  if ((top === "issue" || top === "pr") && (sub === "read" || sub === "view")) {
    const shorthand = parseReadViewTarget(third);
    const repo = shorthand?.repo ?? optionValue(args, "--repo") ?? "owner/name";
    const number = shorthand?.number ?? (third !== undefined && /^\d+$/u.test(third) ? Number(third) : 0);
    details.supportedCommands = number > 0
      ? readViewSupportedCommands(top, repo, number)
      : [
        `bun scripts/cli.ts gh ${top} view <number> --repo owner/name --json ${readViewSupportedJsonFields(top)}`,
        `bun scripts/cli.ts gh ${top} view owner/name#<number> --raw`,
      ];
  }
  return details;
}
