// SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-default-render

import { isRecord } from "./notify-claudeqq";
import { ghShort, ghTable, ghText } from "./render";
import type { GitHubCommandResult } from "./types";

const STRUCTURED_FLAGS = new Set(["--json", "--full", "--raw"]);

export function withGhDefaultRendered(args: string[], value: unknown): unknown {
  if (wantsStructuredGhOutput(args)) return value;
  if (isGhHelpPayload(value)) {
    return {
      ...value,
      ok: true,
      command: typeof value.command === "string" ? value.command : "gh",
      contentType: "text/plain",
      renderedText: renderGhHelp(value),
    };
  }
  if (!isGitHubCommandResult(value)) return value;
  if (typeof value.renderedText === "string") return value;
  return {
    ...value,
    contentType: "text/plain",
    renderedText: renderGhDefaultText(value),
  };
}

function wantsStructuredGhOutput(args: string[]): boolean {
  return args.some((arg) => STRUCTURED_FLAGS.has(arg));
}

function isGitHubCommandResult(value: unknown): value is GitHubCommandResult {
  return isRecord(value)
    && typeof value.ok === "boolean"
    && typeof value.command === "string"
    && typeof value.repo === "string";
}

function isGhHelpPayload(value: unknown): value is Record<string, unknown> {
  return isRecord(value)
    && typeof value.command === "string"
    && (Array.isArray(value.usage) || Array.isArray(value.notes) || typeof value.fullHelpCommand === "string");
}

function renderGhDefaultText(result: GitHubCommandResult): string {
  if (result.ok === false) return renderGhError(result);
  const command = result.command;
  if (isIssueReadResult(result)) return renderIssueView(result);
  if (command === "issue comments") return renderIssueComments(result);
  if (isPrReadResult(result)) return renderPrView(result);
  if (command === "pr list") return renderPrList(result);
  if (command === "run list") return renderRunList(result);
  if (command === "run view") return renderRunView(result);
  if (command === "run logs") return renderRunLogs(result);
  if (command === "run benchmark") return renderRunBenchmark(result);
  if (command === "pr files" || command === "pr diff --stat") return renderPrFiles(result);
  if (command === "pr review-plan") return renderPrReviewPlan(result);
  if (command === "pr diff" && isRecord(result.file)) return renderPrDiffFile(result);
  if (command.includes("comment")) return renderCommentResult(result);
  if (command === "auth status") return renderAuthStatus(result);
  if (command.startsWith("repo ")) return renderRepoResult(result);
  if (command.startsWith("issue board-row")) return renderBoardRowResult(result);
  if (command === "issue board-audit") return renderBoardAudit(result);
  if (command === "issue attachment list" || command === "issue attachment download") return renderAttachmentResult(result);
  if (command === "issue stale-close") return renderStaleClose(result);
  if (command === "issue scan-escape" || command === "issue cleanup-plan") return renderScanEscape(result);
  if (command === "issue close" || command === "issue reopen") return renderIssueLifecycle(result);
  if (command === "issue update" || command === "issue edit" || command === "issue patch") return renderIssueWrite(result);
  if (command === "pr update" || command === "pr edit" || command === "pr close" || command === "pr reopen" || command === "pr ready") return renderPrWrite(result);
  return renderGenericResult(result);
}

function renderGhHelp(help: Record<string, unknown>): string {
  const usage = Array.isArray(help.usage) ? help.usage.filter((item): item is string => typeof item === "string") : [];
  const notes = Array.isArray(help.notes) ? help.notes.filter((item): item is string => typeof item === "string") : [];
  const usageRows = usage.slice(0, 24).map((line, index) => [String(index + 1), ghShort(line, 132)]);
  const lines = [
    `${ghText(help.command)} help`,
    "",
    usageRows.length > 0 ? ghTable(["#", "USAGE"], usageRows) : ghText(help.message),
    "",
    "Summary:",
    `  usage=${usage.length} shown=${usageRows.length} notes=${notes.length} output=text`,
  ];
  if (notes.length > 0) lines.push("", "Notes:", ...notes.slice(0, 8).map((note) => `  ${ghShort(note, 160)}`));
  const fullHelpCommand = typeof help.fullHelpCommand === "string" ? help.fullHelpCommand : "bun scripts/cli.ts gh --help";
  lines.push("", "Next:", `  ${fullHelpCommand}`, "  bun scripts/cli.ts gh <command> --help");
  lines.push("", "Disclosure:", "  help output is bounded; use scoped help for a specific command or --raw where supported for structured metadata.");
  return lines.join("\n");
}

function renderGhError(result: GitHubCommandResult): string {
  const details = record(result.details);
  const message = ghText(result.message ?? details.message);
  const reason = ghText(result.degradedReason ?? details.degradedReason ?? "failed");
  const lines = [
    `gh ${result.command} (failed)`,
    "",
    ghTable(["COMMAND", "STATUS", "REASON", "MESSAGE"], [[
      result.command,
      "failed",
      ghShort(reason, 36),
      ghShort(message, 120),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} runnerDisposition=${ghText(result.runnerDisposition ?? details.runnerDisposition)}`,
  ];
  const supportedCommands = Array.isArray(result.supportedCommands) ? result.supportedCommands.filter((item): item is string => typeof item === "string") : [];
  if (supportedCommands.length > 0) lines.push("", "Next:", ...supportedCommands.slice(0, 4).map((command) => `  ${command}`));
  lines.push("", "Disclosure:", "  default error output is compact; rerun with explicit --json/--full/--raw where supported for structured diagnostics.");
  return lines.join("\n");
}

function isIssueReadResult(result: GitHubCommandResult): boolean {
  return (result.command === "issue view" || result.command === "issue read") && isRecord(result.issue);
}

function isPrReadResult(result: GitHubCommandResult): boolean {
  return (result.command === "pr view" || result.command === "pr read") && isRecord(result.pullRequest);
}

function renderIssueView(result: GitHubCommandResult): string {
  const issue = record(result.issue);
  const number = ghText(issue.number);
  const body = bodyInfo(issue);
  const readCommands = {
    ...record(record(result.compatibility).readCommands),
    ...record(result.readCommands),
  };
  const lines = [
    `gh ${result.command} (observed)`,
    "",
    ghTable(["ISSUE", "STATE", "UPDATED", "COMMENTS", "BODY", "TITLE"], [[
      number === "-" ? "-" : `#${number}`,
      ghText(issue.state),
      shortDate(issue.updatedAt),
      ghText(issue.commentCount),
      body.label,
      ghShort(ghText(issue.title), 96),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} url=${ghText(issue.url)}`,
    `  author=${ghText(issue.author)} bodySha=${ghText(issue.bodySha)} bodyPreview=${body.preview}`,
    "",
    "Next:",
    `  ${ghText(readCommands.body ?? `trans gh:/${result.repo}/issue/${number} cat`)}`,
    `  ${ghText(readCommands.search ?? `trans gh:/${result.repo}/issue/${number} rg <pattern>`)}`,
    `  bun scripts/cli.ts gh issue comments ${number} --repo ${result.repo}`,
    "",
    "Disclosure:",
    "  default view omits full bodies; use trans gh: cat/rg for human body reading, bounded issue comments for progress, and --json body/--full/--raw only for explicit structured disclosure.",
  ];
  return lines.join("\n");
}

function renderIssueComments(result: GitHubCommandResult): string {
  const issue = record(result.issue);
  const comments = arrayOfRecords(result.comments);
  const commentRange = record(result.commentRange);
  const readCommands = record(result.readCommands);
  const rows = comments.slice(0, 40).map((comment) => [
    ghText(comment.id),
    ghText(comment.author),
    shortDate(comment.createdAt),
    ghShort(ghText(comment.url), 40),
    ghShort(previewFrom(comment), 72),
  ]);
  const lines = [
    "gh issue comments (observed)",
    "",
    rows.length > 0
      ? ghTable(["COMMENT", "AUTHOR", "CREATED", "URL", "PREVIEW"], rows)
      : "No comments found.",
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} issue=#${ghText(issue.number ?? result.issueNumber)} total=${ghText(result.totalComments)} returned=${ghText(result.returned)} omitted=${ghText(result.omitted)}`,
    `  range=${ghText(commentRange.from)}..${ghText(commentRange.to)} title=${ghShort(ghText(issue.title), 96)}`,
    "",
    "Next:",
    `  ${ghText(readCommands.full ?? `bun scripts/cli.ts gh issue comments ${ghText(issue.number ?? result.issueNumber)} --repo ${result.repo} --full`)}`,
    `  ${ghText(readCommands.comment ?? `bun scripts/cli.ts gh issue comment view <commentId> --repo ${result.repo} --full`)}`,
    "",
    "Disclosure:",
    "  default comments output is a bounded recent-comment table with body preview; use --full/--raw for structured full bodies.",
  ];
  return lines.join("\n");
}

function renderPrView(result: GitHubCommandResult): string {
  const pr = record(result.pullRequest);
  const number = ghText(pr.number);
  const body = bodyInfo(pr);
  const head = record(pr.head);
  const base = record(pr.base);
  const readCommands = record(result.readCommands);
  return [
    `gh ${result.command} (observed)`,
    "",
    ghTable(["PR", "STATE", "BASE", "HEAD", "DRAFT", "BODY", "TITLE"], [[
      number === "-" ? "-" : `#${number}`,
      ghText(pr.stateDetail ?? pr.state),
      ghText(base.ref ?? pr.baseRefName),
      ghText(head.ref ?? pr.headRefName),
      ghText(pr.draft),
      body.label,
      ghShort(ghText(pr.title), 96),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} url=${ghText(pr.url)}`,
    `  author=${ghText(pr.author)} merged=${ghText(pr.merged)} bodySha=${ghText(pr.bodySha)} bodyPreview=${body.preview}`,
    "",
    "Next:",
    `  ${ghText(readCommands.body ?? `trans gh:/${result.repo}/pr/${number} cat`)}`,
    `  ${ghText(readCommands.search ?? `trans gh:/${result.repo}/pr/${number} rg <pattern>`)}`,
    `  bun scripts/cli.ts gh pr files ${number} --repo ${result.repo}`,
    "",
    "Disclosure:",
    "  default view omits full pullRequest.body; use trans gh: cat/rg for human reading and --json body/--full/--raw only for explicit structured disclosure.",
  ].join("\n");
}

function renderPrList(result: GitHubCommandResult): string {
  const prs = arrayOfRecords(result.pullRequests);
  const rows = prs.slice(0, 40).map((pr) => {
    const head = record(pr.head);
    const base = record(pr.base);
    return [
      `#${ghText(pr.number)}`,
      ghText(pr.state),
      shortDate(pr.updatedAt),
      ghText(base.ref),
      ghText(head.ref),
      ghText(pr.draft),
      ghShort(ghText(pr.title), 96),
    ];
  });
  return [
    "gh pr list (observed)",
    "",
    ghTable(["PR", "STATE", "UPDATED", "BASE", "HEAD", "DRAFT", "TITLE"], rows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} state=${ghText(result.state)} count=${ghText(result.count)} limit=${ghText(result.limit)}`,
    "",
    "Next:",
    "  bun scripts/cli.ts gh pr view <number> --repo " + ghText(result.repo),
    "",
    "Disclosure:",
    "  default list omits PR bodies and closeout metadata; use --json fields, --full, or --raw for structured output.",
  ].join("\n");
}

function renderPrFiles(result: GitHubCommandResult): string {
  const files = arrayOfRecords(result.files);
  const rows = files.slice(0, 40).map((file) => [
    ghShort(ghText(file.filename), 84),
    ghText(file.status),
    ghText(file.additions),
    ghText(file.deletions),
    ghText(file.changes),
  ]);
  const summary = record(result.summary);
  const truncation = record(result.truncation);
  const next = record(result.next);
  const lines = [
    `gh ${result.command} (observed)`,
    "",
    ghTable(["FILE", "STATUS", "ADD", "DEL", "CHANGES"], rows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} pr=#${ghText(record(result.pullRequest).number)} files=${ghText(summary.files)} returned=${ghText(result.filesReturned)} truncated=${ghText(truncation.truncated)}`,
    `  additions=${ghText(summary.additions)} deletions=${ghText(summary.deletions)} changes=${ghText(summary.changes)}`,
  ];
  if (typeof next.command === "string") lines.push("", "Next:", `  ${next.command}`);
  lines.push("", "Disclosure:", "  default view is a bounded file/stat table and never emits raw diffs.");
  return lines.join("\n");
}

function renderPrReviewPlan(result: GitHubCommandResult): string {
  const files = arrayOfRecords(result.files);
  const rows = files.slice(0, 40).map((file) => {
    const patch = record(file.patch);
    return [
      ghText(file.index),
      ghShort(ghText(file.filename), 72),
      ghText(file.status),
      ghText(file.additions),
      ghText(file.deletions),
      ghText(patch.hunks),
      ghText(patch.lines),
      ghText(patch.defaultTruncated),
    ];
  });
  const summary = record(result.summary);
  const truncation = record(result.truncation);
  const drillDown = files
    .map((file) => record(file.drillDown).patch)
    .filter((command): command is string => typeof command === "string")
    .slice(0, 40);
  const next = record(result.next);
  const lines = [
    "gh pr review-plan (observed)",
    "",
    ghTable(["#", "FILE", "STATUS", "ADD", "DEL", "HUNKS", "PATCH_LINES", "PATCH_TRUNC"], rows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} pr=#${ghText(record(result.pullRequest).number)} files=${ghText(summary.files)} returned=${ghText(result.filesReturned)} truncated=${ghText(truncation.truncated)}`,
    `  additions=${ghText(summary.additions)} deletions=${ghText(summary.deletions)} changes=${ghText(summary.changes)}`,
    "",
    "Next:",
  ];
  if (drillDown.length > 0) lines.push(...drillDown.map((command, index) => `  [${index + 1}] ${command}`));
  else lines.push(`  ${ghText(next.metadata)}`);
  if (typeof next.files === "string") lines.push(`  ${next.files}`);
  lines.push("", "Disclosure:", "  default review-plan is a bounded changed-file index; it omits patch bodies and prints per-file drill-down commands.");
  return lines.join("\n");
}

function renderPrDiffFile(result: GitHubCommandResult): string {
  const file = record(result.file);
  const patch = record(result.patch);
  const selectedHunk = record(patch.hunk);
  const patchLines = Array.isArray(patch.lines) ? patch.lines.filter((line): line is string => typeof line === "string") : [];
  const next = record(result.next);
  const lines = [
    "gh pr diff (observed)",
    "",
    ghTable(["FILE", "STATUS", "ADD", "DEL", "HUNKS", "SELECTED", "LINES", "SHOWN", "TRUNC"], [[
      ghShort(ghText(file.filename), 72),
      ghText(file.status),
      ghText(file.additions),
      ghText(file.deletions),
      ghText(file.hunks),
      ghText(selectedHunk.index ?? "file"),
      ghText(patch.linesTotal),
      ghText(patch.linesShown),
      ghText(patch.truncated),
    ]]),
    "",
    "Patch:",
    ...patchLines.map((line) => `  ${line}`),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} pr=#${ghText(record(result.pullRequest).number)} file=${ghText(file.filename)} redactions=${ghText(patch.redactions)} rawDiffIncluded=${ghText(result.rawDiffIncluded)}`,
    "",
    "Next:",
    `  ${ghText(next.reviewPlan)}`,
    `  ${ghText(next.nextHunk ?? next.fullFilePatch)}`,
    "",
    "Disclosure:",
    "  default diff output is a bounded patch excerpt; use --hunk N for one hunk or --full/--raw for explicit structured full-patch disclosure.",
  ];
  return lines.join("\n");
}

function renderCommentResult(result: GitHubCommandResult): string {
  const comment = record(result.comment);
  const status = result.dryRun === true ? "dry-run" : result.deleted === true ? "deleted" : result.ok ? "ok" : "failed";
  const id = ghText(result.commentId ?? comment.id);
  const readCommands = record(result.readCommands);
  const lines = [
    `gh ${result.command} (${status})`,
    "",
    ghTable(["COMMENT", "STATUS", "BODY", "UPDATED", "URL"], [[
      id,
      status,
      `${ghText(result.bodyChars ?? comment.bodyChars)} chars`,
      shortDate(comment.updatedAt),
      ghShort(ghText(comment.url), 96),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} bodySha=${ghText(result.bodySha ?? comment.bodySha)} preview=${previewFrom(comment)}`,
  ];
  if (typeof readCommands.full === "string" || typeof readCommands.comments === "string") lines.push("", "Next:", `  ${ghText(readCommands.comments ?? readCommands.full)}`);
  lines.push("", "Disclosure:", "  default comment output omits full body; use comment view <id> --full or --raw for explicit disclosure.");
  return lines.join("\n");
}

function renderAuthStatus(result: GitHubCommandResult): string {
  const token = record(result.token);
  const gh = record(result.gh);
  const probes = record(result.probes);
  const capabilities = record(result.capabilities);
  const repositoryRead = record(capabilities.repositoryRead);
  const repositoryWrite = record(capabilities.repositoryWrite);
  const pullRequestMerge = record(capabilities.pullRequestMerge);
  const warnings = Array.isArray(result.warnings) ? result.warnings : [];
  const tokenDetail = [
    `source=${ghText(token.source)}`,
    `scope=${ghText(token.scope)}`,
    `yaml=${ghText(token.yamlSourceEnabled)}`,
    `priority=${ghText(token.yamlSourcePriority)}`,
    `sourceRef=${ghText(token.sourceRef)}`,
    `key=${ghText(token.sourceKey)}`,
    `projectedEnv=${ghText(token.projectedEnv)}`,
    `projectedEnvPresent=${ghText(token.projectedEnvPresent)}`,
    `fingerprint=${ghText(token.tokenFingerprint)}`,
    `ghFallback=${ghText(token.ghFallbackAttempted)}`,
  ].filter((item) => !item.endsWith("=-") && !item.endsWith("=undefined")).join(" ");
  const rows = [
    ["token", token.present === true ? "ok" : "missing", tokenDetail],
    ["gh-binary", gh.binaryFound === true ? "ok" : "missing", `path=${ghText(gh.path)}`],
    ["rest-api", probeStatus(probes.restApi), ghText(probes.restApi)],
    ["repo", probeStatus(probes.repo), probeDetail(probes.repo)],
    ["issue-read", probeStatus(probes.issueRead), probeDetail(probes.issueRead)],
    ["repo-read", capabilityProbeStatus(repositoryRead), capabilityProbeDetail(repositoryRead)],
    ["repo-write", capabilityProbeStatus(repositoryWrite), capabilityProbeDetail(repositoryWrite)],
    ["pr-merge", capabilityProbeStatus(pullRequestMerge), capabilityProbeDetail(pullRequestMerge)],
  ];
  return [
    `gh auth status (${result.ok ? "ok" : "failed"})`,
    "",
    ghTable(["CHECK", "STATUS", "DETAIL"], rows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} restFallback=${ghText(result.restFallback)} valuesPrinted=false`,
    `  warnings=${warnings.length} blocking=false`,
    "",
    "Disclosure:",
    "  token values are never printed; use --raw only where supported for structured diagnostics.",
  ].join("\n");
}

function capabilityProbeStatus(value: Record<string, unknown>): string {
  if (value.status === "available") return "ok";
  if (value.status === "unavailable") return "unavailable";
  if (value.status === "unknown") return "unknown";
  return "skipped";
}

function capabilityProbeDetail(value: Record<string, unknown>): string {
  if (Object.keys(value).length === 0) return "not-probed";
  return [
    `status=${ghText(value.status)}`,
    `permission=${ghText(value.permissionLevel)}`,
    `evidence=${ghText(value.evidence ?? value.inferredFrom)}`,
    `reason=${ghText(value.reason)}`,
    `mutation=${ghText(value.mutation)}`,
  ].filter((item) => !item.endsWith("=-") && !item.endsWith("=undefined")).join(" ");
}

function renderRepoResult(result: GitHubCommandResult): string {
  const repo = record(result.repository);
  const planned = record(result.planned);
  const status = result.dryRun === true ? "dry-run" : result.command === "repo create" ? "created" : "observed";
  return [
    `gh ${result.command} (${status})`,
    "",
    ghTable(["REPO", "STATUS", "PRIVATE", "URL", "DESCRIPTION"], [[
      ghText(repo.fullName ?? result.repo),
      status,
      ghText(repo.private ?? planned.visibility),
      ghShort(ghText(repo.url ?? repo.htmlUrl), 96),
      ghShort(ghText(repo.description ?? planned.description), 80),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} dryRun=${ghText(result.dryRun)}`,
    "",
    "Disclosure:",
    "  default repo output is compact; use --raw where supported for request metadata.",
  ].join("\n");
}

function renderBoardRowResult(result: GitHubCommandResult): string {
  const rows = arrayOfRecords(result.rows);
  const row = isRecord(result.row) ? [result.row] : [];
  const shown = rows.length > 0 ? rows : row;
  const tableRows = shown.slice(0, 30).map((item) => [
    `#${ghText(item.issueNumber ?? item.number)}`,
    ghText(item.section ?? item.sectionKind ?? result.state),
    ghShort(ghText(item.status ?? item.githubStatus ?? item.summary), 28),
    ghShort(ghText(item.progress ?? item.focus ?? item.raw), 96),
  ]);
  const boardIssue = record(result.boardIssue);
  const lines = [
    `gh ${result.command} (${result.dryRun === true ? "dry-run" : "ok"})`,
    "",
    ghTable(["ISSUE", "SECTION", "STATUS", "SUMMARY"], tableRows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} board=#${ghText(boardIssue.number)} issue=#${ghText(result.issueNumber)} count=${ghText(result.count)} operation=${ghText(result.operation)}`,
  ];
  lines.push("", "Disclosure:", "  default board-row output is a bounded row summary; use --full/--raw where supported for mutation plans.");
  return lines.join("\n");
}

function renderBoardAudit(result: GitHubCommandResult): string {
  const sections = arrayOfRecords(result.sections);
  const rows = sections.map((section) => [
    ghText(section.kind),
    ghShort(ghText(section.heading), 80),
    ghText(section.rows),
    ghText(section.headerLine),
  ]);
  const summary = record(result.summary);
  return [
    "gh issue board-audit (observed)",
    "",
    ghTable(["KIND", "HEADING", "ROWS", "LINE"], rows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} board=#${ghText(record(result.boardIssue).number)} sections=${ghText(summary.parsedSections)} rows=${ghText(summary.parsedRows)} warnings=${ghText(summary.parserWarnings)}`,
    "",
    "Disclosure:",
    "  default board audit prints parsed section counts only; use --raw for full parser details.",
  ].join("\n");
}

function renderAttachmentResult(result: GitHubCommandResult): string {
  const attachments = arrayOfRecords(result.attachments);
  const rows = attachments.map((attachment) => [
    ghText(attachment.index),
    ghText(attachment.source),
    ghText(attachment.assetId),
    ghShort(ghText(attachment.url), 96),
  ]);
  const output = record(result.output);
  const next = record(result.next);
  const table = rows.length > 0
    ? ghTable(["INDEX", "SOURCE", "ASSET", "URL"], rows)
    : result.command === "issue attachment download"
      ? ghTable(["OUTPUT", "BYTES", "SHA256", "TYPE"], [[ghText(output.path), ghText(output.bytes), ghShort(ghText(output.sha256), 16), ghText(output.contentType)]])
      : "No attachments found.";
  const lines = [
    `gh ${result.command} (${result.dryRun === true ? "dry-run" : "ok"})`,
    "",
    table,
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} issue=#${ghText(record(result.issue).number)} count=${ghText(result.count)} commentsScanned=${ghText(result.commentsScanned)}`,
  ];
  if (typeof next.command === "string") lines.push("", "Next:", `  ${next.command}`);
  lines.push("", "Disclosure:", "  default attachment output prints metadata/path only and never prints binary bytes.");
  return lines.join("\n");
}

function renderStaleClose(result: GitHubCommandResult): string {
  const stale = record(result.stale);
  const closed = record(result.closed);
  return [
    `gh issue stale-close (${result.dryRun === true ? "dry-run" : "applied"})`,
    "",
    ghTable(["SCOPE", "COUNT", "RETURNED", "OMITTED"], [
      ["stale", ghText(stale.count), ghText(stale.returned), ghText(stale.omitted)],
      ["closed", ghText(closed.count), ghText(closed.returned), ghText(closed.omitted)],
    ]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} scanned=${ghText(result.scannedCount)} stale=${ghText(result.staleCount)} closed=${ghText(result.closedCount)} failed=${ghText(result.failedCount)}`,
    "",
    "Disclosure:",
    "  default stale-close output is a lifecycle summary; use --raw for candidate arrays and failure details.",
  ].join("\n");
}

function renderScanEscape(result: GitHubCommandResult): string {
  const summary = record(result.summary);
  return [
    `gh ${result.command} (observed)`,
    "",
    ghTable(["FINDINGS", "POLLUTION", "EXPLANATORY", "RISK", "BODY_RISK", "COMMENT_ERRORS"], [[
      ghText(summary.findings),
      ghText(summary.suspectedPollution),
      ghText(summary.explanatoryMention),
      ghText(summary.risk),
      ghText(summary.bodyRisks),
      ghText(summary.commentScanErrors),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} scannedIssues=${ghText(result.scannedIssues)} scannedComments=${ghText(result.scannedComments)} suggestions=${ghText(summary.suggestions)}`,
    "",
    "Disclosure:",
    "  default scan output is a bounded summary; use --raw for full findings.",
  ].join("\n");
}

function renderIssueLifecycle(result: GitHubCommandResult): string {
  const issue = record(result.issue);
  const comment = record(result.comment);
  const readCommands = record(result.readCommands);
  const commentStatus = comment.id ?? (comment.planned === true ? "planned" : "-");
  return [
    `gh ${result.command} (${result.dryRun === true ? "dry-run" : "updated"})`,
    "",
    ghTable(["ISSUE", "STATE", "COMMENT", "UPDATED", "TITLE"], [[
      `#${ghText(result.issueNumber ?? issue.number)}`,
      ghText(issue.state ?? record(result.wouldPatch).state),
      ghText(commentStatus),
      shortDate(issue.updatedAt),
      ghShort(ghText(issue.title), 96),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} bodySha=${ghText(issue.bodySha)} fullBodyIncluded=${ghText(record(result.disclosure).fullBodyIncluded)}`,
    "",
    "Next:",
    `  ${ghText(readCommands.body ?? `trans gh:/${result.repo}/issue/${ghText(result.issueNumber ?? issue.number)} cat`)}`,
    `  ${ghText(readCommands.search ?? `trans gh:/${result.repo}/issue/${ghText(result.issueNumber ?? issue.number)} rg <pattern>`)}`,
    "",
    "Disclosure:",
    "  default lifecycle output omits full issue.body; use trans gh: cat/rg for human reading and readCommands.json/full/raw only for explicit structured disclosure.",
  ].join("\n");
}

function renderIssueWrite(result: GitHubCommandResult): string {
  const issue = record(result.issue);
  const bodyChange = record(result.bodyChange);
  const readCommands = record(result.readCommands);
  return [
    `gh ${result.command} (${result.dryRun === true ? "dry-run" : "updated"})`,
    "",
    ghTable(["ISSUE", "MODE", "BODY", "DELTA", "UPDATED", "TITLE"], [[
      `#${ghText(result.issueNumber ?? issue.number)}`,
      ghText(result.mode ?? record(result.wouldPatch).mode),
      `${ghText(result.bodyChars ?? issue.bodyChars ?? bodyChange.newBodyChars)} chars`,
      ghText(bodyChange.deltaChars),
      shortDate(issue.updatedAt ?? bodyChange.newUpdatedAt),
      ghShort(ghText(issue.title ?? result.title), 96),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} bodySha=${ghText(result.bodySha ?? issue.bodySha ?? bodyChange.newBodySha)} fullBodyIncluded=${ghText(record(result.disclosure).fullBodyIncluded)}`,
    "",
    "Next:",
    `  ${ghText(readCommands.body ?? `trans gh:/${result.repo}/issue/${ghText(result.issueNumber ?? issue.number)} cat`)}`,
    `  ${ghText(readCommands.search ?? `trans gh:/${result.repo}/issue/${ghText(result.issueNumber ?? issue.number)} rg <pattern>`)}`,
    "",
    "Disclosure:",
    "  default write output omits full issue.body; human verification uses trans gh: cat/rg, while --json body/--full/--raw are explicit structured disclosure.",
  ].join("\n");
}

function renderPrWrite(result: GitHubCommandResult): string {
  const pr = record(result.pullRequest);
  const number = ghText(result.number ?? pr.number);
  const status = result.dryRun === true
    ? "dry-run"
    : result.action === "already-ready"
      ? "already-ready"
      : "updated";
  return [
    `gh ${result.command} (${status})`,
    "",
    ghTable(["PR", "STATE", "CHANGED", "URL", "TITLE"], [[
      `#${ghText(result.number ?? pr.number)}`,
      ghText(pr.stateDetail ?? pr.state ?? record(result.wouldPatch).state),
      arrayText(result.changedFields),
      ghShort(ghText(result.url ?? pr.url), 96),
      ghShort(ghText(pr.title), 96),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} rest=${ghText(result.rest)} graphQl=${ghText(result.graphQl)}`,
    "",
    "Next:",
    `  trans gh:/${result.repo}/pr/${number} cat`,
    `  trans gh:/${result.repo}/pr/${number} rg <pattern>`,
    `  bun scripts/cli.ts gh pr view ${number} --repo ${result.repo}`,
    "",
    "Disclosure:",
    "  default PR write output is compact; use trans gh: cat/rg for human body verification and --json body/--full/--raw only for explicit structured disclosure.",
  ].join("\n");
}

function renderGenericResult(result: GitHubCommandResult): string {
  const rows = Object.entries(result)
    .filter(([key]) => !["ok", "command", "repo", "renderedText", "contentType"].includes(key))
    .slice(0, 16)
    .map(([key, value]) => [key, valueSummary(value)]);
  return [
    `gh ${result.command} (${result.ok ? "ok" : "failed"})`,
    "",
    ghTable(["FIELD", "VALUE"], rows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)}`,
    "",
    "Disclosure:",
    "  default output is compact; use explicit --json/--full/--raw where supported for structured details.",
  ].join("\n");
}

function renderRunList(result: GitHubCommandResult): string {
  const runs = arrayOfRecords(result.runs);
  const rows = runs.map((run) => [
    ghText(run.id),
    ghShort(ghText(run.workflow), 28),
    ghText(run.status),
    ghText(run.conclusion),
    ghShort(ghText(run.branch), 20),
    ghShort(ghText(run.commit), 10),
    ghShort(ghText(run.title), 54),
  ]);
  return [
    "gh run list (observed)",
    "",
    ghTable(["RUN", "WORKFLOW", "STATUS", "CONCLUSION", "BRANCH", "COMMIT", "TITLE"], rows),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} count=${ghText(result.count)} total=${ghText(result.total)} mutation=false`,
    "",
    "Next:",
    `  bun scripts/cli.ts gh run view <run-id> --repo ${ghText(result.repo)}`,
  ].join("\n");
}

function renderRunView(result: GitHubCommandResult): string {
  const run = record(result.run);
  const jobs = arrayOfRecords(result.jobs);
  const jobRows = jobs.map((job) => [ghText(job.id), ghShort(ghText(job.name), 36), ghText(job.status), ghText(job.conclusion)]);
  const stepRows = jobs.flatMap((job) => arrayOfRecords(job.steps)
    .filter((step) => step.conclusion === "failure" || step.status === "in_progress")
    .map((step) => [ghShort(ghText(job.name), 24), ghText(step.number), ghShort(ghText(step.name), 54), ghText(step.status), ghText(step.conclusion)]));
  const lines = [
    "gh run view (observed)",
    "",
    ghTable(["RUN", "WORKFLOW", "STATUS", "CONCLUSION", "COMMIT", "TITLE"], [[
      ghText(run.id), ghShort(ghText(run.workflow), 28), ghText(run.status), ghText(run.conclusion), ghShort(ghText(run.commit), 10), ghShort(ghText(run.title), 54),
    ]]),
    "",
    ghTable(["JOB", "NAME", "STATUS", "CONCLUSION"], jobRows),
  ];
  if (stepRows.length > 0) lines.push("", ghTable(["JOB", "STEP", "NAME", "STATUS", "CONCLUSION"], stepRows));
  if (result.startupFailure === true) lines.push("", "Diagnosis:", `  ${ghText(result.diagnosis)}`);
  const next = Array.isArray(result.next) ? result.next.filter((value): value is string => typeof value === "string") : [];
  if (next.length > 0) lines.push("", "Next:", ...next.map((command) => `  ${command}`));
  lines.push("", "Summary:", `  repo=${ghText(result.repo)} jobs=${jobs.length} startupFailure=${ghText(result.startupFailure)} mutation=false`);
  return lines.join("\n");
}

function renderRunLogs(result: GitHubCommandResult): string {
  const job = record(result.job);
  return [
    "gh run logs (observed)",
    "",
    `Run ${ghText(result.runId)} / job ${ghText(job.id)} (${ghText(job.name)})`,
    "",
    "First failure context:",
    typeof result.failureExcerpt === "string" && result.failureExcerpt.length > 0 ? result.failureExcerpt : "<no failure marker found>",
    "",
    "Log tail:",
    typeof result.logTail === "string" && result.logTail.length > 0 ? result.logTail : "<no log lines returned>",
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} status=${ghText(job.status)} conclusion=${ghText(job.conclusion)} truncated=${ghText(result.truncated)} mutation=false`,
    "",
    "Disclosure:",
    `  bounded redacted tail: lines<=${ghText(result.lineLimit)} chars<=${ghText(result.charLimit)} valuesPrinted=false`,
  ].join("\n");
}

function renderRunBenchmark(result: GitHubCommandResult): string {
  const entries = arrayOfRecords(result.entries);
  const latest = entries[0] ?? {};
  const latestCommit = ghText(record(latest.commit).id);
  const measurements = arrayOfRecords(result.measurements)
    .filter((measurement) => ghText(measurement.commit) === latestCommit);
  const historyRows = entries.slice(0, 12).map((entry) => {
    const commit = record(entry.commit);
    const points = arrayOfRecords(result.measurements)
      .find((bench) => bench.commit === commit.id && bench.name === "Performance Points");
    return [
      ghShort(ghText(commit.id), 10),
      benchmarkDate(entry.date),
      points === undefined ? "-" : Number(points.value).toFixed(2),
      ghShort(ghText(commit.message).split("\n")[0], 72),
    ];
  });
  const measurementRows = measurements.slice(0, 30).map((bench) => [
    ghShort(ghText(bench.name), 52),
    typeof bench.value === "number" ? bench.value.toFixed(6) : ghText(bench.value),
    ghText(bench.unit),
  ]);
  const source = record(result.source);
  const next = record(result.next);
  return [
    "gh run benchmark (observed)",
    "",
    ghTable(["COMMIT", "DATE", "POINTS", "TITLE"], historyRows),
    "",
    `Latest measurements (${ghShort(ghText(record(latest.commit).id), 10)}):`,
    measurementRows.length > 0 ? ghTable(["BENCHMARK", "VALUE", "UNIT"], measurementRows) : "No measurements returned.",
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} total=${ghText(result.totalEntries)} matched=${ghText(result.matchingEntries)} returned=${ghText(result.returnedEntries)} suites=${arrayText(result.suites)} mutation=false`,
    `  source=${ghText(source.dataUrl)} bytes=${ghText(source.bytes)} lastUpdate=${benchmarkDate(source.lastUpdate)}`,
    "",
    "Next:",
    `  ${ghText(next.commit)}`,
    "",
    "Disclosure:",
    "  default output is bounded to commit history plus the latest measurements; use --raw for selected structured entries, never the full data.js source.",
  ].join("\n");
}

function benchmarkDate(value: unknown): string {
  if (typeof value === "number" && Number.isFinite(value)) return new Date(value).toISOString();
  return shortDate(value);
}

function record(value: unknown): Record<string, unknown> {
  return isRecord(value) ? value : {};
}

function arrayOfRecords(value: unknown): Record<string, unknown>[] {
  return Array.isArray(value) ? value.filter(isRecord) : [];
}

function bodyInfo(source: Record<string, unknown>): { label: string; preview: string } {
  const chars = ghText(source.bodyChars);
  const preview = previewFrom(source);
  return { label: chars === "-" ? "-" : `${chars} chars`, preview };
}

function previewFrom(source: Record<string, unknown>): string {
  if (typeof source.bodyPreview === "string") return ghShort(source.bodyPreview.replace(/\s+/gu, " "), 160);
  if (typeof source.body === "string") return ghShort(source.body.replace(/\s+/gu, " "), 160);
  if (typeof source.preview === "string") return ghShort(source.preview.replace(/\s+/gu, " "), 160);
  return "-";
}

function shortDate(value: unknown): string {
  if (typeof value !== "string" || value.length === 0) return "-";
  return ghShort(value, 19);
}

function probeStatus(value: unknown): string {
  if (value === "ok") return "ok";
  if (value === "skipped") return "skipped";
  if (isRecord(value) && value.ok === true) return "ok";
  if (isRecord(value) && value.ok === false) return "failed";
  return "-";
}

function probeDetail(value: unknown): string {
  if (isRecord(value)) return valueSummary(value);
  return ghText(value);
}

function arrayText(value: unknown): string {
  return Array.isArray(value) ? value.map(ghText).join(",") || "-" : ghText(value);
}

function valueSummary(value: unknown): string {
  if (value === null || value === undefined || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return ghShort(ghText(value), 120);
  if (Array.isArray(value)) return `array(${value.length})`;
  if (isRecord(value)) {
    const keys = Object.keys(value);
    const title = ghText(value.title ?? value.name ?? value.number ?? value.id ?? value.path ?? value.command);
    return title === "-" ? `object(${keys.slice(0, 5).join(",")})` : ghShort(title, 120);
  }
  return ghShort(String(value), 120);
}
