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

import path from "node:path";
import { preview } from "./auth-and-safety";
import { runnerDisposition } from "./client";

export const DEFAULT_REPO = "pikasTech/unidesk";

export const GITHUB_API = (process.env.UNIDESK_GITHUB_API_URL ?? "https://api.github.com").replace(/\/$/u, "");

export const USER_AGENT = "unidesk-cli-gh";

export const PREVIEW_CHARS = 240;

export const REQUEST_TIMEOUT_MS = 20_000;

export const MAX_ISSUE_ATTACHMENT_DOWNLOAD_BYTES = 25 * 1024 * 1024;

export const MIN_SAFE_ISSUE_BODY_CHARS = 20;

export const MAX_INLINE_ISSUE_COMMENT_BODY_CHARS = 1000;

export const GITHUB_REST_PAGE_SIZE = 100;

export const MAX_ISSUE_LIST_LIMIT = 1000;

export const DEFAULT_ISSUE_COMMENTS_LIMIT = 8;

export const DEFAULT_STALE_CLOSE_INACTIVE_HOURS = 48;

export const MAX_STALE_CLOSE_INACTIVE_HOURS = 24 * 365 * 10;

export const ISSUE_LIFECYCLE_PREVIEW_LIMIT = 80;

export const DEFAULT_COMMANDER_BRIEF_CLAUDEQQ_BASE_URL = "http://backend-core:8080/api/microservices/claudeqq/proxy";

export const DEFAULT_COMMANDER_BRIEF_CLAUDEQQ_USER_ID = "645275593";

export const CODE_QUEUE_BOARD_TARGET_ISSUE = 20;

export const COMMANDER_BRIEF_TARGET_ISSUE = 24;

export const MAX_PR_FILES_LIMIT = 3000;

export const DEFAULT_BOARD_KNOWN_META_ISSUES = [CODE_QUEUE_BOARD_TARGET_ISSUE, COMMANDER_BRIEF_TARGET_ISSUE] as const;

export const BOARD_AUDIT_REQUIRED_COLUMNS = ["branch", "acceptance", "relatedTask", "progress"] as const;

export const BOARD_ROW_FIELDS = ["progress", "status", "validation", "branch", "tasks", "focus"] as const;

export const BOARD_ROW_UPSERT_TEXT_FIELDS = ["category", "branch", "tasks", "summary", "focus", "validation", "progress"] as const;

export const ISSUE_VIEW_JSON_FIELDS = ["body", "title", "state", "closed", "closedAt", "comments", "commentCount", "number", "url", "author", "createdAt", "updatedAt"] as const;

export const ISSUE_LIST_JSON_FIELDS = ["number", "title", "state", "closed", "closedAt", "url", "updatedAt", "createdAt", "author", "labels"] as const;

export const PR_LIST_JSON_FIELDS = [
  "body",
  "title",
  "state",
  "number",
  "url",
  "author",
  "head",
  "base",
  "draft",
  "createdAt",
  "updatedAt",
  "closedAt",
  "merged",
  "mergedAt",
  "mergeCommit",
  "headRefName",
  "baseRefName"
] as const;

export const PR_READ_JSON_FIELDS = ["body", "title", "state", "stateDetail", "number", "url", "author", "head", "base", "draft", "createdAt", "updatedAt", "closed", "closedAt", "merged", "mergedAt", "mergeCommit", "headRefName", "baseRefName", "mergeable", "mergeStateStatus", "statusCheckRollup"] as const;

export const PR_CLOSEOUT_JSON_FIELDS = ["mergeable", "mergeStateStatus", "statusCheckRollup"] as const;

export const PR_CLOSEOUT_VIEW_JSON = "headRefName,baseRefName,mergeable,mergeStateStatus,statusCheckRollup";

export const ISSUE_LIST_STATES = ["open", "closed", "all"] as const;

export const BODY_UPDATE_MODES = ["replace", "append"] as const;

export const BOARD_MUTATION_SECTIONS = ["open", "closed"] as const;

export const BOARD_GITHUB_STATUSES = ["OPEN", "CLOSED"] as const;

export const GH_VALUE_OPTIONS = new Set([
  "--repo", "--limit", "--board-issue", "--known-meta-issue", "--ignore-issue", "--title",
  "--body-file", "--body-patch-file", "--body", "--base", "--head", "--json", "--state", "--mode",
  "--expect-updated-at", "--expect-body-sha", "--body-profile", "--label", "--field",
  "--value", "--section", "--to", "--status", "--row-file", "--category", "--branch",
  "--tasks", "--summary", "--focus", "--validation", "--progress", "--number", "--pr",
  "--search", "--title-prefix", "--inactive-hours", "--comment", "--comment-file", "--description",
  "--attachment", "--output", "--file", "--hunk", "--sync-node",
  "--job", "--url", "--commit",
]);

export const GH_FLAG_OPTIONS = new Set(["--dry-run", "--draft", "--notify-claudeqq-brief-diff", "--allow-short-body", "--body-stdin", "--body-patch-stdin", "--comment-stdin", "--raw", "--full", "--stat", "--merge", "--squash", "--rebase", "--delete-branch", "--keep-branch", "--skip-local-closeout", "--private", "--public", "--auto-init"]);

export const MIN_SAFE_BODY_SCAN_CHARS = MIN_SAFE_ISSUE_BODY_CHARS;

export const ISSUE_SCAN_MAX_FINDINGS = 60;

export const ISSUE_BODY_PROFILES = {
  "code-queue-board": {
    label: "Code Queue long board issue #20",
    issueNumber: CODE_QUEUE_BOARD_TARGET_ISSUE,
    requiredHeadings: ["## 看板（OPEN）"],
  },
  "commander-brief": {
    label: "Commander brief body-only issue (#24 legacy or daily rolling brief)",
    legacyIssueNumber: COMMANDER_BRIEF_TARGET_ISSUE,
    requiredHeadings: ["## 常驻观察与长期建议"],
  },
} as const;

export const DAILY_COMMANDER_BRIEF_TITLE_PATTERN = /^\d{4}-\d{2}-\d{2}\s+指挥简报（北京时间）$/u;

export const DAILY_COMMANDER_BRIEF_BODY_HEADING_PATTERN = /^#\s+\d{4}-\d{2}-\d{2}\s+指挥简报（北京时间）\s*$/u;

export const LEGACY_COMMANDER_BRIEF_BODY_HEADING_PATTERN = /^#\s+指挥简报\s*$/u;

export const COMMANDER_BRIEF_UPDATE_HEADING_PATTERNS = [
  /^##\s+更新\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+北京时间\s*$/u,
  /^##\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+北京时间指挥更新\s*$/u,
  /^###\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+CST[:：].*$/u,
] as const;

export const UNIDESK_CLI_CONFIG_PATH = "config/unidesk-cli.yaml";

export type IssueViewJsonField = typeof ISSUE_VIEW_JSON_FIELDS[number];

export type IssueListJsonField = typeof ISSUE_LIST_JSON_FIELDS[number];

export type PrListJsonField = typeof PR_LIST_JSON_FIELDS[number];

export type PrReadJsonField = typeof PR_READ_JSON_FIELDS[number];

export type IssueListState = typeof ISSUE_LIST_STATES[number];

export type PrListState = typeof ISSUE_LIST_STATES[number];

export type BodyUpdateMode = typeof BODY_UPDATE_MODES[number];

export type PullRequestMergeMethod = "merge" | "squash" | "rebase";

export type RepoVisibility = "private" | "public";

export type BoardMutationSection = typeof BOARD_MUTATION_SECTIONS[number];

export type BoardGithubStatus = typeof BOARD_GITHUB_STATUSES[number];

export type IssueBodyProfileName = keyof typeof ISSUE_BODY_PROFILES;

export type IssueBodyProfileOption = "auto" | IssueBodyProfileName;

export type EscapeFindingClassification = "suspected-pollution" | "explanatory-mention" | "risk";

export type EscapeFindingSeverity = "low" | "medium" | "high";

export type EscapeBodyKind = "issue-body" | "comment-body";

export type CleanupSuggestionAction = "rewrite-issue-body-with-body-stdin" | "review-comment-manually" | "review-body-length" | "no-cleanup-needed";

export type BoardSectionKind = "open" | "closed";

export type BoardRequiredColumn = typeof BOARD_AUDIT_REQUIRED_COLUMNS[number];

export type BoardColumnKind = BoardRequiredColumn | "focus" | "githubStatus" | "category" | "summary";

export type BoardRowField = typeof BOARD_ROW_FIELDS[number];

export type BoardRowUpsertTextField = typeof BOARD_ROW_UPSERT_TEXT_FIELDS[number];

export type BoardRowUpsertField = BoardRowUpsertTextField | "status";

export type BoardIgnoreReason = "known-meta" | "ignored" | "brief-index-managed";

export type GitHubDegradedReason =
  | "missing-binary"
  | "missing-token"
  | "auth-failed"
  | "egress-failed"
  | "github-transient"
  | "action-log-not-ready"
  | "network-proxy-failed"
  | "permission-denied"
  | "repo-not-found"
  | "repo-forbidden"
  | "issue-not-found"
  | "pr-not-found"
  | "scope-insufficient"
  | "validation-failed"
  | "invalid-response"
  | "unsupported-command";

export type RunnerDisposition = "infra-blocked" | "business-failed";

export type ClaudeQqTargetType = "private" | "group";

export type CommanderBriefDiffMode = "identical" | "append-only" | "heading-diff" | "unreliable";

export interface PrMergeUnknownRetryConfig {
  maxAttempts: number;
  initialDelayMs: number;
  maxDelayMs: number;
  factor: number;
}

export interface CommanderBriefDiff {
  ok: boolean;
  mode: CommanderBriefDiffMode;
  message: string;
  chars: number;
  sections: string[];
  sectionCount: number;
  skippedReason?: string;
}

export interface ClaudeQqConfig {
  enabled: boolean;
  baseUrl: string;
  targetType: ClaudeQqTargetType;
  userId?: string;
  groupId?: string;
  timeoutMs: number;
}

export interface ClaudeQqSendResult {
  ok: boolean;
  attempted: boolean;
  skipped?: boolean;
  skippedReason?: string;
  endpoint?: string;
  status?: number;
  degradedReason?: string;
  message?: string;
  response?: unknown;
  target: Record<string, unknown>;
}

export interface ClaudeQqEndpointResult {
  ok: boolean;
  endpoint: string;
  status?: number;
  degradedReason?: string;
  message?: string;
  response?: unknown;
}

export interface CommanderBriefSection {
  heading: string;
  text: string;
  startLine: number;
}

export interface EscapePatternDefinition {
  kind: string;
  pattern: RegExp;
  description: string;
}

export interface EscapeMatchFinding {
  type: "issue" | "comment";
  bodyKind: EscapeBodyKind;
  bodyId: string;
  issueNumber: number;
  issueId: number;
  commentId?: number;
  url: string;
  kind: string;
  classification: EscapeFindingClassification;
  severity: EscapeFindingSeverity;
  reason: string;
  snippet: string;
  lineNumber: number;
  column: number;
  match: string;
  bodyChars: number | null;
  bodyTrimmedChars: number | null;
  cleanupPreview?: {
    before: string;
    after: string;
  };
}

export interface EscapeCleanupSuggestion {
  type: EscapeBodyKind;
  bodyId: string;
  issueNumber: number;
  issueId: number;
  commentId?: number;
  url: string;
  classification: EscapeFindingClassification;
  action: CleanupSuggestionAction;
  reason: string;
  findings: Array<Pick<EscapeMatchFinding, "kind" | "classification" | "severity" | "lineNumber" | "column" | "snippet" | "match">>;
  plannedCommand?: string;
  bodyInputHint?: string;
  preview?: {
    before: string;
    after: string;
  };
}

export interface EscapeScanErrorFinding {
  type: "comment-scan-error";
  bodyKind: EscapeBodyKind;
  bodyId: string;
  issueNumber: number;
  issueId: number;
  url: string;
  degradedReason: GitHubDegradedReason;
  runnerDisposition: RunnerDisposition;
  details: GitHubErrorPayload;
}

export type EscapeScanEntry = EscapeMatchFinding | EscapeScanErrorFinding;

export interface BoardTableRow {
  section: BoardSectionKind;
  lineNumber: number;
  raw: string;
  cells: string[];
  issueNumbers: number[];
  issueNumber: number | null;
  title: string | null;
  columns: Partial<Record<BoardRequiredColumn, string>>;
}

export interface BoardTableSection {
  kind: BoardSectionKind;
  heading: string;
  headingLine: number;
  headerLine: number;
  headers: string[];
  rows: BoardTableRow[];
}

export interface BoardIssueEntry {
  number: number;
  title: string;
  state: string;
  url: string;
}

export interface BoardIgnoredIssue {
  number: number;
  reason: BoardIgnoreReason;
  title?: string;
  state?: string;
  url?: string;
}

export interface BoardRowValidationWarning {
  issueNumber: number | null;
  section: BoardSectionKind;
  lineNumber: number;
  kind: string;
  message: string;
  missingColumns?: BoardRequiredColumn[];
  columns?: Partial<Record<BoardRequiredColumn, string>>;
  rowPreview: string;
}

export interface BoardRowLookup {
  ok: true;
  section: BoardTableSection;
  row: BoardTableRow;
  matches: Array<{ section: BoardTableSection; row: BoardTableRow }>;
}

export interface BoardRowUpdatePlanResult {
  ok: true;
  plan: Record<string, unknown>;
  newBody: string;
}

export interface BoardRowMutationPlanResult {
  ok: true;
  plan: Record<string, unknown>;
  newBody: string;
}

export interface BoardRowUpsertValues {
  category?: string;
  branch?: string;
  tasks?: string;
  summary?: string;
  focus?: string;
  validation?: string;
  progress?: string;
  status?: BoardGithubStatus;
}

export interface GitHubCommandResult {
  ok: boolean;
  repo: string;
  command: string;
  degradedReason?: GitHubDegradedReason;
  degraded?: GitHubDegradedReason[];
  runnerDisposition?: RunnerDisposition;
  [key: string]: unknown;
}

export type GitHubCommandFailure = GitHubCommandResult & { ok: false };

export interface GitHubTokenProbe {
  present: boolean;
  source: "GH_TOKEN" | "GITHUB_TOKEN" | "yaml-token-source" | "yaml-token-projection" | "gh-auth-token" | null;
  scope?: "repository-override" | "global";
  ghFallbackAttempted: boolean;
  ghBinaryFound?: boolean;
  ghAuthTokenAvailable?: boolean | null;
  yamlSourceAttempted?: boolean;
  yamlSourceEnabled?: boolean;
  yamlSourcePriority?: "before-env" | "after-env";
  configRef?: string;
  sourceRef?: string;
  sourceKey?: string;
  sourceExists?: boolean;
  sourceKeyPresent?: boolean;
  projectedEnv?: string;
  projectedEnvPresent?: boolean;
  tokenFingerprint?: string | null;
  valuesPrinted?: false;
}

export interface GitHubOptions {
  repo: string;
  dryRun: boolean;
  raw: boolean;
  full: boolean;
  limit: number;
  inactiveHours: number;
  boardIssue: number;
  knownMetaIssues: number[];
  ignoredIssues: number[];
  draft: boolean;
  notifyClaudeQqBriefDiff: boolean;
  allowShortBody: boolean;
  labels: string[];
  search?: string;
  titlePrefix?: string;
  title?: string;
  body?: string;
  bodyFile?: string;
  bodyPatchFile?: string;
  comment?: string;
  commentFile?: string;
  repoDescription?: string;
  repoVisibility?: RepoVisibility;
  repoAutoInit: boolean;
  base?: string;
  head?: string;
  jsonFields?: IssueViewJsonField[];
  issueListJsonFields?: IssueListJsonField[];
  prListJsonFields?: PrListJsonField[];
  prJsonFields?: PrReadJsonField[];
  listState: IssueListState;
  prListState: PrListState;
  mode: BodyUpdateMode;
  expectUpdatedAt?: string;
  expectBodySha?: string;
  bodyProfile: IssueBodyProfileOption;
  boardRowField?: BoardRowField;
  boardRowValue?: string;
  boardRowFile?: string;
  boardSection?: BoardMutationSection;
  boardMoveTo?: BoardMutationSection;
  boardGithubStatus?: BoardGithubStatus;
  boardRowUpsertValues: BoardRowUpsertValues;
  mergeMethod: PullRequestMergeMethod;
  deleteBranch: boolean;
  localCloseout: boolean;
  syncNodes: string[];
  attachmentSelector?: string;
  outputPath?: string;
  filePath?: string;
  hunk?: number;
  benchmarkUrl?: string;
  benchmarkCommit?: string;
}

export interface GitHubShorthandReference {
  input: string;
  repo: string;
  number: number;
  source?: "owner-repo-number" | "github-url" | "number-option";
  urlKind?: "issue" | "pr";
  standardCommand?: string;
}

export interface GitHubResolvedNumberReference {
  repo: string;
  number: number;
  shorthand?: GitHubShorthandReference;
}

export interface IssueProfileValidationContext {
  issueTitle?: string | null;
  issueBody?: string | null;
  issueMetadataFetched?: boolean;
}

export interface GitHubIssueAttachment {
  index: number;
  url: string;
  assetId: string | null;
  source: "issue-body" | "comment";
  sourceUrl: string;
  commentId?: number;
  author?: string | null;
  createdAt?: string | null;
  updatedAt?: string | null;
  marker: "markdown-image" | "html-img" | "plain-url";
  alt?: string | null;
  width?: string | null;
  height?: string | null;
}

export interface GitHubAttachmentDownload {
  buffer: Buffer;
  contentType: string | null;
  contentLengthHeader: string | null;
  finalUrl: string;
}

export interface GitHubErrorPayload {
  ok: false;
  degradedReason: GitHubDegradedReason;
  runnerDisposition: RunnerDisposition;
  status?: number;
  message: string;
  retryable?: boolean;
  commanderAction?: string;
  details?: unknown;
  scopes?: {
    accepted: string | null;
    token: string | null;
  };
  request?: {
    method: string;
    path: string;
  };
}

export interface GitHubIssue {
  id: number;
  number: number;
  title: string;
  body: string | null;
  state: string;
  html_url: string;
  comments: number;
  user?: { login?: string };
  labels?: Array<string | { name?: string; color?: string; description?: string | null }>;
  pull_request?: unknown;
  created_at?: string;
  updated_at?: string;
  closed_at?: string | null;
}

export interface GitHubComment {
  id: number;
  body: string | null;
  html_url: string;
  user?: { login?: string };
  created_at?: string;
  updated_at?: string;
}

export interface GitHubIssueSearchResponse {
  total_count?: number;
  incomplete_results?: boolean;
  items?: GitHubIssue[];
}

export interface GitHubIssueListPage {
  path: string;
  rawCount: number;
  issueCount: number;
}

export interface GitHubIssueListResult {
  items: GitHubIssue[];
  rawCount: number;
  fetchedPages: number;
  pageSize: number;
  exhausted: boolean;
  hasMore: boolean;
  pages: GitHubIssueListPage[];
  searchTotalCount?: number;
  searchIncomplete?: boolean;
}

export interface GitHubPullRequest {
  id: number;
  node_id?: string;
  number: number;
  title: string;
  body: string | null;
  state: string;
  html_url: string;
  draft?: boolean;
  user?: { login?: string };
  head?: { ref?: string; sha?: string; repo?: { full_name?: string | null } | null };
  base?: { ref?: string; sha?: string };
  additions?: number;
  deletions?: number;
  changed_files?: number;
  commits?: number;
  closed_at?: string | null;
  merged?: boolean | null;
  merged_at?: string | null;
  merge_commit_sha?: string | null;
  mergeable?: string | null;
  merge_state_status?: string | null;
  created_at?: string;
  updated_at?: string;
}

export interface GitHubPullRequestFile {
  sha?: string;
  filename: string;
  status?: string;
  additions?: number;
  deletions?: number;
  changes?: number;
  blob_url?: string;
  raw_url?: string;
  contents_url?: string;
  previous_filename?: string;
  patch?: string;
}

export interface GitHubPullRequestGraphqlStatusContext {
  __typename?: string;
  name?: string | null;
  status?: string | null;
  conclusion?: string | null;
  context?: string | null;
  state?: string | null;
  targetUrl?: string | null;
  description?: string | null;
}

export interface GitHubPullRequestGraphqlStatusCheckRollup {
  state?: string | null;
  contexts?: {
    nodes?: GitHubPullRequestGraphqlStatusContext[] | null;
  } | null;
}

export interface GitHubPullRequestGraphqlMetadata {
  mergeable?: string | null;
  mergeStateStatus?: string | null;
  headRefName?: string | null;
  baseRefName?: string | null;
  statusCheckRollup?: GitHubPullRequestGraphqlStatusCheckRollup | null;
}

export interface GitHubRepository {
  id?: number;
  name?: string;
  full_name?: string;
  private?: boolean;
  html_url?: string;
  clone_url?: string;
  ssh_url?: string;
  description?: string | null;
  default_branch?: string;
  permissions?: Record<string, boolean>;
  owner?: { login?: string };
  created_at?: string;
  updated_at?: string;
}

export interface GitHubAuthenticatedUser {
  login?: string;
  id?: number;
}

export interface GitHubBranch {
  name?: string;
  commit?: { sha?: string };
}

export interface GitHubCompare {
  status?: string;
  ahead_by?: number;
  behind_by?: number;
  total_commits?: number;
  html_url?: string;
  base_commit?: { sha?: string };
  merge_base_commit?: { sha?: string };
}
