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

import path from "node:path";
import { repoParts } from "./auth-and-safety";
import { codeQueueBoardCommanderBriefHint } from "./body-profiles";
import { commandError, githubRequest, isGitHubError, issueBodyReadCommands, issueCommentReadCommands } from "./client";
import { issueSummary } from "./issue-summary";
import { commentSummary, compactCommentSummary, compactIssueViewCommentSummary } from "./render";
import { GITHUB_REST_PAGE_SIZE } from "./types";
import type { GitHubCommandResult, GitHubComment, GitHubErrorPayload, GitHubIssue, GitHubIssueListPage, GitHubIssueListResult, GitHubIssueSearchResponse, IssueListState, IssueViewJsonField } from "./types";

export async function listIssueComments(token: string, repo: string, issueNumber: number): Promise<GitHubComment[] | GitHubErrorPayload> {
  const { owner, name } = repoParts(repo);
  return githubRequest<GitHubComment[]>(token, "GET", `/repos/${owner}/${name}/issues/${issueNumber}/comments?per_page=100`);
}

export function githubSearchLabelQualifier(label: string): string {
  if (/^[A-Za-z0-9_.:-]+$/u.test(label)) return `label:${label}`;
  return `label:"${label.replace(/["\\]/gu, "\\$&")}"`;
}

export function issueListPaginationSummary(result: GitHubIssueListResult): Record<string, unknown> {
  return {
    pageSize: result.pageSize,
    fetchedPages: result.fetchedPages,
    exhausted: result.exhausted,
    hasMore: result.hasMore,
    pages: result.pages,
  };
}

export async function listIssues(token: string, repo: string, state: IssueListState, limit: number, search?: string, labels: string[] = []): Promise<GitHubIssueListResult | GitHubErrorPayload> {
  const { owner, name } = repoParts(repo);
  const normalizedSearch = String(search ?? "").trim();
  const pageSize = GITHUB_REST_PAGE_SIZE;
  const collected: GitHubIssue[] = [];
  const pages: GitHubIssueListPage[] = [];
  let rawCount = 0;
  let fetchedPages = 0;
  let exhausted = false;
  let searchTotalCount: number | undefined;
  let searchIncomplete: boolean | undefined;
  const maxPages = Math.max(1, Math.ceil(limit / pageSize) + 10);
  for (let page = 1; collected.length < limit && page <= maxPages; page += 1) {
    const perPage = pageSize;
    let path: string;
    let rawIssueItems: GitHubIssue[];
    if (normalizedSearch) {
      const qualifiers = [`repo:${owner}/${name}`, "type:issue"];
      if (state !== "all") qualifiers.push(`state:${state}`);
      for (const label of labels) qualifiers.push(githubSearchLabelQualifier(label));
      const params = new URLSearchParams({ q: `${normalizedSearch} ${qualifiers.join(" ")}`, per_page: String(perPage), page: String(page) });
      path = `/search/issues?${params.toString()}`;
      const response = await githubRequest<GitHubIssueSearchResponse>(token, "GET", path);
      if (isGitHubError(response)) return response;
      rawIssueItems = Array.isArray(response.items) ? response.items : [];
      if (searchTotalCount === undefined) searchTotalCount = response.total_count;
      searchIncomplete = searchIncomplete === true || response.incomplete_results === true;
    } else {
      const params = new URLSearchParams({ state, per_page: String(perPage), page: String(page) });
      if (labels.length > 0) params.set("labels", labels.join(","));
      path = `/repos/${owner}/${name}/issues?${params.toString()}`;
      const response = await githubRequest<GitHubIssue[]>(token, "GET", path);
      if (isGitHubError(response)) return response;
      rawIssueItems = response;
    }
    const issueItems = rawIssueItems.filter((issue) => issue.pull_request === undefined);
    rawCount += rawIssueItems.length;
    fetchedPages += 1;
    pages.push({ path, rawCount: rawIssueItems.length, issueCount: issueItems.length });
    collected.push(...issueItems);
    if (rawIssueItems.length < perPage) {
      exhausted = true;
      break;
    }
  }
  const hasMore = collected.length > limit || !exhausted;
  return {
    items: collected.slice(0, limit),
    rawCount,
    fetchedPages,
    pageSize,
    exhausted,
    hasMore,
    pages,
    searchTotalCount,
    searchIncomplete,
  };
}

export async function getIssue(token: string, repo: string, issueNumber: number): Promise<GitHubIssue | GitHubErrorPayload> {
  const { owner, name } = repoParts(repo);
  return githubRequest<GitHubIssue>(token, "GET", `/repos/${owner}/${name}/issues/${issueNumber}`);
}

export async function getIssueComment(token: string, repo: string, commentId: number): Promise<GitHubComment | GitHubErrorPayload> {
  const { owner, name } = repoParts(repo);
  return githubRequest<GitHubComment>(token, "GET", `/repos/${owner}/${name}/issues/comments/${commentId}`);
}

export function selectedIssueJson(issue: GitHubIssue, comments: GitHubComment[] | null, fields: IssueViewJsonField[] | undefined, options: { includeFullCommentBodies?: boolean } = {}): Record<string, unknown> | null {
  if (fields === undefined) return null;
  const summary = issueSummary(issue);
  const selected: Record<string, unknown> = {};
  for (const field of fields) {
    if (field === "comments") {
      selected.comments = (comments ?? []).map(options.includeFullCommentBodies === true ? commentSummary : compactIssueViewCommentSummary);
    } else {
      selected[field] = summary[field];
    }
  }
  return selected;
}

export async function issueRead(repo: string, token: string, issueNumber: number, jsonFields: IssueViewJsonField[] | undefined, commandName = "issue read", disclosure: Record<string, unknown> | null = null, options: { includeFullCommentBodies?: boolean } = {}): Promise<GitHubCommandResult> {
  const issue = await getIssue(token, repo, issueNumber);
  if (isGitHubError(issue)) return commandError(commandName, repo, issue, { issueNumber });
  const needsComments = jsonFields === undefined || jsonFields.includes("comments");
  const includeBody = jsonFields === undefined || jsonFields.includes("body");
  const includeFullCommentBodies = options.includeFullCommentBodies === true;
  const comments = needsComments ? await listIssueComments(token, repo, issueNumber) : null;
  if (isGitHubError(comments)) return commandError(commandName, repo, comments, { issueNumber, issue: issueSummary(issue, { includeBody, includePreview: false }) });
  return {
    ok: true,
    command: commandName,
    repo,
    ...(disclosure === null ? {} : { disclosure }),
    issue: issueSummary(issue, { includeBody, includePreview: false }),
    codeQueueBoardHint: codeQueueBoardCommanderBriefHint(issueNumber, issue.body ?? ""),
    ...(comments === null || jsonFields !== undefined ? {} : { comments: comments.map(includeFullCommentBodies ? commentSummary : compactCommentSummary) }),
    ...(jsonFields === undefined ? {} : {
      jsonFields,
      json: selectedIssueJson(issue, comments, jsonFields, { includeFullCommentBodies }),
      compatibility: {
        legacyJsonBodyPath: includeBody ? ".data.issue.body" : null,
        bodyOmitted: !includeBody,
        commentsCompacted: comments !== null && !includeFullCommentBodies,
        commentBodiesOmitted: comments !== null && !includeFullCommentBodies,
        fullCommentBodiesIncluded: comments !== null && includeFullCommentBodies,
        commentsPath: comments === null ? null : ".data.json.comments",
        readCommands: {
          ...(includeBody ? {} : issueBodyReadCommands(repo, issueNumber)),
          ...(comments !== null && !includeFullCommentBodies ? issueCommentReadCommands(repo, issueNumber) : {}),
        },
      },
    }),
  };
}

export async function issueView(repo: string, token: string, issueNumber: number, jsonFields: IssueViewJsonField[] | undefined, disclosure: Record<string, unknown> | null = null, options: { includeFullCommentBodies?: boolean } = {}): Promise<GitHubCommandResult> {
  return issueRead(repo, token, issueNumber, jsonFields, "issue view", disclosure, options);
}
