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

import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { readIssueCommentBody } from "./body-input";
import { PREVIEW_CHARS } from "./types";
import type { GitHubOptions, GitHubTokenProbe } from "./types";

export function readIssueLifecycleCommentBody(options: GitHubOptions, command: string): { body: string; bodySource: Record<string, unknown> } | null {
  if (options.comment === undefined && options.commentFile === undefined) return null;
  if (options.comment !== undefined && options.commentFile !== undefined) {
    throw new Error(`${command} --comment and --comment-file/--comment-stdin are mutually exclusive`);
  }
  if (options.body !== undefined || options.bodyFile !== undefined) {
    throw new Error(`${command} --comment/--comment-file/--comment-stdin cannot be combined with --body/--body-file/--body-stdin`);
  }
  if (options.commentFile !== undefined) {
    return readIssueCommentBody({ ...options, body: undefined, bodyFile: options.commentFile });
  }
  return readIssueCommentBody({ ...options, body: options.comment, bodyFile: undefined });
}

export function tokenFromEnvironment(): GitHubTokenProbe {
  if (process.env.GH_TOKEN && process.env.GH_TOKEN.length > 0) {
    return { present: true, source: "GH_TOKEN", ghFallbackAttempted: false };
  }
  if (process.env.GITHUB_TOKEN && process.env.GITHUB_TOKEN.length > 0) {
    return { present: true, source: "GITHUB_TOKEN", ghFallbackAttempted: false };
  }
  return { present: false, source: null, ghFallbackAttempted: false };
}

export function ghBinaryPath(): string | null {
  try {
    const output = execFileSync("sh", ["-lc", "command -v gh"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
    return output.length > 0 ? output : null;
  } catch {
    return null;
  }
}

export function ghAuthToken(): string | null {
  try {
    const output = execFileSync("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 }).trim();
    return output.length > 0 ? output : null;
  } catch {
    return null;
  }
}

export function resolveToken(allowGhFallback: boolean): { token: string | null; probe: GitHubTokenProbe } {
  const envProbe = tokenFromEnvironment();
  if (envProbe.present) {
    const token = envProbe.source === "GH_TOKEN" ? process.env.GH_TOKEN ?? null : process.env.GITHUB_TOKEN ?? null;
    return { token, probe: envProbe };
  }
  if (!allowGhFallback) return { token: null, probe: envProbe };
  const ghPath = ghBinaryPath();
  if (ghPath === null) return { token: null, probe: { present: false, source: null, ghFallbackAttempted: true, ghBinaryFound: false, ghAuthTokenAvailable: null } };
  const token = ghAuthToken();
  if (token !== null) return { token, probe: { present: true, source: "gh-auth-token", ghFallbackAttempted: true, ghBinaryFound: true, ghAuthTokenAvailable: true } };
  return { token: null, probe: { present: false, source: null, ghFallbackAttempted: true, ghBinaryFound: true, ghAuthTokenAvailable: false } };
}

export function repoParts(repo: string): { owner: string; name: string } {
  const [owner, name, extra] = repo.split("/");
  if (!owner || !name || extra !== undefined) throw new Error("--repo must be in owner/name format");
  return { owner, name };
}

export function preview(text: string): string {
  return text.length > PREVIEW_CHARS ? `${text.slice(0, PREVIEW_CHARS)}...` : text;
}

export function previewLines(text: string, maxLines = 12): string[] {
  return text.split(/\r?\n/).slice(0, maxLines).map((line) => preview(line));
}

export function bodySha(text: string): string {
  return createHash("sha256").update(text, "utf8").digest("hex");
}

export function binarySha(data: Buffer): string {
  return createHash("sha256").update(data).digest("hex");
}

export function normalizeExpectedSha(raw: string): string {
  const value = raw.replace(/^sha256:/iu, "").toLowerCase();
  if (!/^[a-f0-9]{64}$/u.test(value)) throw new Error("--expect-body-sha must be a 64-character SHA-256 hex value, optionally prefixed with sha256:");
  return value;
}

export function shellPollutionEvidence(body: string): string[] {
  const evidence: string[] = [];
  if (body.includes("\\n")) evidence.push("literal-backslash-n");
  if (body.includes("\\t")) evidence.push("literal-backslash-t");
  if (body.includes("\\`")) evidence.push("escaped-backtick");
  if (/\\x1b|\\u001b|\u001b\[/u.test(body)) evidence.push("ansi-escape");
  if (/\r(?!\n)/u.test(body)) evidence.push("bare-carriage-return");
  return evidence;
}

export function bodySafetySignals(body: string): Record<string, unknown> {
  const evidence = shellPollutionEvidence(body);
  return {
    bodyChars: body.length,
    bodyTrimmedChars: body.trim().length,
    bodyLines: body.length === 0 ? 0 : body.split(/\r?\n/).length,
    bodySha: bodySha(body),
    preservesRawNewlines: body.includes("\n"),
    containsLiteralBackslashN: body.includes("\\n"),
    containsBackticks: body.includes("`"),
    containsMarkdownTable: /^\s*\|.+\|\s*$/m.test(body),
    shellPollution: {
      suspected: evidence.length > 0,
      evidence,
    },
  };
}
