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

import path from "node:path";
import { bodySha, preview, previewLines, readIssueLifecycleCommentBody, repoParts } from "./auth-and-safety";
import { assertConcurrencyOptions, validateCommentConcurrency } from "./body-guards";
import { applyVirtualBodyPatch, bodyPatchErrorSummary, readBodyPatch, readIssueCommentBody } from "./body-input";
import { commandError, githubRequest, isGitHubError, issueBodyReadCommands, issueCommentReadCommands, validationError, writeBodyPlan } from "./client";
import { getIssueComment } from "./issue-read";
import { issueLifecycleDisclosure, issueLifecycleSummary } from "./issue-summary";
import { isRecord } from "./notify-claudeqq";
import { commentSummary, compactCommentSummary, ghShort, ghTable, ghText } from "./render";
import type { GitHubCommandResult, GitHubComment, GitHubIssue, GitHubOptions } from "./types";

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

export function renderIssueCommentCreateTable(result: GitHubCommandResult): string {
  const comment = isRecord(result.comment) ? result.comment : {};
  const readCommands = isRecord(result.readCommands) ? result.readCommands : {};
  const status = result.dryRun === true ? "dry-run" : "created";
  const issueNumber = ghText(result.issueNumber);
  const commentId = ghText(comment.id ?? result.commentId);
  const sourceKind = isRecord(result.bodySource) ? result.bodySource.kind : result.source;
  const lines = [
    `gh issue comment create (${status})`,
    "",
    ghTable(["ISSUE", "COMMENT", "STATUS", "BODY", "URL"], [[
      issueNumber === "-" ? "-" : `#${issueNumber}`,
      commentId,
      status,
      `${ghText(result.bodyChars ?? comment.bodyChars)} chars`,
      ghShort(ghText(comment.url ?? result.url), 96),
    ]]),
    "",
    "Summary:",
    `  repo=${ghText(result.repo)} source=${ghText(sourceKind)} bodySha=${ghText(result.bodySha ?? comment.bodySha)}`,
    "",
    "Next:",
    `  ${ghText(readCommands.comments ?? readCommands.full ?? readCommands.raw)}`,
    "",
    "Disclosure:",
    "  default view is a bounded table; use --full/--raw or the read command for full comment metadata/body.",
  ];
  return lines.join("\n");
}

export async function issueComment(repo: string, token: string, issueNumber: number, options: GitHubOptions): Promise<GitHubCommandResult> {
  let bodyInput: { body: string; bodySource: Record<string, unknown> };
  try {
    bodyInput = readIssueCommentBody(options, "issue comment create");
  } catch (error) {
    return validationError("issue comment create", repo, error instanceof Error ? error.message : String(error), { issueNumber });
  }
  const { body, bodySource } = bodyInput;
  if (options.dryRun) {
    return withIssueCommentCreateRendered({
      ok: true,
      command: "issue comment create",
      repo,
      dryRun: true,
      planned: true,
      issueNumber,
      readCommands: issueCommentReadCommands(repo, issueNumber),
      ...writeBodyPlan("issue comment create", repo, body, bodySource, { issueNumber }),
    });
  }
  const { owner, name } = repoParts(repo);
  const comment = await githubRequest<GitHubComment>(token, "POST", `/repos/${owner}/${name}/issues/${issueNumber}/comments`, { body });
  if (isGitHubError(comment)) return commandError("issue comment", repo, comment, { issueNumber });
  return withIssueCommentCreateRendered({
    ok: true,
    command: "issue comment create",
    repo,
    issueNumber,
    comment: compactCommentSummary(comment),
    bodySource,
    bodyChars: body.length,
    bodySha: bodySha(body),
    bodyPreview: preview(body),
    source: String(bodySource.kind ?? "unknown"),
    readCommands: issueCommentReadCommands(repo, issueNumber),
    rest: true,
  });
}

export async function commentPatch(repo: string, token: string, ownerKind: "issue" | "pr", commentId: number, options: GitHubOptions, commandName?: string): Promise<GitHubCommandResult> {
  const command = commandName ?? `${ownerKind} comment patch`;
  const concurrencyOptionError = assertConcurrencyOptions(options, command);
  if (concurrencyOptionError !== null) return concurrencyOptionError;
  let patchInput: { patch: string; patchSource: Record<string, unknown> };
  try {
    patchInput = readBodyPatch(options, command);
  } catch (error) {
    return validationError(command, repo, error instanceof Error ? error.message : String(error), { commentId });
  }
  const { patch, patchSource } = patchInput;
  const readCommands = ownerKind === "issue"
    ? {
      comments: `bun scripts/cli.ts gh issue view <issueNumber> --repo ${repo} --json comments`,
      full: `bun scripts/cli.ts gh issue view <issueNumber> --repo ${repo} --full`,
      note: "GitHub comment patch targets commentId directly; use the owning issue number to read the surrounding comments.",
    }
    : {
      comments: `bun scripts/cli.ts gh issue view <prNumber> --repo ${repo} --json comments`,
      full: `bun scripts/cli.ts gh issue view <prNumber> --repo ${repo} --full`,
      note: "GitHub PR comments are issue comments; use the owning PR number through gh issue view to read the surrounding comments.",
    };
  const oldComment = await getIssueComment(token, repo, commentId);
  if (isGitHubError(oldComment)) return commandError(command, repo, oldComment, { commentId, phase: "read-before-patch" });
  const concurrencyError = validateCommentConcurrency(repo, commentId, oldComment, options, command);
  if (concurrencyError !== null) return concurrencyError;
  const oldBody = oldComment.body ?? "";
  let patched: { newBody: string; patchSummary: Record<string, unknown> };
  try {
    patched = applyVirtualBodyPatch("comment.md", oldBody, patch);
  } catch (error) {
    return validationError(command, repo, "body patch failed; no GitHub write performed", {
      commentId,
      patchSource,
      patchError: bodyPatchErrorSummary(error),
      oldBody: {
        bodyChars: oldBody.length,
        bodySha: bodySha(oldBody),
        updatedAt: oldComment.updated_at ?? null,
      },
      noWrite: true,
    });
  }
  const bodyChange = {
    oldBodyChars: oldBody.length,
    newBodyChars: patched.newBody.length,
    deltaChars: patched.newBody.length - oldBody.length,
    oldBodySha: bodySha(oldBody),
    newBodySha: bodySha(patched.newBody),
    oldUpdatedAt: oldComment.updated_at ?? null,
  };
  const concurrency = {
    checked: true,
    oldCommentUpdatedAt: oldComment.updated_at ?? null,
    oldBodySha: bodySha(oldBody),
    expectUpdatedAt: options.expectUpdatedAt ?? null,
    expectBodySha: options.expectBodySha ?? null,
  };
  if (options.dryRun) {
    return {
      ok: true,
      command,
      repo,
      dryRun: true,
      planned: true,
      commentId,
      patchSource,
      patch: patched.patchSummary,
      bodyPreview: preview(patched.newBody),
      bodyPreviewLines: previewLines(patched.newBody, 4),
      bodyChars: patched.newBody.length,
      bodySha: bodySha(patched.newBody),
      bodyChange,
      concurrency: { ...concurrency, dryRunNoWrite: true },
      readCommands,
      wouldPatch: {
        commentId,
        bodyChars: patched.newBody.length,
        bodySha: bodySha(patched.newBody),
        method: "PATCH",
        path: `/repos/{owner}/{repo}/issues/comments/${commentId}`,
      },
    };
  }
  const { owner, name } = repoParts(repo);
  const comment = await githubRequest<GitHubComment>(token, "PATCH", `/repos/${owner}/${name}/issues/comments/${commentId}`, { body: patched.newBody });
  if (isGitHubError(comment)) return commandError(command, repo, comment, { commentId });
  return {
    ok: true,
    command,
    repo,
    commentId,
    comment: compactCommentSummary(comment),
    patchSource,
    patch: patched.patchSummary,
    bodyChange: {
      ...bodyChange,
      newUpdatedAt: comment.updated_at ?? null,
    },
    concurrency,
    readCommands,
    request: {
      method: "PATCH",
      path: `/repos/${owner}/${name}/issues/comments/${commentId}`,
      bodyChars: patched.newBody.length,
    },
    rest: true,
  };
}

export async function commentUpdate(repo: string, token: string, ownerKind: "issue" | "pr", commentId: number, options: GitHubOptions, commandName?: string): Promise<GitHubCommandResult> {
  const command = commandName ?? `${ownerKind} comment update`;
  let bodyInput: { body: string; bodySource: Record<string, unknown> };
  try {
    bodyInput = readIssueCommentBody(options, command);
  } catch (error) {
    return validationError(command, repo, error instanceof Error ? error.message : String(error), { commentId });
  }
  const { body, bodySource } = bodyInput;
  const readCommands = ownerKind === "issue"
    ? {
      comments: `bun scripts/cli.ts gh issue view <issueNumber> --repo ${repo} --json comments`,
      full: `bun scripts/cli.ts gh issue view <issueNumber> --repo ${repo} --full`,
      note: "GitHub comment update targets commentId directly; use the owning issue number to read the surrounding comments.",
    }
    : {
      comments: `bun scripts/cli.ts gh issue view <prNumber> --repo ${repo} --json comments`,
      full: `bun scripts/cli.ts gh issue view <prNumber> --repo ${repo} --full`,
      note: "GitHub PR comments are issue comments; use the owning PR number through gh issue view to read the surrounding comments.",
    };
  const writePlanCommand = ownerKind === "issue" ? "issue comment update" : "pr comment update";
  if (options.dryRun) {
    return {
      ok: true,
      command,
      repo,
      dryRun: true,
      planned: true,
      commentId,
      readCommands,
      ...writeBodyPlan(writePlanCommand, repo, body, bodySource, { commentId }),
    };
  }
  const { owner, name } = repoParts(repo);
  const comment = await githubRequest<GitHubComment>(token, "PATCH", `/repos/${owner}/${name}/issues/comments/${commentId}`, { body });
  if (isGitHubError(comment)) return commandError(command, repo, comment, { commentId });
  return {
    ok: true,
    command,
    repo,
    commentId,
    comment: compactCommentSummary(comment),
    bodySource,
    bodyChars: body.length,
    bodySha: bodySha(body),
    bodyPreview: preview(body),
    source: String(bodySource.kind ?? "unknown"),
    readCommands,
    request: {
      method: "PATCH",
      path: `/repos/${owner}/${name}/issues/comments/${commentId}`,
      bodyChars: body.length,
    },
    rest: true,
  };
}

export async function commentView(repo: string, token: string, ownerKind: "issue" | "pr", commentId: number, includeBody: boolean, commandName?: string): Promise<GitHubCommandResult> {
  const command = commandName ?? `${ownerKind} comment view`;
  const comment = await getIssueComment(token, repo, commentId);
  if (isGitHubError(comment)) return commandError(command, repo, comment, { commentId });
  const body = comment.body ?? "";
  return {
    ok: true,
    command,
    repo,
    commentId,
    comment: includeBody ? commentSummary(comment) : compactCommentSummary(comment),
    bodyChars: body.length,
    bodySha: bodySha(body),
    bodyOmitted: !includeBody,
    fullBodyIncluded: includeBody,
    readCommands: {
      compact: `bun scripts/cli.ts gh ${ownerKind} comment view ${commentId} --repo ${repo}`,
      full: `bun scripts/cli.ts gh ${ownerKind} comment view ${commentId} --repo ${repo} --full`,
      raw: `bun scripts/cli.ts gh ${ownerKind} comment view ${commentId} --repo ${repo} --raw`,
    },
    request: {
      method: "GET",
      path: `/repos/{owner}/{repo}/issues/comments/${commentId}`,
    },
    rest: true,
  };
}

export async function commentDelete(repo: string, token: string, ownerKind: "issue" | "pr", commentId: number, dryRun: boolean): Promise<GitHubCommandResult> {
  const command = `${ownerKind} comment delete`;
  const { owner, name } = repoParts(repo);
  if (dryRun) {
    return {
      ok: true,
      command,
      repo,
      dryRun: true,
      planned: true,
      commentId,
      request: { method: "DELETE", path: `/repos/{owner}/{repo}/issues/comments/${commentId}` },
    };
  }
  const result = await githubRequest<null>(token, "DELETE", `/repos/${owner}/${name}/issues/comments/${commentId}`);
  if (isGitHubError(result)) return commandError(command, repo, result, { commentId });
  return {
    ok: true,
    command,
    repo,
    commentId,
    deleted: true,
    request: { method: "DELETE", path: `/repos/${owner}/${name}/issues/comments/${commentId}` },
    rest: true,
  };
}

export async function issueState(repo: string, token: string, issueNumber: number, state: "open" | "closed", dryRun: boolean, options?: GitHubOptions): Promise<GitHubCommandResult> {
  const command = state === "closed" ? "issue close" : "issue reopen";
  let lifecycleComment: { body: string; bodySource: Record<string, unknown> } | null = null;
  try {
    lifecycleComment = options === undefined ? null : readIssueLifecycleCommentBody(options, command);
  } catch (error) {
    return validationError(command, repo, error instanceof Error ? error.message : String(error), { issueNumber });
  }
  if (dryRun) {
    return {
      ok: true,
      command,
      dryRun: true,
      repo,
      issueNumber,
      disclosure: issueLifecycleDisclosure(repo, issueNumber, true),
      readCommands: issueBodyReadCommands(repo, issueNumber),
      comment: lifecycleComment === null
        ? null
        : {
          planned: true,
          ...writeBodyPlan("issue comment create", repo, lifecycleComment.body, lifecycleComment.bodySource, { issueNumber }),
        },
      wouldPatch: { state },
    };
  }
  const { owner, name } = repoParts(repo);
  let commentSummary: Record<string, unknown> | null = null;
  if (lifecycleComment !== null) {
    const comment = await githubRequest<GitHubComment>(token, "POST", `/repos/${owner}/${name}/issues/${issueNumber}/comments`, { body: lifecycleComment.body });
    if (isGitHubError(comment)) {
      return commandError(command, repo, comment, {
        issueNumber,
        phase: "comment",
        commentBodySource: lifecycleComment.bodySource,
        commentBodyChars: lifecycleComment.body.length,
        commentBodySha: bodySha(lifecycleComment.body),
      });
    }
    commentSummary = compactCommentSummary(comment);
  }
  const issue = await githubRequest<GitHubIssue>(token, "PATCH", `/repos/${owner}/${name}/issues/${issueNumber}`, { state });
  if (isGitHubError(issue)) return commandError(command, repo, issue, { issueNumber, phase: "state", comment: commentSummary });
  return {
    ok: true,
    command,
    repo,
    comment: commentSummary,
    issue: issueLifecycleSummary(issue),
    disclosure: issueLifecycleDisclosure(repo, issueNumber, false),
    readCommands: issueBodyReadCommands(repo, issueNumber),
    rest: true,
  };
}
