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

import path from "node:path";
import { commandError, isGitHubError, validationError } from "./client";
import { githubSearchLabelQualifier, issueListPaginationSummary, listIssues } from "./issue-read";
import { issueListSummary } from "./issue-summary";
import { isRecord } from "./notify-claudeqq";
import { ghShort, ghTable, ghText } from "./render";
import { GITHUB_REST_PAGE_SIZE, ISSUE_LIST_JSON_FIELDS, MAX_ISSUE_LIST_LIMIT } from "./types";
import type { GitHubCommandResult, IssueListJsonField, IssueListState } from "./types";

const COMPACT_JSON_ISSUE_LIMIT = 40;

export function shellWord(value: string): string {
  return JSON.stringify(value);
}

export function issueListNextCommand(repo: string, state: IssueListState, limit: number, search: string, labels: string[], titlePrefix: string): string {
  const parts = [
    "bun scripts/cli.ts gh issue list",
    "--repo", shellWord(repo),
    "--state", state,
    "--limit", String(Math.min(limit * 2, MAX_ISSUE_LIST_LIMIT)),
  ];
  if (search.length > 0) parts.push("--search", shellWord(search));
  if (titlePrefix.length > 0) parts.push("--title-prefix", shellWord(titlePrefix));
  for (const label of labels) parts.push("--label", shellWord(label));
  return parts.join(" ");
}

export function issueListLabelText(value: unknown): string {
  if (!Array.isArray(value)) return ghText(value);
  const labels = value
    .map((item) => typeof item === "string" ? item : isRecord(item) ? ghText(item.name) : ghText(item))
    .filter((item) => item !== "-");
  return labels.length === 0 ? "-" : labels.join(",");
}

export function withIssueListRendered(result: GitHubCommandResult): GitHubCommandResult {
  return {
    ...result,
    contentType: "text/plain",
    renderedText: renderIssueListTable(result),
  };
}

export function renderIssueListTable(result: GitHubCommandResult): string {
  const issues = Array.isArray(result.issues) ? result.issues.map((item) => isRecord(item) ? item : {}) : [];
  const rows = issues.map((issue) => [
    issue.number === undefined ? "-" : `#${ghText(issue.number)}`,
    ghText(issue.state),
    ghShort(ghText(issue.updatedAt), 19),
    ghShort(issueListLabelText(issue.labels), 32),
    ghShort(ghText(issue.title), 96),
  ]);
  const next = isRecord(result.next) ? result.next : {};
  const lines = [
    "gh issue list (observed)",
    "",
    ghTable(["ISSUE", "STATE", "UPDATED", "LABELS", "TITLE"], rows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} state=${ghText(result.state)} count=${ghText(result.count)} raw=${ghText(result.rawCount)} hasMore=${ghText(result.hasMore)}`,
    `  search=${ghText(result.search)} labels=${issueListLabelText(result.labels)} titlePrefix=${ghText(result.titlePrefix)}`,
  ];
  if (result.searchTotalCount !== undefined) lines.push(`  searchTotal=${ghText(result.searchTotalCount)} incomplete=${ghText(result.searchIncomplete)}`);
  if (typeof next.command === "string" && next.command.length > 0) {
    lines.push("", "Next:", `  ${next.command}`);
  }
  lines.push("", "Disclosure:");
  lines.push("  default view is a bounded table; use --json for selected fields or --full/--raw for structured pagination and request metadata.");
  return lines.join("\n");
}

export async function issueList(repo: string, token: string, state: IssueListState, limit: number, jsonFields: IssueListJsonField[] | undefined, search?: string, labels: string[] = [], noDump = false, titlePrefix?: string): Promise<GitHubCommandResult> {
  const normalizedSearch = String(search ?? "").trim();
  const normalizedTitlePrefix = String(titlePrefix ?? "").trim();
  if (titlePrefix !== undefined && normalizedTitlePrefix.length === 0) return validationError("issue list", repo, "--title-prefix requires a non-empty value");
  const result = await listIssues(token, repo, state, limit, normalizedSearch, labels);
  if (isGitHubError(result)) return commandError("issue list", repo, result, { state, limit, search: normalizedSearch || null, titlePrefix: normalizedTitlePrefix || null, labels });
  const listedIssues = result.items;
  const issues = normalizedTitlePrefix.length > 0
    ? listedIssues.filter((issue) => issue.title.startsWith(normalizedTitlePrefix))
    : listedIssues;
  const fields = jsonFields ?? ISSUE_LIST_JSON_FIELDS.slice();
  const selectedIssues = issues.map((issue) => issueListSummary(issue, fields));
  const compactJson = jsonFields !== undefined && !noDump;
  const returnedIssues = compactJson ? selectedIssues.slice(0, COMPACT_JSON_ISSUE_LIMIT) : selectedIssues;
  const omittedIssues = Math.max(0, selectedIssues.length - returnedIssues.length);
  const fullCommand = [
    "bun scripts/cli.ts gh issue list",
    "--repo", shellWord(repo),
    "--state", state,
    "--limit", String(limit),
    ...(normalizedSearch.length > 0 ? ["--search", shellWord(normalizedSearch)] : []),
    ...(normalizedTitlePrefix.length > 0 ? ["--title-prefix", shellWord(normalizedTitlePrefix)] : []),
    ...labels.flatMap((label) => ["--label", shellWord(label)]),
    "--json", shellWord(fields.join(",")),
    "--full",
  ].join(" ");
  const payload: GitHubCommandResult = {
    ok: true,
    command: "issue list",
    repo,
    ...(noDump ? { noDump: true } : {}),
    state,
    limit,
    search: normalizedSearch || null,
    titlePrefix: normalizedTitlePrefix || null,
    ...(normalizedTitlePrefix.length > 0
      ? {
        titleFilter: {
          field: "title",
          operator: "startsWith",
          prefix: normalizedTitlePrefix,
          scope: "bounded-listed-issues",
          inputCount: listedIssues.length,
          outputCount: issues.length,
          filteredOut: listedIssues.length - issues.length,
        },
      }
      : {}),
    labels,
    count: issues.length,
    rawCount: result.rawCount,
    searchTotalCount: result.searchTotalCount,
    searchIncomplete: result.searchIncomplete,
    ...(compactJson ? {} : { pagination: issueListPaginationSummary(result) }),
    hasMore: result.hasMore,
    jsonFields: fields,
    issues: returnedIssues,
    ...(compactJson
      ? {
        issueListDisclosure: {
          mode: "bounded-json",
          returned: returnedIssues.length,
          omitted: omittedIssues,
          totalMatchedInBoundedScan: issues.length,
          inlineLimit: COMPACT_JSON_ISSUE_LIMIT,
          fullCommand,
          note: "Default --json issue list output is bounded for interactive use; add --full for complete selected fields and pagination/request metadata.",
        },
      }
      : {}),
    ...(omittedIssues > 0
      ? {
        omittedIssues,
        fullCommand,
      }
      : {}),
    ...(result.hasMore && limit < MAX_ISSUE_LIST_LIMIT
      ? {
        next: {
          command: issueListNextCommand(repo, state, limit, normalizedSearch, labels, normalizedTitlePrefix),
          note: "Increase --limit to continue scanning beyond the current bounded result set.",
        },
      }
      : result.hasMore
        ? {
          scanLimit: {
            maxLimitReached: true,
            maxLimit: MAX_ISSUE_LIST_LIMIT,
            note: "The command reached the maximum bounded issue scan; narrow with --state/--label/--search before treating the repository as exhausted.",
          },
        }
      : {}),
    ...(compactJson
      ? {}
      : {
        request: {
          method: "GET",
          path: normalizedSearch ? "/search/issues" : "/repos/{owner}/{repo}/issues",
          query: normalizedSearch
            ? { q: `${normalizedSearch} repo:${repo} type:issue${state === "all" ? "" : ` state:${state}`}${labels.map((label) => ` ${githubSearchLabelQualifier(label)}`).join("")}`, per_page: GITHUB_REST_PAGE_SIZE }
            : { state, labels, per_page: GITHUB_REST_PAGE_SIZE },
          localTitlePrefixFilter: normalizedTitlePrefix || null,
        },
      }),
    note: "GitHub's issues endpoint may include pull requests; this command filters pull requests from .issues.",
  };
  return jsonFields === undefined && !noDump ? withIssueListRendered(payload) : payload;
}
