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

import path from "node:path";
import { ghBinaryPath, repoParts, resolveToken } from "./auth-and-safety";
import { commandError, errorPayload, githubRequest, isGitHubError, runnerDisposition } from "./client";
import { issueRead } from "./issue-read";
import { needsPrGraphqlMetadata, numberOrNull, prCloseoutMetadata, prCloseoutMetadataError, prCompactSummary, prFileSummary, prGraphqlMetadata, prMetadataSummary, prSummary, selectedPrJson, sumPrFileStats } from "./pr-summary";
import { MAX_PR_FILES_LIMIT, PR_LIST_JSON_FIELDS } from "./types";
import type { GitHubCommandResult, GitHubDegradedReason, GitHubIssue, GitHubPullRequest, GitHubPullRequestFile, PrListJsonField, PrListState, PrReadJsonField } from "./types";

export async function authStatus(repo: string): Promise<GitHubCommandResult> {
  const ghPath = ghBinaryPath();
  const { token, probe } = resolveToken(ghPath !== null);
  const degraded: GitHubDegradedReason[] = [];
  if (ghPath === null) degraded.push("missing-binary");
  if (!probe.present || token === null) {
    if (ghPath === null) {
      return {
        ok: false,
        command: "auth status",
        repo,
        degradedReason: "missing-binary",
        degraded,
        runnerDisposition: runnerDisposition("missing-binary"),
        gh: { binaryFound: false, path: null },
        token: probe,
        probes: { restApi: "skipped", repo: "skipped", issueRead: "skipped" },
      };
    }
    degraded.push("missing-token");
    return commandError("auth status", repo, errorPayload("missing-token", "GH_TOKEN or GITHUB_TOKEN is required", { details: probe }), {
      degraded,
      gh: { binaryFound: ghPath !== null, path: ghPath },
      token: probe,
      probes: { restApi: "skipped", repo: "skipped", issueRead: "skipped" },
    });
  }

  const { owner, name } = repoParts(repo);
  const api = await githubRequest<Record<string, unknown>>(token, "GET", "/rate_limit");
  if (isGitHubError(api)) {
    return commandError("auth status", repo, api, {
      degraded: [...degraded, api.degradedReason],
      gh: { binaryFound: ghPath !== null, path: ghPath },
      token: probe,
      probes: { restApi: api, repo: "skipped", issueRead: "skipped" },
    });
  }

  const repoProbe = await githubRequest<{ full_name?: string; private?: boolean }>(token, "GET", `/repos/${owner}/${name}`);
  if (isGitHubError(repoProbe)) {
    return commandError("auth status", repo, repoProbe, {
      degraded: [...degraded, repoProbe.degradedReason],
      gh: { binaryFound: ghPath !== null, path: ghPath },
      token: probe,
      probes: { restApi: "ok", repo: repoProbe, issueRead: "skipped" },
    });
  }

  const issueProbe = await githubRequest<GitHubIssue[]>(token, "GET", `/repos/${owner}/${name}/issues?per_page=1&state=all`);
  if (isGitHubError(issueProbe)) {
    return commandError("auth status", repo, issueProbe, {
      degraded: [...degraded, issueProbe.degradedReason],
      gh: { binaryFound: ghPath !== null, path: ghPath },
      token: probe,
      probes: { restApi: "ok", repo: { ok: true, fullName: repoProbe.full_name ?? repo, private: repoProbe.private ?? null }, issueRead: issueProbe },
    });
  }

  return {
    ok: true,
    command: "auth status",
    repo,
    degraded,
    gh: { binaryFound: ghPath !== null, path: ghPath },
    token: probe,
    probes: {
      restApi: "ok",
      repo: { ok: true, fullName: repoProbe.full_name ?? repo, private: repoProbe.private ?? null },
      issueRead: { ok: true, readable: true, sampleCount: issueProbe.length },
    },
    restFallback: true,
  };
}

export async function prList(repo: string, token: string, state: PrListState, limit: number, jsonFields: PrListJsonField[] | undefined, noDump = false): Promise<GitHubCommandResult> {
  const { owner, name } = repoParts(repo);
  const prs = await githubRequest<GitHubPullRequest[]>(token, "GET", `/repos/${owner}/${name}/pulls?state=${state}&per_page=${limit}`);
  if (isGitHubError(prs)) return commandError("pr list", repo, prs, { state, limit });
  const fields = jsonFields ?? PR_LIST_JSON_FIELDS.slice();
  return {
    ok: true,
    command: "pr list",
    repo,
    ...(noDump ? { noDump: true } : {}),
    state,
    limit,
    count: prs.length,
    jsonFields: fields,
    pullRequests: prs.map((pr) => selectedPrJson(prSummary(pr), fields)),
  };
}

export async function prFiles(repo: string, token: string, number: number, limit: number, commandName = "pr files"): Promise<GitHubCommandResult> {
  const { owner, name } = repoParts(repo);
  const boundedLimit = Math.min(limit, MAX_PR_FILES_LIMIT);
  const pr = await githubRequest<GitHubPullRequest>(token, "GET", `/repos/${owner}/${name}/pulls/${number}`);
  if (isGitHubError(pr)) return commandError(commandName, repo, pr, { number });
  const perPage = Math.max(1, Math.min(100, boundedLimit));
  const files: GitHubPullRequestFile[] = [];
  let page = 1;
  while (files.length < boundedLimit) {
    const remaining = boundedLimit - files.length;
    const pageSize = Math.min(perPage, remaining);
    const path = `/repos/${owner}/${name}/pulls/${number}/files?per_page=${pageSize}&page=${page}`;
    const pageFiles = await githubRequest<GitHubPullRequestFile[]>(token, "GET", path);
    if (isGitHubError(pageFiles)) return commandError(commandName, repo, pageFiles, { number, phase: "fetch-pr-files", filesReturned: files.length });
    files.push(...pageFiles);
    if (pageFiles.length < pageSize || pageFiles.length === 0) break;
    page += 1;
  }
  const totalFiles = numberOrNull(pr.changed_files);
  const listedStats = sumPrFileStats(files);
  const totalAdditions = numberOrNull(pr.additions);
  const totalDeletions = numberOrNull(pr.deletions);
  const fullStatsAvailable = totalAdditions !== null && totalDeletions !== null;
  const totalChanges = fullStatsAvailable ? totalAdditions + totalDeletions : listedStats.changes;
  const truncated = totalFiles !== null ? files.length < totalFiles : files.length >= boundedLimit;
  const nextLimit = totalFiles === null ? MAX_PR_FILES_LIMIT : Math.min(totalFiles, MAX_PR_FILES_LIMIT);
  const nextCommand = truncated
    ? `bun scripts/cli.ts gh pr files ${number} --repo ${repo} --limit ${nextLimit}`
    : `bun scripts/cli.ts gh pr view ${number} --repo ${repo} --json body,title,state,head,base`;
  return {
    ok: true,
    command: commandName,
    repo,
    readOnly: true,
    rawDiffIncluded: false,
    pullRequest: prCompactSummary(pr),
    summary: {
      files: totalFiles ?? files.length,
      additions: totalAdditions ?? listedStats.additions,
      deletions: totalDeletions ?? listedStats.deletions,
      changes: totalChanges,
      commits: numberOrNull(pr.commits),
      source: fullStatsAvailable && totalFiles !== null ? "pull-request-rest" : "listed-files-rest",
    },
    files: files.map(prFileSummary),
    filesReturned: files.length,
    limit: boundedLimit,
    truncation: {
      truncated,
      requestedLimit: limit,
      appliedLimit: boundedLimit,
      returned: files.length,
      totalFiles,
      maxLimit: MAX_PR_FILES_LIMIT,
    },
    next: {
      command: nextCommand,
      purpose: truncated ? "Fetch a larger bounded file summary page." : "Fetch full PR metadata/body; raw diffs remain intentionally excluded.",
    },
    request: {
      method: "GET",
      paths: [
        `/repos/${owner}/${name}/pulls/${number}`,
        `/repos/${owner}/${name}/pulls/${number}/files`,
      ],
    },
  };
}

export async function prRead(repo: string, token: string, number: number, jsonFields: PrReadJsonField[] | undefined, commandName = "pr read", disclosure: Record<string, unknown> | null = null): Promise<GitHubCommandResult> {
  const { owner, name } = repoParts(repo);
  const pr = await githubRequest<GitHubPullRequest>(token, "GET", `/repos/${owner}/${name}/pulls/${number}`);
  if (isGitHubError(pr)) return commandError(commandName, repo, pr, { number });
  const fullSummary = prSummary(pr);
  const includeBody = jsonFields === undefined || jsonFields.includes("body");
  const summary = includeBody ? fullSummary : prSummary(pr, { includeBody: false, includePreview: false });
  const metadata = needsPrGraphqlMetadata(jsonFields) ? await prGraphqlMetadata(repo, token, number) : null;
  if (isGitHubError(metadata)) return commandError(commandName, repo, metadata, { number, phase: "fetch-pr-closeout-metadata", pullRequest: summary, requestedJsonFields: jsonFields ?? [], closeoutMetadata: prCloseoutMetadataError(metadata) });
  const selectionSummary = metadata === null ? fullSummary : { ...fullSummary, ...prMetadataSummary(metadata) };
  return {
    ok: true,
    command: commandName,
    repo,
    ...(disclosure === null ? {} : { disclosure }),
    pullRequest: summary,
    ...(metadata === null ? {} : { closeoutMetadata: prCloseoutMetadata(metadata) }),
    ...(jsonFields === undefined ? {} : {
      jsonFields,
      json: selectedPrJson(selectionSummary, jsonFields),
      compatibility: {
        legacyJsonBodyPath: includeBody ? ".data.pullRequest.body" : null,
        bodyOmitted: !includeBody,
      },
    }),
  };
}

export async function prView(repo: string, token: string, number: number, jsonFields: PrReadJsonField[] | undefined, disclosure: Record<string, unknown> | null = null): Promise<GitHubCommandResult> {
  return prRead(repo, token, number, jsonFields, "pr view", disclosure);
}
