import { createHash, randomBytes } from "node:crypto";
import type { Readable, Writable } from "node:stream";

export type PatchHunk =
  | { kind: "add"; path: string; content: string }
  | { kind: "delete"; path: string }
  | { kind: "update"; path: string; movePath: string | null; chunks: UpdateChunk[] };

export interface UpdateChunk {
  changeContext: string | null;
  sourceStartLine: number | null;
  oldLines: string[];
  newLines: string[];
  contextLinePairs: Array<{ oldIndex: number; newIndex: number }>;
  contextLineCount: number;
  addedLineCount: number;
  deletedLineCount: number;
  isEndOfFile: boolean;
}

export interface PatchParseResult {
  patch: string;
  hunks: PatchHunk[];
  hints: string[];
}

export interface PatchUpdateResult {
  oldContent: string;
  newContent: string;
}

export interface ApplyPatchV2Outcome {
  hunk: number;
  action: PatchHunk["kind"] | "move";
  status: "applied" | "failed";
  path: string;
  targetPath?: string;
  change?: string;
  partialChanges?: string[];
  error?: {
    name?: string;
    message: string;
    details?: unknown;
  };
}

export interface ApplyPatchV2Options {
  executor: ApplyPatchV2Executor;
  stdin: Readable;
  stdout: Writable;
  stderr?: Writable;
  argv?: string[];
  timing?: ApplyPatchV2TimingOptions;
}

export interface ApplyPatchV2Executor {
  run?: (command: string[], input?: string) => Promise<ApplyPatchV2RemoteResult>;
  fs?: ApplyPatchV2FileSystem;
}

export interface ApplyPatchV2FileSystem {
  stat(path: string): Promise<ApplyPatchV2FileStat>;
  readBlock(path: string, blockIndex: number, blockBytes: number): Promise<Buffer>;
  writeFile(path: string, content: Buffer): Promise<void>;
  deleteFile(path: string): Promise<void>;
  readFiles?(paths: string[]): Promise<Map<string, ApplyPatchV2ReadFile | string>>;
  applyReplacementsBulk?(paths: Iterable<string>, plans: Map<string, ApplyPatchV2BulkReplacementWritePlan>): Promise<void>;
}

export interface ApplyPatchV2ReadFile {
  content: string;
  bytes: Buffer;
  encodingStyle: "utf8" | "utf8-bom" | "legacy-single-byte";
}

export interface ApplyPatchV2FileStat {
  bytes: number;
  sha256: string;
}

export interface ApplyPatchV2RemoteResult {
  exitCode: number;
  stdout: string;
  stderr: string;
}

export interface ApplyPatchV2TimingOptions {
  providerId?: string;
  route?: string;
  transport?: "backend-core-broker" | "frontend-websocket" | "local" | "virtual";
  thresholdMs?: number;
}

export interface ApplyPatchV2TimingSummary {
  code: "apply-patch-v2-timing";
  level: "info" | "warning";
  status: "succeeded" | "failed";
  providerId?: string;
  route?: string;
  transport?: ApplyPatchV2TimingOptions["transport"];
  durationMs: number;
  thresholdMs: number;
  slow: boolean;
  patchBytes: number;
  fileCount: number;
  hunkCount: number;
  changedCount: number;
  remoteOperationCount: number;
  remoteOperationCounts: Record<string, number>;
  remoteElapsedMs: number;
  remoteFailureCount: number;
  message: string;
  note: string;
}

export class ApplyPatchV2Error extends Error {
  constructor(message: string, public readonly details: Record<string, unknown> = {}) {
    super(message);
    this.name = "ApplyPatchV2Error";
  }
}

type PlannedFileState = { exists: true; content: string; bytes: Buffer; encodingStyle: ApplyPatchV2ReadFile["encodingStyle"] } | { exists: false; content: "" };
type PlannedOperation = { kind: "write"; path: string; content: string } | { kind: "delete"; path: string };
export type ApplyPatchV2Replacement = [start: number, oldLength: number, newLines: string[]];
type Replacement = ApplyPatchV2Replacement;
export interface ApplyPatchV2BulkReplacementWritePlan {
  path: string;
  originalBytes: number;
  originalSha256: string;
  finalBytes: number;
  finalSha256: string;
  newlineStyle: "lf" | "crlf" | "mixed" | "none";
  encodingStyle: ApplyPatchV2ReadFile["encodingStyle"];
  replacements: ApplyPatchV2Replacement[];
}
type BulkReplacementWritePlan = ApplyPatchV2BulkReplacementWritePlan;

interface ApplyPatchV2Plan {
  changed: string[];
  outcomes: ApplyPatchV2Outcome[];
}

export interface ApplyPatchV2RemoteMetrics {
  remoteOperationCount: number;
  remoteOperationCounts: Record<string, number>;
  remoteElapsedMs: number;
  remoteFailureCount: number;
}

const beginMarker = "*** Begin Patch";
const endMarker = "*** End Patch";
const environmentIdMarker = "*** Environment ID: ";
const addFileMarker = "*** Add File: ";
const deleteFileMarker = "*** Delete File: ";
const updateFileMarker = "*** Update File: ";
const moveToMarker = "*** Move to: ";
const emptyChangeContextMarker = "@@";
const changeContextMarker = "@@ ";
const eofMarker = "*** End of File";
const defaultApplyPatchV2SlowWarningMs = 10_000;

export function isApplyPatchV2HelpArgs(args: string[] = []): boolean {
  const first = args[0] ?? "";
  return first === "help" || first === "--help" || first === "-h";
}

export function applyPatchV2HelpPayload() {
  return {
    ok: true,
    command: "trans <route> apply-patch <<'PATCH'",
    summary: "Apply a standard apply_patch patch to a remote route with the local v2 line-based engine.",
    usage: [
      "trans G14:/root/hwlab/.worktree/task apply-patch <<'PATCH'",
      "trans D601:/tmp apply-patch <<'PATCH'"
    ],
    input: {
      required: true,
      firstLine: beginMarker,
      lastLine: endMarker
    },
    timing: {
      stderrPrefix: "UNIDESK_APPLY_PATCH_TIMING",
      fields: [
        "durationMs",
        "patchBytes",
        "fileCount",
        "hunkCount",
        "changedCount",
        "remoteOperationCount",
        "remoteOperationCounts",
        "remoteElapsedMs",
        "remoteFailureCount",
        "providerId",
        "route",
        "transport"
      ],
      stdoutCompatibility: "Timing is written to stderr so success stdout remains Codex apply_patch-compatible."
    },
    rules: [
      "Add File has no @@ hunk marker: put content immediately after `*** Add File: <path>` and prefix every content line with +.",
      "A blank line in Add File is a line containing only +.",
      "Update File uses @@ or @@ context markers, followed by context lines starting with one extra space prefix and changed lines starting with - or +; for a column-0 source line `const x`, write ` const x`, and for a two-space-indented source line write three spaces total. Unified-diff line-range headers are accepted with hints for MiniMax compatibility.",
      "Send the patch directly through a quoted `<<'PATCH'` heredoc so paths, quotes, `$`, and backticks reach apply-patch unchanged; do not create a temporary patch file.",
      "If `failed to find expected lines` reports stale or oversized context for a block/function replacement, re-read the exact current block and retry with a smaller Update File hunk or split hunks around unique anchors.",
      "MiniMax compatibility: stray @@ or unprefixed content inside Add File, unprefixed Update File context lines, and extra hunk/body lines after Delete File, are accepted with stderr hints.",
      "MiniMax/MXCX concatenated patch compatibility: repeated nested Begin Patch / End Patch markers are accepted with stderr hints, but the CLI still uses only the v2 engine and never auto-falls back to apply-patch-v1."
    ],
    examples: {
      addFile: [
        beginMarker,
        `${addFileMarker}path/to/new.txt`,
        "+first line",
        "+",
        "+third line after a blank line",
        endMarker
      ].join("\n"),
      updateFile: [
        beginMarker,
        `${updateFileMarker}path/to/existing.txt`,
        emptyChangeContextMarker,
        " context line",
        "-old line",
        "+new line",
        " more context",
        endMarker
      ].join("\n")
    },
    commonPitfalls: [
      "Do not put @@ after `*** Add File:`; @@ is only for Update File.",
      "Prefer canonical @@ or @@ context over unified diff headers such as `@@ -1,3 +1,4 @@`; v2 accepts those headers with a hint.",
      "If multiple printf/heredoc fragments were concatenated, keep one outer Begin/End envelope or include complete nested envelopes; v2 will hint on nested markers instead of trying the legacy helper.",
      "Do not recover from apply-patch context mismatch by switching to download/upload, remote Python/Perl/sed, or whole-file rewrites; retry with corrected v2 hunks.",
      "Do not use remote Python/Perl/sed heredocs for text patches when `trans <route> apply-patch` is available."
    ],
    note: "apply-patch reads patch text directly from stdin and uses the v2 engine by default. Use `apply-patch-v1` only for the legacy helper."
  };
}

export function parseApplyPatchV2(patchText: string): PatchParseResult {
  const patch = stripLenientHeredoc(patchText).trim();
  const lines = patch.length === 0 ? [] : patch.split(/\r?\n/u);
  const first = lines[0]?.trim();
  const last = lines[lines.length - 1]?.trim();
  if (first !== beginMarker) throw new ApplyPatchV2Error(`invalid patch: first line must be '${beginMarker}'`);
  if (last !== endMarker) throw new ApplyPatchV2Error(`invalid patch: last line must be '${endMarker}'`);

  const hunks: PatchHunk[] = [];
  const hints: string[] = [];
  let index = 1;
  const environmentLine = lines[index]?.trimStart() ?? "";
  if (environmentLine.startsWith(environmentIdMarker)) {
    const environmentId = environmentLine.slice(environmentIdMarker.length).trim();
    if (environmentId.length === 0) throw new ApplyPatchV2Error("apply_patch environment_id cannot be empty", { line: index + 1 });
    index += 1;
  }
  while (index < lines.length - 1) {
    const line = lines[index]?.trim() ?? "";
    if (line.length === 0) {
      index += 1;
      continue;
    }
    if (line === beginMarker) {
      pushUniqueHint(
        hints,
        "apply-patch hint: ignored nested MiniMax-style Begin Patch marker",
        `apply-patch hint: ignored nested MiniMax-style Begin Patch marker on line ${index + 1}; keep exactly one ${beginMarker}/${endMarker} envelope around all hunks.`,
      );
      index += 1;
      continue;
    }
    if (line === endMarker) {
      pushUniqueHint(
        hints,
        "apply-patch hint: ignored nested MiniMax-style End Patch marker",
        `apply-patch hint: ignored nested MiniMax-style End Patch marker on line ${index + 1}; keep exactly one ${beginMarker}/${endMarker} envelope around all hunks.`,
      );
      index += 1;
      continue;
    }
    if (line.startsWith(addFileMarker)) {
      const filePath = validatePatchPath(line.slice(addFileMarker.length), index + 1);
      index += 1;
      const added: string[] = [];
      while (index < lines.length - 1 && !isFileHeader(lines[index] ?? "")) {
        const addLine = lines[index] ?? "";
        if (addLine.startsWith("+")) {
          if (addLine.length > 1 && addLine.slice(1).trim().length === 0) {
            hints.push(`apply-patch hint: accepted MiniMax-style whitespace-only Add File line in ${filePath} on line ${index + 1}; prefer a line containing only + for blank lines.`);
            added.push("");
            index += 1;
            continue;
          }
          added.push(addLine.slice(1));
          index += 1;
          continue;
        }
        if (addLine.trimStart().startsWith("@@")) {
          hints.push(`apply-patch hint: accepted MiniMax-style @@ inside Add File ${filePath} on line ${index + 1}; Add File does not need @@.`);
          index += 1;
          continue;
        }
        if (addLine.trim().length === 0) {
          hints.push(`apply-patch hint: accepted a bare blank line inside Add File ${filePath} on line ${index + 1}; prefer a line containing only +.`);
          added.push("");
          index += 1;
          continue;
        }
        hints.push(`apply-patch hint: accepted unprefixed Add File content in ${filePath} on line ${index + 1}; prefix new-file content lines with +.`);
        added.push(addLine);
        index += 1;
      }
      hunks.push({ kind: "add", path: filePath, content: joinLinesWithFinalNewline(added) });
      continue;
    }
    if (line.startsWith(deleteFileMarker)) {
      const filePath = validatePatchPath(line.slice(deleteFileMarker.length), index + 1);
      index += 1;
      const extraLines: number[] = [];
      while (index < lines.length - 1 && !isFileHeader(lines[index] ?? "")) {
        if ((lines[index] ?? "").trim().length > 0) extraLines.push(index + 1);
        index += 1;
      }
      if (extraLines.length > 0) {
        hints.push(`apply-patch hint: ignored extra MiniMax-style hunk/body lines after Delete File ${filePath} on line ${extraLines[0]}; Delete File only needs the header.`);
      }
      hunks.push({ kind: "delete", path: filePath });
      continue;
    }
    if (line.startsWith(updateFileMarker)) {
      const filePath = validatePatchPath(line.slice(updateFileMarker.length), index + 1);
      index += 1;
      let movePath: string | null = null;
      if ((lines[index] ?? "").startsWith(moveToMarker)) {
        movePath = validatePatchPath((lines[index] ?? "").slice(moveToMarker.length), index + 1);
        index += 1;
      }
      const chunks: UpdateChunk[] = [];
      while (index < lines.length - 1 && !isFileHeader(lines[index] ?? "")) {
        if ((lines[index] ?? "").trim().length === 0) {
          index += 1;
          continue;
        }
        const parsed = parseUpdateChunk(lines, index, chunks.length === 0, filePath, hints);
        chunks.push(parsed.chunk);
        index = parsed.nextIndex;
      }
      if (chunks.length === 0) throw new ApplyPatchV2Error("update file hunk is empty", { line: index + 1, path: filePath });
      hunks.push({ kind: "update", path: filePath, movePath, chunks });
      continue;
    }
    throw new ApplyPatchV2Error("invalid hunk header", { line: index + 1, text: line });
  }

  return { patch, hunks, hints };
}

export function deriveUpdatedContent(filePath: string, originalContent: string, chunks: UpdateChunk[]): PatchUpdateResult {
  const originalLines = splitContentLines(originalContent);
  const replacements = computeReplacements(filePath, originalLines, chunks);
  const newLines = applyReplacements(originalLines, replacements);
  const newContent = joinLinesWithFinalNewline(newLines);
  return { oldContent: originalContent, newContent };
}

export async function runApplyPatchV2(options: ApplyPatchV2Options): Promise<number> {
  if (isApplyPatchV2HelpArgs(options.argv)) {
    options.stdout.write(`${JSON.stringify(applyPatchV2HelpPayload(), null, 2)}\n`);
    return 0;
  }
  const stderr = options.stderr ?? process.stderr;
  if ((options.argv?.length ?? 0) > 0) {
    stderr.write("ssh apply-patch uses the v2 engine and accepts no helper flags. Use apply-patch-v1 for legacy helper options.\n");
    return 2;
  }
  if ((options.stdin as Readable & { isTTY?: boolean }).isTTY === true) {
    stderr.write(`${applyPatchV2StdinHint("interactive-tty-without-stdin")}\n`);
    return 2;
  }
  const patchText = await readStreamText(options.stdin);
  if (!patchText.trim()) {
    stderr.write(`${applyPatchV2StdinHint("empty-stdin")}\n`);
    return 2;
  }
  const startedAtMs = Date.now();
  const metrics = createApplyPatchV2RemoteMetrics();
  const executor = instrumentApplyPatchV2Executor(options.executor, metrics);
  let parsed: PatchParseResult | null = null;
  let plan: ApplyPatchV2Plan | null = null;
  try {
    parsed = parseApplyPatchV2(patchText);
    plan = await applyPatchV2Hunks(executor, parsed.hunks);
    for (const hint of parsed.hints) stderr.write(`${hint}\n`);
    options.stdout.write("Success. Updated the following files:\n");
    for (const item of plan.changed) options.stdout.write(`${item}\n`);
    stderr.write(formatApplyPatchV2TimingSummary(applyPatchV2TimingSummary({
      status: "succeeded",
      patchText,
      parsed,
      plan,
      metrics,
      startedAtMs,
      options: options.timing,
    })));
    return 0;
  } catch (error) {
    const failureSummary = applyPatchV2TimingSummary({
      status: "failed",
      patchText,
      parsed,
      plan,
      metrics,
      startedAtMs,
      options: options.timing,
    });
    if (options.stderr === undefined) throw error;
    options.stderr.write(formatApplyPatchFailure(error));
    options.stderr.write(formatApplyPatchV2TimingSummary(failureSummary));
    return 1;
  }
}

function applyPatchV2StdinHint(reason: "interactive-tty-without-stdin" | "empty-stdin"): string {
  return `UNIDESK_APPLY_PATCH_INPUT ${JSON.stringify({
    code: "apply-patch-stdin-required",
    reason,
    message: "apply-patch requires patch text from stdin and will not wait on an interactive TTY.",
    try: "trans <route> apply-patch <<'PATCH'",
    envelope: [beginMarker, "*** Update File: <path>", "@@", "-old", "+new", endMarker, "PATCH"],
    temporaryFileRequired: false,
  })}`;
}

function createApplyPatchV2RemoteMetrics(): ApplyPatchV2RemoteMetrics {
  return { remoteOperationCount: 0, remoteOperationCounts: {}, remoteElapsedMs: 0, remoteFailureCount: 0 };
}

function instrumentApplyPatchV2Executor(executor: ApplyPatchV2Executor, metrics: ApplyPatchV2RemoteMetrics): ApplyPatchV2Executor {
  return {
    ...executor,
    run: executor.run === undefined ? undefined : async (command, input) => {
      const operation = applyPatchV2OperationName(command);
      const started = Date.now();
      metrics.remoteOperationCount += 1;
      metrics.remoteOperationCounts[operation] = (metrics.remoteOperationCounts[operation] ?? 0) + 1;
      try {
        const result = await executor.run!(command, input);
        metrics.remoteElapsedMs += Math.max(0, Date.now() - started);
        if (result.exitCode !== 0) metrics.remoteFailureCount += 1;
        return result;
      } catch (error) {
        metrics.remoteElapsedMs += Math.max(0, Date.now() - started);
        metrics.remoteFailureCount += 1;
        throw error;
      }
    },
    fs: executor.fs === undefined ? undefined : instrumentApplyPatchV2FileSystem(executor.fs, metrics),
  };
}

function instrumentApplyPatchV2FileSystem(fs: ApplyPatchV2FileSystem, metrics: ApplyPatchV2RemoteMetrics): ApplyPatchV2FileSystem {
  const instrumented: ApplyPatchV2FileSystem = {
    stat: (path) => recordApplyPatchV2FsOperation(metrics, "fs.stat", () => fs.stat(path)),
    readBlock: (path, blockIndex, blockBytes) => recordApplyPatchV2FsOperation(metrics, "fs.readBlock", () => fs.readBlock(path, blockIndex, blockBytes)),
    writeFile: (path, content) => recordApplyPatchV2FsOperation(metrics, "fs.writeFile", () => fs.writeFile(path, content)),
    deleteFile: (path) => recordApplyPatchV2FsOperation(metrics, "fs.deleteFile", () => fs.deleteFile(path)),
  };
  if (fs.readFiles !== undefined) {
    instrumented.readFiles = (paths) => recordApplyPatchV2FsOperation(metrics, "fs.readFiles", () => fs.readFiles!(paths));
  }
  if (fs.applyReplacementsBulk !== undefined) {
    instrumented.applyReplacementsBulk = (paths, plans) => recordApplyPatchV2FsOperation(metrics, "fs.applyReplacementsBulk", () => fs.applyReplacementsBulk!(paths, plans));
  }
  return instrumented;
}

async function recordApplyPatchV2FsOperation<T>(metrics: ApplyPatchV2RemoteMetrics, operation: string, run: () => Promise<T>): Promise<T> {
  const started = Date.now();
  metrics.remoteOperationCount += 1;
  metrics.remoteOperationCounts[operation] = (metrics.remoteOperationCounts[operation] ?? 0) + 1;
  try {
    const result = await run();
    metrics.remoteElapsedMs += Math.max(0, Date.now() - started);
    return result;
  } catch (error) {
    metrics.remoteElapsedMs += Math.max(0, Date.now() - started);
    metrics.remoteFailureCount += 1;
    throw error;
  }
}

function applyPatchV2OperationName(command: string[]): string {
  const index = command.indexOf("unidesk-apply-patch-v2");
  return index >= 0 ? command[index + 1] ?? "unknown" : command[0] ?? "unknown";
}

export function applyPatchV2TimingSummary(options: {
  status: ApplyPatchV2TimingSummary["status"];
  patchText: string;
  parsed: PatchParseResult | null;
  plan: ApplyPatchV2Plan | null;
  metrics: ApplyPatchV2RemoteMetrics;
  startedAtMs: number;
  finishedAtMs?: number;
  options?: ApplyPatchV2TimingOptions;
}): ApplyPatchV2TimingSummary {
  const finishedAtMs = options.finishedAtMs ?? Date.now();
  const durationMs = Math.max(0, Math.round(finishedAtMs - options.startedAtMs));
  const thresholdMs = options.options?.thresholdMs ?? defaultApplyPatchV2SlowWarningMs;
  const slow = durationMs > thresholdMs;
  const patchBytes = Buffer.byteLength(options.patchText, "utf8");
  const fileCount = countApplyPatchV2Files(options.parsed?.hunks ?? []);
  const hunkCount = options.parsed?.hunks.length ?? 0;
  const changedCount = options.plan?.changed.length ?? applyPatchV2ChangedCountFromOutcomes(options.plan?.outcomes ?? []);
  const durationSeconds = Number((durationMs / 1000).toFixed(3));
  const thresholdSeconds = Number((thresholdMs / 1000).toFixed(3));
  return {
    code: "apply-patch-v2-timing",
    level: slow ? "warning" : "info",
    status: options.status,
    ...(options.options?.providerId === undefined ? {} : { providerId: options.options.providerId }),
    ...(options.options?.route === undefined ? {} : { route: options.options.route }),
    ...(options.options?.transport === undefined ? {} : { transport: options.options.transport }),
    durationMs,
    thresholdMs,
    slow,
    patchBytes,
    fileCount,
    hunkCount,
    changedCount,
    remoteOperationCount: options.metrics.remoteOperationCount,
    remoteOperationCounts: options.metrics.remoteOperationCounts,
    remoteElapsedMs: Math.max(0, Math.round(options.metrics.remoteElapsedMs)),
    remoteFailureCount: options.metrics.remoteFailureCount,
    message: slow
      ? `apply-patch completed in ${durationSeconds}s, above the ${thresholdSeconds}s warning threshold; inspect remoteOperationCounts and remoteElapsedMs before repeating high-frequency patch work.`
      : `apply-patch completed in ${durationSeconds}s.`,
    note: "Timing summary is written to stderr so stdout remains Codex apply_patch-compatible.",
  };
}

export function formatApplyPatchV2TimingSummary(summary: ApplyPatchV2TimingSummary): string {
  return `UNIDESK_APPLY_PATCH_TIMING ${JSON.stringify(summary)}\n`;
}

function countApplyPatchV2Files(hunks: PatchHunk[]): number {
  const files = new Set<string>();
  for (const hunk of hunks) {
    files.add(hunk.path);
    if (hunk.kind === "update" && hunk.movePath !== null) files.add(hunk.movePath);
  }
  return files.size;
}

function applyPatchV2ChangedCountFromOutcomes(outcomes: ApplyPatchV2Outcome[]): number {
  return outcomes.filter((outcome) => outcome.status === "applied").length;
}

async function applyPatchV2Hunks(executor: ApplyPatchV2Executor, hunks: PatchHunk[]): Promise<ApplyPatchV2Plan> {
  if (hunks.length === 0) throw new ApplyPatchV2Error("No files were modified.");
  if (shouldUseBulkUpdatePath(executor, hunks)) {
    try {
      return await applyPatchV2UpdateHunksBulk(executor, hunks as Array<Extract<PatchHunk, { kind: "update" }>>);
    } catch (error) {
      if (!isBulkReadFailure(error)) throw error;
    }
  }
  return applyPatchV2HunksSequential(executor, hunks);
}

async function applyPatchV2HunksSequential(executor: ApplyPatchV2Executor, hunks: PatchHunk[]): Promise<ApplyPatchV2Plan> {
  const states = new Map<string, PlannedFileState>();
  const changed: string[] = [];
  const outcomes: ApplyPatchV2Outcome[] = [];

  async function readPlannedText(filePath: string): Promise<string> {
    const state = states.get(filePath);
    if (state !== undefined) {
      if (!state.exists) throw new ApplyPatchV2Error("cannot update a file deleted earlier in this patch", { path: filePath });
      return state.content;
    }
    const content = await readRemoteText(executor, filePath);
    states.set(filePath, { exists: true, content });
    return content;
  }

  async function applyWrite(filePath: string, content: string): Promise<void> {
    await executePlannedOperation(executor, { kind: "write", path: filePath, content });
    states.set(filePath, { exists: true, content });
  }

  async function applyDelete(filePath: string): Promise<void> {
    const state = states.get(filePath);
    if (state === undefined) {
      await ensureRemoteFileExists(executor, filePath);
    } else if (!state.exists) {
      throw new ApplyPatchV2Error("cannot delete a file deleted earlier in this patch", { path: filePath });
    }
    await executePlannedOperation(executor, { kind: "delete", path: filePath });
    states.set(filePath, { exists: false, content: "" });
  }

  for (let index = 0; index < hunks.length; index += 1) {
    const hunk = hunks[index] as PatchHunk;
    const changedBefore = changed.length;
    try {
      if (hunk.kind === "add") {
        await applyWrite(hunk.path, hunk.content);
        pushChanged(changed, `A ${hunk.path}`);
        outcomes.push({ ...outcomeBase(hunk, index), status: "applied", change: `A ${hunk.path}` });
        continue;
      }
      if (hunk.kind === "delete") {
        await applyDelete(hunk.path);
        pushChanged(changed, `D ${hunk.path}`);
        outcomes.push({ ...outcomeBase(hunk, index), status: "applied", change: `D ${hunk.path}` });
        continue;
      }
      const originalContent = await readPlannedText(hunk.path);
      const update = deriveUpdatedContent(hunk.path, originalContent, hunk.chunks);
      if (hunk.movePath !== null && hunk.movePath !== hunk.path) {
        await applyWrite(hunk.movePath, update.newContent);
        pushChanged(changed, `M ${hunk.movePath}`);
        await applyDelete(hunk.path);
        outcomes.push({ ...outcomeBase(hunk, index), action: "move", status: "applied", change: `M ${hunk.movePath}` });
        continue;
      }
      await applyWrite(hunk.path, update.newContent);
      pushChanged(changed, `M ${hunk.path}`);
      outcomes.push({ ...outcomeBase(hunk, index), status: "applied", change: `M ${hunk.path}` });
    } catch (error) {
      const partialChanges = changed.slice(changedBefore);
      outcomes.push({
        ...outcomeBase(hunk, index),
        status: "failed",
        ...(partialChanges.length > 0 ? { partialChanges } : {}),
        error: errorSummary(error),
      });
      throw new ApplyPatchV2Error(error instanceof Error ? error.message : String(error), {
        partialChanges: changed,
        outcomes,
        failed: outcomes.find((item) => item.status === "failed") ?? null,
        cause: error instanceof ApplyPatchV2Error ? error.details : undefined,
      });
    }
  }

  return { changed, outcomes };
}

function isBulkReadFailure(error: unknown): boolean {
  if (!(error instanceof ApplyPatchV2Error)) return false;
  if (error.message.includes("bulk read")) return true;
  return JSON.stringify(error.details).includes("read-bulk-b64");
}

function shouldUseBulkUpdatePath(executor: ApplyPatchV2Executor, hunks: PatchHunk[]): boolean {
  const isFsBulkPath = executor.fs !== undefined;
  if (executor.fs !== undefined) {
    if (executor.fs.readFiles === undefined || executor.fs.applyReplacementsBulk === undefined) return false;
  } else if (!executor.run) {
    return false;
  }
  const paths = new Set<string>();
  for (const hunk of hunks) {
    if (hunk.kind !== "update" || hunk.movePath !== null) return false;
    if (paths.has(hunk.path)) return false;
    paths.add(hunk.path);
  }
  return isFsBulkPath ? paths.size >= 1 : paths.size >= 2;
}

async function applyPatchV2UpdateHunksBulk(executor: ApplyPatchV2Executor, hunks: Array<Extract<PatchHunk, { kind: "update" }>>): Promise<ApplyPatchV2Plan> {
  const paths = uniqueStrings(hunks.map((hunk) => hunk.path));
  const originalFiles = await readRemoteTextsBulk(executor, paths);
  const states = new Map<string, PlannedFileState>();
  for (const [filePath, file] of originalFiles) states.set(filePath, { exists: true, ...file });

  const changed: string[] = [];
  const outcomes: ApplyPatchV2Outcome[] = [];
  const pendingWrites = new Set<string>();
  const replacementPlans = new Map<string, BulkReplacementWritePlan>();

  for (let index = 0; index < hunks.length; index += 1) {
    const hunk = hunks[index] as Extract<PatchHunk, { kind: "update" }>;
    const changedBefore = changed.length;
    try {
      const state = states.get(hunk.path);
      if (state === undefined || !state.exists) throw new ApplyPatchV2Error("cannot update a missing file in bulk patch", { path: hunk.path });
      const originalLines = splitContentLines(state.content);
      const textFormat = detectApplyPatchTextFormat(state.content, state.encodingStyle);
      const replacements = preserveReplacementNewlines(computeReplacements(hunk.path, originalLines, hunk.chunks), textFormat.newlineStyle);
      const newContent = joinLinesWithFinalNewline(applyReplacements(originalLines, replacements));
      const originalBuffer = state.bytes;
      const finalBuffer = encodeApplyPatchText(newContent, state.encodingStyle, hunk.path);
      states.set(hunk.path, { exists: true, content: newContent, bytes: finalBuffer, encodingStyle: state.encodingStyle });
      replacementPlans.set(hunk.path, {
        path: hunk.path,
        originalBytes: originalBuffer.length,
        originalSha256: sha256Hex(originalBuffer),
        finalBytes: finalBuffer.length,
        finalSha256: sha256Hex(finalBuffer),
        newlineStyle: textFormat.newlineStyle,
        encodingStyle: textFormat.encodingStyle,
        replacements,
      });
      pendingWrites.add(hunk.path);
      pushChanged(changed, `M ${hunk.path}`);
      outcomes.push({ ...outcomeBase(hunk, index), status: "applied", change: `M ${hunk.path}` });
    } catch (error) {
      const partialChanges = changed.slice(changedBefore);
      if (pendingWrites.size > 0) await writeRemoteReplacementsBulk(executor, pendingWrites, replacementPlans);
      outcomes.push({
        ...outcomeBase(hunk, index),
        status: "failed",
        ...(partialChanges.length > 0 ? { partialChanges } : {}),
        error: errorSummary(error),
      });
      throw new ApplyPatchV2Error(error instanceof Error ? error.message : String(error), {
        partialChanges: changed,
        outcomes,
        failed: outcomes.find((item) => item.status === "failed") ?? null,
        cause: error instanceof ApplyPatchV2Error ? error.details : undefined,
      });
    }
  }

  if (pendingWrites.size > 0) await writeRemoteReplacementsBulk(executor, pendingWrites, replacementPlans);
  return { changed, outcomes };
}

function uniqueStrings(values: string[]): string[] {
  return Array.from(new Set(values));
}

function pushChanged(changed: string[], item: string): void {
  if (!changed.includes(item)) changed.push(item);
}

function outcomeBase(hunk: PatchHunk, index: number): Omit<ApplyPatchV2Outcome, "status"> {
  if (hunk.kind === "update" && hunk.movePath !== null && hunk.movePath !== hunk.path) {
    return { hunk: index + 1, action: "move", path: hunk.path, targetPath: hunk.movePath };
  }
  return { hunk: index + 1, action: hunk.kind, path: hunk.path };
}

function errorSummary(error: unknown): ApplyPatchV2Outcome["error"] {
  if (error instanceof ApplyPatchV2Error) {
    return { name: error.name, message: error.message, details: error.details };
  }
  if (error instanceof Error) {
    return { name: error.name, message: error.message };
  }
  return { message: String(error) };
}

function formatApplyPatchFailure(error: unknown): string {
  const message = error instanceof Error ? error.message : String(error);
  const details = error instanceof ApplyPatchV2Error ? error.details : {};
  const outcomes = Array.isArray(details.outcomes) ? details.outcomes as ApplyPatchV2Outcome[] : [];
  const lines = [`${message.trimEnd()}`];
  appendParserFailureHint(lines, message, details);
  appendRemoteOperationFailureDetails(lines, details);
  appendExpectedLinesFailureHint(lines, details);
  if (outcomes.length > 1 || outcomes.some((item) => item.status === "applied")) {
    lines.push("Patch status:");
    appendOutcomeSection(lines, "Applied before failure:", outcomes.filter((item) => item.status === "applied"));
    appendOutcomeSection(lines, "Failed:", outcomes.filter((item) => item.status === "failed"));
  }
  return `${lines.join("\n")}\n`;
}

function appendParserFailureHint(lines: string[], message: string, details: Record<string, unknown>): void {
  const text = typeof details.text === "string" ? details.text : "";
  const parserLine = typeof details.line === "number" ? `line ${details.line}` : "";
  if (message === "invalid hunk header") {
    const shown = text.length === 0 ? "" : ` (${JSON.stringify(text)})`;
    lines.push(`Hint: unexpected patch body ${parserLine}${shown}. For concatenated MiniMax/MXCX fragments, keep exactly one outer *** Begin Patch / *** End Patch envelope or make each nested fragment a complete Begin/End pair.`);
    lines.push("Hint: apply-patch remains the v2 engine only; fix the patch text or run `trans <route> apply-patch --help`, do not retry by switching to apply-patch-v1.");
    return;
  }
  if (message.startsWith("invalid patch:")) {
    lines.push("Hint: apply-patch expects a single stdin patch whose first non-wrapper line is *** Begin Patch and last line is *** End Patch. Send it directly with `trans <route> apply-patch <<'PATCH'`; quoted heredocs preserve required leading spaces without a temporary patch file.");
    lines.push("Hint: apply-patch remains the v2 engine only; v1 is never auto-selected for malformed MiniMax/MXCX patch envelopes.");
  }
}

function appendRemoteOperationFailureDetails(lines: string[], details: Record<string, unknown>): void {
  const remote = remoteOperationFailureRecord(details);
  if (remote === null) return;
  const summaryFields = [
    ["operation", stringField(remote, "operation")],
    ["route", stringField(remote, "route")],
    ["file", stringField(remote, "filePath") ?? stringField(remote, "path")],
    ["targetCount", numberOrStringField(remote, "targetCount")],
    ["inputBytes", numberOrStringField(remote, "inputBytes")],
    ["expectedBytes", numberOrStringField(remote, "expectedBytes")],
    ["sha256", stringField(remote, "expectedSha256") ?? stringField(remote, "sha256")],
    ["elapsedMs", numberOrStringField(remote, "remoteElapsedMs")],
    ["landed", booleanOrStringField(remote, "landed")],
    ["exitCode", numberOrStringField(remote, "exitCode")],
  ].filter(([, value]) => value !== undefined && value !== null && value !== "");
  if (summaryFields.length > 0) {
    lines.push(`Remote operation: ${summaryFields.map(([key, value]) => `${key}=${value}`).join(" ")}`);
  }
  const integrityDiagnostics = Array.isArray(remote.integrityDiagnostics) ? remote.integrityDiagnostics : [];
  for (const item of integrityDiagnostics) {
    const diagnostic = recordValue(item);
    if (diagnostic === null) continue;
    const fields = [
      ["adapter", stringField(diagnostic, "adapter")],
      ["stage", stringField(diagnostic, "stage")],
      ["file", stringField(diagnostic, "path")],
      ["expectedOriginalBytes", numberOrStringField(diagnostic, "expectedOriginalBytes")],
      ["currentBytes", numberOrStringField(diagnostic, "currentBytes")],
      ["expectedOriginalSha256", stringField(diagnostic, "expectedOriginalSha256")],
      ["currentSha256", stringField(diagnostic, "currentSha256")],
      ["expectedFinalBytes", numberOrStringField(diagnostic, "expectedFinalBytes")],
      ["expectedFinalSha256", stringField(diagnostic, "expectedFinalSha256")],
      ["expectedNewline", stringField(diagnostic, "expectedNewline")],
      ["currentNewline", stringField(diagnostic, "currentNewline")],
      ["expectedEncoding", stringField(diagnostic, "expectedEncoding")],
      ["currentEncoding", stringField(diagnostic, "currentEncoding")],
    ].filter(([, value]) => value !== undefined && value !== null && value !== "");
    lines.push(`Integrity: ${fields.map(([key, value]) => `${key}=${value}`).join(" ")}`);
  }
  const stderrTail = stringField(remote, "stderrTail") ?? stringField(remote, "stderr");
  if (stderrTail !== undefined && stderrTail.trim().length > 0) {
    lines.push("Remote stderr tail:");
    appendQuotedBlock(lines, stderrTail.trim(), 8, 1200);
  }
  const stdoutTail = stringField(remote, "stdoutTail") ?? stringField(remote, "stdout");
  if (stdoutTail !== undefined && stdoutTail.trim().length > 0) {
    lines.push("Remote stdout tail:");
    appendQuotedBlock(lines, stdoutTail.trim(), 6, 900);
  }
}

function remoteOperationFailureRecord(details: Record<string, unknown>): Record<string, unknown> | null {
  if (typeof details.operation === "string") return details;
  const cause = recordValue(details.cause);
  if (cause !== null) {
    const nested = remoteOperationFailureRecord(cause);
    if (nested !== null) return nested;
  }
  const failed = recordValue(details.failed);
  const failedError = recordValue(failed?.error);
  const failedDetails = recordValue(failedError?.details);
  if (failedDetails !== null) {
    const nested = remoteOperationFailureRecord(failedDetails);
    if (nested !== null) return nested;
  }
  return null;
}

function stringField(record: Record<string, unknown>, key: string): string | undefined {
  const value = record[key];
  return typeof value === "string" ? value : undefined;
}

function numberOrStringField(record: Record<string, unknown>, key: string): string | undefined {
  const value = record[key];
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
  if (typeof value === "string" && value.length > 0) return value;
  return undefined;
}

function booleanOrStringField(record: Record<string, unknown>, key: string): string | undefined {
  const value = record[key];
  if (typeof value === "boolean") return String(value);
  if (typeof value === "string" && value.length > 0) return value;
  return undefined;
}

function appendExpectedLinesFailureHint(lines: string[], details: Record<string, unknown>): void {
  const cause = recordValue(details.cause) ?? details;
  const expected = typeof cause.expected === "string" ? cause.expected : "";
  if (expected.length === 0) return;
  const path = typeof cause.path === "string" ? cause.path : "target file";
  const chunk = typeof cause.chunk === "number" ? ` hunk ${cause.chunk}` : "";
  lines.push(`Expected lines for ${path}${chunk}:`);
  appendQuotedBlock(lines, expected, 20, 1600);
  appendExpectedLineDiagnostics(lines, cause);
  lines.push("Hint: re-read the target file around this hunk. In Update File hunks, every context line needs a leading space prefix; for a column-0 source line like `]);`, write ` ]);`.");
  lines.push("Hint: this is still handled by apply-patch v2. Correct the hunk context or prefixes; do not switch the same patch to apply-patch-v1.");
}

function appendExpectedLineDiagnostics(lines: string[], cause: Record<string, unknown>): void {
  const diagnostics = recordValue(cause.diagnostics);
  if (diagnostics === null) return;
  const candidates = Array.isArray(diagnostics.firstExpectedLineCandidates) ? diagnostics.firstExpectedLineCandidates.filter((item): item is number => typeof item === "number") : [];
  const firstExpectedLine = typeof diagnostics.firstExpectedLine === "string" ? diagnostics.firstExpectedLine : "";
  if (firstExpectedLine.length > 0 && candidates.length > 0) {
    const suffix = Boolean(diagnostics.firstExpectedLineCandidatesTruncated) ? "+" : "";
    lines.push(`First expected line appears near target line(s): ${candidates.join(", ")}${suffix}`);
  }
  if (typeof diagnostics.bestPrefixMatchedLines === "number" && diagnostics.bestPrefixMatchedLines > 0 && typeof diagnostics.bestPrefixStartLine === "number") {
    lines.push(`Best partial context match: ${diagnostics.bestPrefixMatchedLines} expected line(s) matched starting near line ${diagnostics.bestPrefixStartLine}.`);
  }
  if (diagnostics.likelyMissingAddedPrefixes === true) {
    lines.push("Hint: this hunk looks like a large insertion whose new lines were written as context. Prefix every inserted line with +, keep only a few real existing context lines before/after the insertion, and regenerate the patch instead of editing it with sed.");
  }
  if (diagnostics.likelyStaleOrOversizedContext === true) {
    lines.push("Hint: this looks like stale or oversized context for a block/function replacement. Do not switch to download/upload, remote Python/Perl/sed, or a whole-file rewrite. Re-read the exact current block and retry apply-patch with a smaller hunk, or split the edit into hunks around unique anchors.");
  }
}

function recordValue(value: unknown): Record<string, unknown> | null {
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
}

function appendQuotedBlock(lines: string[], text: string, maxLines: number, maxChars: number): void {
  const sourceLines = text.split("\n");
  let usedChars = 0;
  let omitted = 0;
  for (const [index, line] of sourceLines.entries()) {
    const quoted = JSON.stringify(line);
    if (index >= maxLines || usedChars + quoted.length > maxChars) {
      omitted = sourceLines.length - index;
      break;
    }
    lines.push(`  ${quoted}`);
    usedChars += quoted.length;
  }
  if (omitted > 0) lines.push(`  ... ${omitted} more expected line(s) omitted`);
}

function appendOutcomeSection(lines: string[], title: string, outcomes: ApplyPatchV2Outcome[]): void {
  if (outcomes.length === 0) return;
  lines.push(title);
  for (const outcome of outcomes) {
    lines.push(`  ${formatOutcome(outcome)}`);
  }
}

function formatOutcome(outcome: ApplyPatchV2Outcome): string {
  const target = outcome.targetPath === undefined ? outcome.path : `${outcome.path} -> ${outcome.targetPath}`;
  const base = `hunk ${outcome.hunk} ${outcome.action} ${target}`;
  if (outcome.status === "applied") return outcome.change === undefined ? base : `${outcome.change} (${base})`;
  const error = outcome.error?.message ?? "failed";
  const partial = outcome.partialChanges === undefined || outcome.partialChanges.length === 0
    ? ""
    : `; partial changes: ${outcome.partialChanges.join(", ")}`;
  return `${base}: ${error}${partial}`;
}

async function ensureRemoteFileExists(executor: ApplyPatchV2Executor, target: string): Promise<void> {
  if (executor.fs) {
    await executor.fs.stat(target);
    return;
  }
  await checkedRemoteV2(executor, "stat", [target]);
}

async function executePlannedOperation(executor: ApplyPatchV2Executor, operation: PlannedOperation): Promise<void> {
  if (operation.kind === "write") {
    await writeRemoteText(executor, operation.path, operation.content);
    return;
  }
  if (executor.fs) {
    await executor.fs.deleteFile(operation.path);
    return;
  }
  await checkedRemoteV2(executor, "delete", [operation.path]);
}

const readBlockBytes = 45_000;
const writeB64ArgvLimit = 48_000;
const writeB64ChunkChars = 12_000;
const remoteReadBlockMarker = "UNIDESK_APPLY_PATCH_V2_BLOCK";
const remoteBulkReadMarker = "UNIDESK_APPLY_PATCH_V2_BULK_READ";

async function readRemoteText(executor: ApplyPatchV2Executor, target: string): Promise<string> {
  if (executor.fs) {
    const stat = await executor.fs.stat(target);
    if (!Number.isSafeInteger(stat.bytes) || stat.bytes < 0 || !/^[0-9a-f]{64}$/u.test(stat.sha256)) {
      throw new ApplyPatchV2Error("remote apply-patch v2 fs stat returned invalid metadata", { path: target, stat });
    }
    const chunks: Buffer[] = [];
    let actualBytes = 0;
    for (let blockIndex = 0; actualBytes < stat.bytes; blockIndex += 1) {
      const chunk = await executor.fs.readBlock(target, blockIndex, readBlockBytes);
      if (chunk.length === 0) {
        throw new ApplyPatchV2Error("remote apply-patch v2 fs read returned an empty block before EOF", {
          path: target,
          blockIndex,
          expectedBytes: stat.bytes,
          actualBytes,
        });
      }
      chunks.push(chunk);
      actualBytes += chunk.length;
    }
    const contentBuffer = Buffer.concat(chunks);
    if (contentBuffer.length !== stat.bytes) {
      throw new ApplyPatchV2Error("remote apply-patch v2 fs read byte count mismatch", {
        path: target,
        expectedBytes: stat.bytes,
        actualBytes: contentBuffer.length,
      });
    }
    const actualSha256 = sha256Hex(contentBuffer);
    if (actualSha256 !== stat.sha256) {
      throw new ApplyPatchV2Error("remote apply-patch v2 fs read sha256 mismatch", {
        path: target,
        expectedSha256: stat.sha256,
        actualSha256,
      });
    }
    return contentBuffer.toString("utf8");
  }

  const stat = await checkedRemoteV2(executor, "stat", [target]);
  const [bytesText, expectedSha256] = stat.stdout.trim().split(/\s+/u);
  const expectedBytes = Number(bytesText);
  if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0 || !/^[0-9a-f]{64}$/u.test(expectedSha256 ?? "")) {
    throw new ApplyPatchV2Error("remote apply-patch v2 stat returned invalid metadata", { path: target, stdout: stat.stdout.slice(0, 500), stderr: stat.stderr.slice(-500) });
  }

  const chunks: Buffer[] = [];
  let actualBytes = 0;
  for (let blockIndex = 0; actualBytes < expectedBytes; blockIndex += 1) {
    const read = await checkedRemoteV2(executor, "read-b64-block", [target, String(blockIndex), String(readBlockBytes)]);
    const expectedChunkBytes = Math.min(readBlockBytes, expectedBytes - actualBytes);
    const chunk = decodeRemoteReadBlock(read.stdout, target, blockIndex, expectedChunkBytes);
    if (chunk.length === 0) {
      throw new ApplyPatchV2Error("remote apply-patch v2 read returned an empty block before EOF", {
        path: target,
        blockIndex,
        expectedBytes,
        actualBytes,
      });
    }
    chunks.push(chunk);
    actualBytes += chunk.length;
  }

  const contentBuffer = Buffer.concat(chunks);
  if (contentBuffer.length !== expectedBytes) {
    throw new ApplyPatchV2Error("remote apply-patch v2 read byte count mismatch", {
      path: target,
      expectedBytes,
      actualBytes: contentBuffer.length,
    });
  }
  const actualSha256 = sha256Hex(contentBuffer);
  if (actualSha256 !== expectedSha256) {
    throw new ApplyPatchV2Error("remote apply-patch v2 read sha256 mismatch", {
      path: target,
      expectedSha256,
      actualSha256,
    });
  }
  return contentBuffer.toString("utf8");
}

async function readRemoteTextsBulk(executor: ApplyPatchV2Executor, targets: string[]): Promise<Map<string, ApplyPatchV2ReadFile>> {
  if (targets.length === 0) return new Map();
  if (executor.fs?.readFiles !== undefined) {
    try {
      const files = await executor.fs.readFiles(targets);
      for (const target of targets) {
        if (!files.has(target)) throw new ApplyPatchV2Error("remote apply-patch v2 fs bulk read omitted target", { target });
      }
      return new Map(Array.from(files, ([path, file]) => [path, normalizeApplyPatchV2ReadFile(file)]));
    } catch (error) {
      if (!isBulkReadFailure(error)) throw error;
    }
  }
  if (executor.fs || targets.length === 1) {
    const files = new Map<string, ApplyPatchV2ReadFile>();
    for (const target of targets) files.set(target, decodeApplyPatchV2ReadFile(Buffer.from(await readRemoteText(executor, target), "utf8")));
    return files;
  }
  const result = await checkedRemoteV2(executor, "read-bulk-b64", targets);
  return decodeApplyPatchV2BulkRead(result.stdout, targets);
}

function decodeRemoteReadBlock(stdout: string, target: string, blockIndex: number, expectedChunkBytes: number): Buffer {
  const lines = stdout.split(/\r?\n/u);
  const markerIndex = lines.findIndex((line) => line.startsWith(`${remoteReadBlockMarker} `));
  let encoded = "";
  let declaredBytes: number | null = null;
  let declaredSha256: string | null = null;
  if (markerIndex >= 0) {
    const [, bytesText, sha256] = lines[markerIndex].split(/\s+/u);
    declaredBytes = Number(bytesText);
    declaredSha256 = sha256 ?? null;
    if (!Number.isSafeInteger(declaredBytes) || declaredBytes < 0 || !/^[0-9a-f]{64}$/u.test(declaredSha256 ?? "")) {
      throw new ApplyPatchV2Error("remote apply-patch v2 read block returned invalid metadata", {
        path: target,
        blockIndex,
        marker: lines[markerIndex].slice(0, 200),
      });
    }
    encoded = (lines[markerIndex + 1] ?? "").replace(/\s+/gu, "");
  } else {
    encoded = stdout.replace(/\s+/gu, "");
  }
  const decoded = encoded.length === 0 ? Buffer.alloc(0) : Buffer.from(encoded, "base64");
  if (declaredBytes !== null) {
    if (decoded.length !== declaredBytes) {
      throw new ApplyPatchV2Error("remote apply-patch v2 read block byte count mismatch", {
        path: target,
        blockIndex,
        expectedBytes: declaredBytes,
        actualBytes: decoded.length,
      });
    }
    const actualSha256 = sha256Hex(decoded);
    if (actualSha256 !== declaredSha256) {
      throw new ApplyPatchV2Error("remote apply-patch v2 read block sha256 mismatch", {
        path: target,
        blockIndex,
        expectedSha256: declaredSha256,
        actualSha256,
      });
    }
  }
  return decoded.length > expectedChunkBytes ? decoded.subarray(0, expectedChunkBytes) : decoded;
}

export function decodeApplyPatchV2BulkRead(stdout: string, targets: string[]): Map<string, ApplyPatchV2ReadFile> {
  const lines = stdout.split(/\r?\n/u);
  const markerIndex = lines.findIndex((line) => line.startsWith(`${remoteBulkReadMarker} `));
  if (markerIndex < 0) {
    throw new ApplyPatchV2Error("remote apply-patch v2 bulk read returned no marker", { expectedTargets: targets.length, stdout: stdout.slice(0, 500) });
  }
  const declaredCount = Number(lines[markerIndex].split(/\s+/u)[1] ?? "-1");
  if (!Number.isSafeInteger(declaredCount) || declaredCount !== targets.length) {
    throw new ApplyPatchV2Error("remote apply-patch v2 bulk read target count mismatch", { expectedTargets: targets.length, declaredCount });
  }
  const records = lines.slice(markerIndex + 1).filter((line) => line.trim().length > 0);
  if (records.length !== declaredCount) {
    throw new ApplyPatchV2Error("remote apply-patch v2 bulk read record count mismatch", { expectedTargets: targets.length, records: records.length });
  }
  const files = new Map<string, ApplyPatchV2ReadFile>();
  for (const record of records) {
    const [pathB64, bytesText, sha256, contentB64] = record.split(/\s+/u);
    const target = Buffer.from(pathB64, "base64").toString("utf8");
    const expectedBytes = Number(bytesText);
    const contentBuffer = Buffer.from(contentB64 ?? "", "base64");
    if (!targets.includes(target)) {
      throw new ApplyPatchV2Error("remote apply-patch v2 bulk read returned unexpected target", { target, expectedTargets: targets });
    }
    if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0 || !/^[0-9a-f]{64}$/u.test(sha256)) {
      throw new ApplyPatchV2Error("remote apply-patch v2 bulk read returned invalid metadata", { target, expectedBytes: bytesText, sha256 });
    }
    if (contentBuffer.length !== expectedBytes) {
      throw new ApplyPatchV2Error(`remote apply-patch v2 bulk read byte count mismatch for ${target}: expected=${expectedBytes} actual=${contentBuffer.length}`, { target, expectedBytes, actualBytes: contentBuffer.length });
    }
    const actualSha256 = sha256Hex(contentBuffer);
    if (actualSha256 !== sha256) {
      throw new ApplyPatchV2Error(`remote apply-patch v2 bulk read sha256 mismatch for ${target}`, { target, expectedSha256: sha256, actualSha256 });
    }
    files.set(target, decodeApplyPatchV2ReadFile(contentBuffer));
  }
  for (const target of targets) {
    if (!files.has(target)) throw new ApplyPatchV2Error("remote apply-patch v2 bulk read omitted target", { target });
  }
  return files;
}

async function writeRemoteText(executor: ApplyPatchV2Executor, target: string, content: string): Promise<void> {
  const contentBuffer = Buffer.from(content, "utf8");
  if (executor.fs) {
    await executor.fs.writeFile(target, contentBuffer);
    return;
  }
  const encoded = contentBuffer.toString("base64");
  const expectedBytes = String(contentBuffer.length);
  const expectedSha256 = sha256Hex(contentBuffer);
  if (encoded.length <= writeB64ArgvLimit) {
    await checkedRemoteV2(executor, "write-b64-argv", [target, expectedBytes, expectedSha256, ...chunkString(encoded, writeB64ChunkChars)]);
    return;
  }
  try {
    await checkedRemoteV2(executor, "write-b64-stdin", [target, expectedBytes, expectedSha256], encoded);
    return;
  } catch {
    // Some SSH/websocket bridges cap stdin payloads without a stable public
    // contract. The stdin path is still the fast path; fall back to small argv
    // chunks only after the remote sha/byte guard proves no partial write moved.
  }
  const token = `${process.pid}-${Date.now()}-${randomBytes(4).toString("hex")}-${expectedSha256.slice(0, 12)}`;
  await checkedRemoteV2(executor, "write-b64-begin", [target, token]);
  for (const chunk of chunkString(encoded, writeB64ChunkChars)) {
    await checkedRemoteV2(executor, "write-b64-append", [target, token, chunk]);
  }
  await checkedRemoteV2(executor, "write-b64-commit", [target, token, expectedBytes, expectedSha256]);
}

async function writeRemoteReplacementsBulk(executor: ApplyPatchV2Executor, paths: Iterable<string>, plans: Map<string, BulkReplacementWritePlan>): Promise<void> {
  const { targets, payload } = formatApplyPatchV2BulkReplacementPayload(paths, plans);
  if (targets.length === 0) return;
  if (executor.fs?.applyReplacementsBulk !== undefined) {
    try {
      await executor.fs.applyReplacementsBulk(targets, plans);
    } catch (error) {
      throw await enrichBulkReplacementIntegrityFailure(error, executor.fs, targets, plans);
    }
    return;
  }
  await checkedRemoteV2(executor, "apply-replacements-bulk-stdin", [String(targets.length)], payload);
}

async function enrichBulkReplacementIntegrityFailure(error: unknown, fs: ApplyPatchV2FileSystem, targets: string[], plans: Map<string, BulkReplacementWritePlan>): Promise<ApplyPatchV2Error> {
  const message = error instanceof Error ? error.message : String(error);
  const details = error instanceof ApplyPatchV2Error ? error.details : {};
  const serialized = `${message}\n${JSON.stringify(details)}`;
  if (fs.readFiles === undefined || !serialized.includes("integrity mismatch")) return error instanceof ApplyPatchV2Error ? error : new ApplyPatchV2Error(message);
  let currentFiles: Map<string, ApplyPatchV2ReadFile | string>;
  try {
    currentFiles = await fs.readFiles(targets);
  } catch (readError) {
    return new ApplyPatchV2Error(message, {
      ...details,
      integrityDiagnosticError: readError instanceof Error ? readError.message : String(readError),
    });
  }
  const stage = serialized.includes("original integrity mismatch") ? "original" : serialized.includes("final integrity mismatch") ? "final" : "unknown";
  const route = typeof details.route === "string" ? details.route : "";
  const adapter = route.includes(":win") ? "windows" : "posix";
  const integrityDiagnostics = targets.flatMap((target) => {
    const plan = plans.get(target);
    const currentValue = currentFiles.get(target);
    if (plan === undefined || currentValue === undefined) return [];
    const current = normalizeApplyPatchV2ReadFile(currentValue);
    const currentBuffer = current.bytes;
    const currentFormat = detectApplyPatchTextFormat(current.content, current.encodingStyle);
    return [{
      adapter,
      stage,
      path: target,
      expectedOriginalBytes: plan.originalBytes,
      expectedOriginalSha256: plan.originalSha256,
      expectedFinalBytes: plan.finalBytes,
      expectedFinalSha256: plan.finalSha256,
      currentBytes: currentBuffer.length,
      currentSha256: sha256Hex(currentBuffer),
      expectedNewline: plan.newlineStyle,
      currentNewline: currentFormat.newlineStyle,
      expectedEncoding: plan.encodingStyle,
      currentEncoding: currentFormat.encodingStyle,
    }];
  });
  return new ApplyPatchV2Error(message, { ...details, integrityDiagnostics });
}

export function formatApplyPatchV2BulkReplacementPayload(paths: Iterable<string>, plans: Map<string, ApplyPatchV2BulkReplacementWritePlan>): { targets: string[]; payload: string } {
  const targets = Array.from(paths);
  const records: string[] = [];
  for (const target of targets) {
    const plan = plans.get(target);
    if (plan === undefined) throw new ApplyPatchV2Error("missing bulk replacement plan", { target });
    const replacementFields: string[] = [];
    for (const [start, oldLength, newLines] of plan.replacements) {
      replacementFields.push([
        String(start),
        String(oldLength),
        Buffer.from(joinLinesWithFinalNewline(newLines), "utf8").toString("base64"),
      ].join(","));
    }
    records.push([
      Buffer.from(plan.path, "utf8").toString("base64"),
      String(plan.originalBytes),
      plan.originalSha256,
      String(plan.finalBytes),
      plan.finalSha256,
      plan.encodingStyle,
      replacementFields.join(";"),
    ].join(" "));
  }
  return { targets, payload: `${records.join("\n")}\n` };
}

export type ApplyPatchV2RemoteOperation =
  | "stat"
  | "read-b64-block"
  | "read-bulk-b64"
  | "apply-replacements-bulk-stdin"
  | "write-b64-argv"
  | "write-b64-stdin"
  | "write-b64-begin"
  | "write-b64-append"
  | "write-b64-commit"
  | "delete";

async function checkedRemoteV2(executor: ApplyPatchV2Executor, operation: ApplyPatchV2RemoteOperation, args: string[], input?: string): Promise<{ stdout: string }> {
  if (!executor.run) {
    throw new ApplyPatchV2Error("remote apply-patch v2 executor does not provide a command runner", { operation, args });
  }
  const result = await executor.run(applyPatchV2RemoteCommand(operation, args), input);
  if (result.exitCode === 0) return result;
  throw new ApplyPatchV2Error("remote apply-patch v2 operation failed", {
    operation,
    args,
    exitCode: result.exitCode,
    stdout: result.stdout.slice(-2000),
    stderr: result.stderr.slice(-4000),
  });
}

export function applyPatchV2RemoteCommand(operation: ApplyPatchV2RemoteOperation, args: string[]): string[] {
  const script = [
    "set -eu",
    "sha256_file() {",
    "  if command -v sha256sum >/dev/null 2>&1; then sha256sum -- \"$1\" | awk '{print $1}'; return; fi",
    "  if command -v shasum >/dev/null 2>&1; then shasum -a 256 -- \"$1\" | awk '{print $1}'; return; fi",
    "  if command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 -- \"$1\" | awk '{print $NF}'; return; fi",
    "  printf 'missing sha256 tool\\n' >&2",
    "  return 127",
    "}",
    "verify_tmp() {",
    "  target=$1",
    "  tmp=$2",
    "  expected_bytes=$3",
    "  expected_sha256=$4",
    "  actual_bytes=$(wc -c < \"$tmp\" | tr -d '[:space:]')",
    "  if [ \"$actual_bytes\" != \"$expected_bytes\" ]; then",
    "    rm -f -- \"$tmp\"",
    "    printf 'v2 decoded byte count mismatch for %s: expected=%s actual=%s\\n' \"$target\" \"$expected_bytes\" \"$actual_bytes\" >&2",
    "    exit 23",
    "  fi",
    "  actual_sha256=$(sha256_file \"$tmp\")",
    "  if [ \"$actual_sha256\" != \"$expected_sha256\" ]; then",
    "    rm -f -- \"$tmp\"",
    "    printf 'v2 decoded sha256 mismatch for %s: expected=%s actual=%s\\n' \"$target\" \"$expected_sha256\" \"$actual_sha256\" >&2",
    "    exit 24",
    "  fi",
    "}",
    "preserve_existing_mode() {",
    "  target=$1",
    "  replacement=$2",
    "  if [ ! -e \"$target\" ]; then return; fi",
    "  if mode=$(stat -c '%a' -- \"$target\" 2>/dev/null); then :",
    "  elif mode=$(stat -f '%Lp' -- \"$target\" 2>/dev/null); then :",
    "  else printf 'unable to read existing mode for %s\\n' \"$target\" >&2; return 1",
    "  fi",
    "  chmod \"$mode\" -- \"$replacement\"",
    "}",
    "set_tmp_paths() {",
    "  target=$1",
    "  token=$2",
    "  case \"$token\" in ''|*[!a-zA-Z0-9_.-]*) printf 'invalid v2 temp token\\n' >&2; exit 2;; esac",
    "  base=${target##*/}",
    "  dir=.",
    "  case \"$target\" in */*) dir=${target%/*};; esac",
    "  tmp=\"$dir/.${base}.unidesk-v2-${token}.tmp\"",
    "  tmp_b64=\"$tmp.b64\"",
    "}",
    "op=$1",
    "shift",
    "case \"$op\" in",
    "  stat)",
    "    target=$1",
    "    if [ ! -e \"$target\" ]; then printf 'file not found: %s\\n' \"$target\" >&2; exit 1; fi",
    "    if [ -d \"$target\" ]; then printf 'not a file: %s\\n' \"$target\" >&2; exit 1; fi",
    "    bytes=$(wc -c < \"$target\" | tr -d '[:space:]')",
    "    digest=$(sha256_file \"$target\")",
    "    printf '%s %s\\n' \"$bytes\" \"$digest\"",
    "    ;;",
    "  read-b64-block)",
    "    target=$1",
    "    block_index=$2",
    "    block_size=$3",
    "    case \"$block_index:$block_size\" in *[!0-9:]*|:*) printf 'invalid read block args\\n' >&2; exit 2;; esac",
    "    tmp=$(mktemp)",
    "    dd if=\"$target\" bs=\"$block_size\" skip=\"$block_index\" count=1 2>/dev/null > \"$tmp\"",
    "    bytes=$(wc -c < \"$tmp\" | tr -d '[:space:]')",
    "    digest=$(sha256_file \"$tmp\")",
    `    printf '${remoteReadBlockMarker} %s %s\\n' "$bytes" "$digest"`,
    "    base64 < \"$tmp\" | tr -d '\\n'",
    "    printf '\\n'",
    "    rm -f -- \"$tmp\"",
    "    ;;",
    "  read-bulk-b64)",
    "    count=$#",
    `    printf '${remoteBulkReadMarker} %s\\n' "$count"`,
    "    for target in \"$@\"; do",
    "      if [ ! -e \"$target\" ]; then printf 'file not found: %s\\n' \"$target\" >&2; exit 1; fi",
    "      if [ -d \"$target\" ]; then printf 'not a file: %s\\n' \"$target\" >&2; exit 1; fi",
    "      path_b64=$(printf '%s' \"$target\" | base64 | tr -d '\\n')",
    "      bytes=$(wc -c < \"$target\" | tr -d '[:space:]')",
    "      digest=$(sha256_file \"$target\")",
    "      printf '%s %s %s ' \"$path_b64\" \"$bytes\" \"$digest\"",
    "      base64 < \"$target\" | tr -d '\\n'",
    "      printf '\\n'",
    "    done",
    "    ;;",
    "  apply-replacements-bulk-stdin)",
    "    expected_count=$1",
    "    tmp_payload=$(mktemp)",
    "    tmp_dir=$(mktemp -d)",
    "    trap 'rm -f -- \"$tmp_payload\"; rm -rf -- \"$tmp_dir\"' EXIT HUP INT TERM",
    "    cat > \"$tmp_payload\"",
    "    python3 - \"$tmp_payload\" \"$tmp_dir\" \"$expected_count\" <<'PY'",
    "import base64, hashlib, pathlib, shutil, stat, sys",
    "def text_profile(source):",
    "    try:",
    "        source.decode('utf-8')",
    "        encoding = 'utf8-bom' if source.startswith(b'\\xef\\xbb\\xbf') else 'utf8'",
    "    except UnicodeDecodeError:",
    "        encoding = 'legacy-single-byte'",
    "    crlf = source.count(b'\\r\\n')",
    "    bare_lf = source.count(b'\\n') - crlf",
    "    lone_cr = source.count(b'\\r') - crlf",
    "    newline = 'crlf' if crlf > 0 and bare_lf == 0 and lone_cr == 0 else ('lf' if bare_lf > 0 and crlf == 0 and lone_cr == 0 else ('none' if crlf == 0 and bare_lf == 0 and lone_cr == 0 else 'mixed'))",
    "    return encoding, newline",
    "def integrity_failure(adapter, stage, target, expected_bytes, expected_sha256, source):",
    "    actual_sha256 = hashlib.sha256(source).hexdigest()",
    "    actual_encoding, actual_newline = text_profile(source)",
    "    print(f'UNIDESK_APPLY_PATCH_V2_INTEGRITY adapter={adapter} stage={stage} expectedBytes={expected_bytes} actualBytes={len(source)} expectedSha256={expected_sha256} actualSha256={actual_sha256} actualEncoding={actual_encoding} actualNewline={actual_newline}', file=sys.stderr)",
    "    raise SystemExit(f'bulk replacement {stage} integrity mismatch for {target}')",
    "payload_path, tmp_dir, expected_count_text = sys.argv[1:4]",
    "expected_count = int(expected_count_text)",
    "records = [line for line in pathlib.Path(payload_path).read_text(encoding='utf-8').splitlines() if line.strip()]",
    "if len(records) != expected_count:",
    "    raise SystemExit(f'bulk replacement record count mismatch: expected={expected_count} actual={len(records)}')",
    "plans = []",
    "for index, record in enumerate(records, start=1):",
    "    fields = record.split()",
    "    if len(fields) != 7:",
    "        raise SystemExit('bulk replacement malformed record')",
    "    path_b64, original_bytes_text, original_sha256, final_bytes_text, final_sha256, encoding_style, replacements_text = fields",
    "    target = base64.b64decode(path_b64).decode('utf-8')",
    "    original_bytes = int(original_bytes_text)",
    "    final_bytes = int(final_bytes_text)",
    "    target_path = pathlib.Path(target)",
    "    source = target_path.read_bytes()",
    "    original_mode = stat.S_IMODE(target_path.stat().st_mode)",
    "    if len(source) != original_bytes or hashlib.sha256(source).hexdigest() != original_sha256:",
    "        integrity_failure('posix', 'original', target, original_bytes, original_sha256, source)",
    "    source_encoding = 'latin-1' if encoding_style == 'legacy-single-byte' else 'utf-8'",
    "    lines = source.decode(source_encoding).split('\\n')",
    "    if lines and lines[-1] == '':",
    "        lines.pop()",
    "    replacements = []",
    "    for item in replacements_text.split(';'):",
    "        if not item:",
    "            continue",
    "        start_text, old_len_text, new_b64 = item.split(',', 2)",
    "        new_text = base64.b64decode(new_b64).decode('utf-8')",
    "        new_lines = new_text.split('\\n')",
    "        if new_lines and new_lines[-1] == '':",
    "            new_lines.pop()",
    "        replacements.append((int(start_text), int(old_len_text), new_lines))",
    "    for start, old_len, new_lines in sorted(replacements, reverse=True):",
    "        if start < 0 or old_len < 0 or start + old_len > len(lines):",
    "            raise SystemExit(f'bulk replacement out of bounds for {target}')",
    "        lines[start:start + old_len] = new_lines",
    "    output = (('\\n'.join(lines) + '\\n') if lines else '').encode(source_encoding)",
    "    if len(output) != final_bytes or hashlib.sha256(output).hexdigest() != final_sha256:",
    "        integrity_failure('posix', 'final', target, final_bytes, final_sha256, output)",
    "    tmp_path = pathlib.Path(tmp_dir) / f'file-{index}.tmp'",
    "    tmp_path.write_bytes(output)",
    "    plans.append((target, final_sha256, tmp_path, original_mode))",
    "for target, final_sha256, tmp_path, original_mode in plans:",
    "    target_path = pathlib.Path(target)",
    "    if str(target_path.parent) not in ('', '.'):",
    "        target_path.parent.mkdir(parents=True, exist_ok=True)",
    "    tmp_path.chmod(original_mode)",
    "    shutil.move(str(tmp_path), str(target_path))",
    "    if hashlib.sha256(target_path.read_bytes()).hexdigest() != final_sha256:",
    "        raise SystemExit(f'bulk replacement final sha256 mismatch for {target}')",
    "PY",
    "    rm -f -- \"$tmp_payload\"",
    "    rm -rf -- \"$tmp_dir\"",
    "    trap - EXIT HUP INT TERM",
    "    ;;",
    "  write-b64-argv)",
    "    target=$1",
    "    expected_bytes=$2",
    "    expected_sha256=$3",
    "    shift 3",
    "    case \"$target\" in */*) parent=${target%/*}; mkdir -p -- \"$parent\";; esac",
    "    base=${target##*/}",
    "    dir=.",
    "    case \"$target\" in */*) dir=${target%/*};; esac",
    "    tmp=\"$dir/.${base}.unidesk-v2-$$.tmp\"",
    "    : > \"$tmp.b64\"",
    "    for chunk in \"$@\"; do printf '%s' \"$chunk\" >> \"$tmp.b64\"; done",
    "    base64 -d < \"$tmp.b64\" > \"$tmp\"",
    "    rm -f -- \"$tmp.b64\"",
    "    verify_tmp \"$target\" \"$tmp\" \"$expected_bytes\" \"$expected_sha256\"",
    "    preserve_existing_mode \"$target\" \"$tmp\"",
    "    mv -f -- \"$tmp\" \"$target\"",
    "    actual_sha256=$(sha256_file \"$target\")",
    "    if [ \"$actual_sha256\" != \"$expected_sha256\" ]; then printf 'v2 final sha256 mismatch for %s\\n' \"$target\" >&2; exit 25; fi",
    "    ;;",
    "  write-b64-stdin)",
    "    target=$1",
    "    expected_bytes=$2",
    "    expected_sha256=$3",
    "    case \"$target\" in */*) parent=${target%/*}; mkdir -p -- \"$parent\";; esac",
    "    base=${target##*/}",
    "    dir=.",
    "    case \"$target\" in */*) dir=${target%/*};; esac",
    "    tmp=\"$dir/.${base}.unidesk-v2-$$.tmp\"",
    "    if ! base64 -d > \"$tmp\"; then rm -f -- \"$tmp\"; printf 'v2 base64 decode failed for %s\\n' \"$target\" >&2; exit 22; fi",
    "    verify_tmp \"$target\" \"$tmp\" \"$expected_bytes\" \"$expected_sha256\"",
    "    preserve_existing_mode \"$target\" \"$tmp\"",
    "    mv -f -- \"$tmp\" \"$target\"",
    "    actual_sha256=$(sha256_file \"$target\")",
    "    if [ \"$actual_sha256\" != \"$expected_sha256\" ]; then printf 'v2 final sha256 mismatch for %s\\n' \"$target\" >&2; exit 25; fi",
    "    ;;",
    "  write-b64-begin)",
    "    target=$1",
    "    case \"$target\" in */*) parent=${target%/*}; mkdir -p -- \"$parent\";; esac",
    "    set_tmp_paths \"$target\" \"$2\"",
    "    : > \"$tmp_b64\"",
    "    ;;",
    "  write-b64-append)",
    "    target=$1",
    "    set_tmp_paths \"$target\" \"$2\"",
    "    printf '%s' \"$3\" >> \"$tmp_b64\"",
    "    ;;",
    "  write-b64-commit)",
    "    target=$1",
    "    set_tmp_paths \"$target\" \"$2\"",
    "    expected_bytes=$3",
    "    expected_sha256=$4",
    "    if ! base64 -d < \"$tmp_b64\" > \"$tmp\"; then rm -f -- \"$tmp\" \"$tmp_b64\"; printf 'v2 base64 decode failed for %s\\n' \"$target\" >&2; exit 22; fi",
    "    rm -f -- \"$tmp_b64\"",
    "    verify_tmp \"$target\" \"$tmp\" \"$expected_bytes\" \"$expected_sha256\"",
    "    preserve_existing_mode \"$target\" \"$tmp\"",
    "    mv -f -- \"$tmp\" \"$target\"",
    "    actual_sha256=$(sha256_file \"$target\")",
    "    if [ \"$actual_sha256\" != \"$expected_sha256\" ]; then printf 'v2 final sha256 mismatch for %s\\n' \"$target\" >&2; exit 25; fi",
    "    ;;",
    "  delete)",
    "    target=$1",
    "    if [ ! -e \"$target\" ]; then printf 'file not found: %s\\n' \"$target\" >&2; exit 1; fi",
    "    rm -- \"$target\"",
    "    ;;",
    "  *)",
    "    printf 'unsupported op: %s\\n' \"$op\" >&2",
    "    exit 2",
    "    ;;",
    "esac",
  ].join("\n");
  return ["sh", "-c", script, "unidesk-apply-patch-v2", operation, ...args];
}

function sha256Hex(value: Buffer): string {
  return createHash("sha256").update(value).digest("hex");
}

function chunkString(value: string, chunkSize: number): string[] {
  const chunks: string[] = [];
  for (let index = 0; index < value.length; index += chunkSize) {
    chunks.push(value.slice(index, index + chunkSize));
  }
  return chunks.length > 0 ? chunks : [""];
}

function readStreamText(stream: Readable): Promise<string> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    stream.on("data", (chunk: Buffer | string) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
    stream.on("error", reject);
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
    stream.resume();
  });
}

function stripLenientHeredoc(text: string): string {
  const trimmed = text.trim();
  const lines = trimmed.length === 0 ? [] : trimmed.split(/\r?\n/u);
  const first = lines[0] ?? "";
  const last = lines[lines.length - 1] ?? "";
  if ((first === "<<EOF" || first === "<<'EOF'" || first === '<<"EOF"') && last.endsWith("EOF") && lines.length >= 4) {
    return lines.slice(1, -1).join("\n");
  }
  return text;
}

function validatePatchPath(value: string, line: number): string {
  const filePath = value.trim();
  if (filePath.length === 0) throw new ApplyPatchV2Error("patch path cannot be empty", { line });
  return filePath;
}

function isFileHeader(line: string): boolean {
  const trimmed = line.trim();
  return trimmed.startsWith(addFileMarker) || trimmed.startsWith(deleteFileMarker) || trimmed.startsWith(updateFileMarker) || trimmed === beginMarker || trimmed === endMarker;
}

function isUpdateChunkHeader(line: string): boolean {
  return line === emptyChangeContextMarker || line.startsWith(changeContextMarker) || parseUnifiedDiffHunkHeader(line) !== null;
}

function pushUniqueHint(hints: string[], prefix: string, hint: string): void {
  if (!hints.some((existing) => existing.startsWith(prefix))) hints.push(hint);
}

function parseUpdateChunk(lines: string[], startIndex: number, allowMissingContext: boolean, filePath: string, hints: string[]): { chunk: UpdateChunk; nextIndex: number } {
  let index = startIndex;
  let changeContext: string | null = null;
  let sourceStartLine: number | null = null;
  const first = lines[index] ?? "";
  if (first === emptyChangeContextMarker) {
    index += 1;
  } else {
    const unifiedHeader = parseUnifiedDiffHunkHeader(first);
    if (unifiedHeader !== null) {
      sourceStartLine = unifiedHeader.oldStart;
      hints.push(`apply-patch hint: accepted unified-diff hunk header in ${filePath} on line ${startIndex + 1}; canonical apply_patch uses @@ or @@ context without line ranges.`);
      index += 1;
    } else if (first.startsWith(changeContextMarker)) {
      changeContext = first.slice(changeContextMarker.length);
      index += 1;
    } else if (!allowMissingContext) {
      throw new ApplyPatchV2Error("expected update chunk to start with @@ context marker", { line: startIndex + 1, text: first });
    }
  }

  const oldLines: string[] = [];
  const newLines: string[] = [];
  const contextLinePairs: Array<{ oldIndex: number; newIndex: number }> = [];
  let parsed = 0;
  let isEndOfFile = false;
  let contextLineCount = 0;
  let addedLineCount = 0;
  let deletedLineCount = 0;
  function pushContextLine(value: string): void {
    contextLinePairs.push({ oldIndex: oldLines.length, newIndex: newLines.length });
    oldLines.push(value);
    newLines.push(value);
    contextLineCount += 1;
  }
  while (index < lines.length - 1) {
    const line = lines[index] ?? "";
    if (isFileHeader(line)) break;
    if (parsed > 0 && isUpdateChunkHeader(line)) break;
    if (line === eofMarker) {
      if (parsed === 0) throw new ApplyPatchV2Error("update chunk does not contain any lines", { line: index + 1 });
      isEndOfFile = true;
      index += 1;
      break;
    }
    const marker = line[0] ?? "";
    if (marker === " ") {
      pushContextLine(line.slice(1));
    } else if (marker === "+") {
      newLines.push(line.slice(1));
      addedLineCount += 1;
    } else if (marker === "-") {
      oldLines.push(line.slice(1));
      deletedLineCount += 1;
    } else if (line.length === 0) {
      pushContextLine("");
    } else {
      pushUniqueHint(
        hints,
        `apply-patch hint: accepted unprefixed Update File context line in ${filePath}`,
        `apply-patch hint: accepted unprefixed Update File context line in ${filePath} on line ${index + 1}; prefix context lines with one extra space in addition to source indentation.`,
      );
      pushContextLine(line);
    }
    parsed += 1;
    index += 1;
  }
  if (parsed === 0) throw new ApplyPatchV2Error("update chunk does not contain any lines", { line: startIndex + 1 });
  return { chunk: { changeContext, sourceStartLine, oldLines, newLines, contextLinePairs, contextLineCount, addedLineCount, deletedLineCount, isEndOfFile }, nextIndex: index };
}

function parseUnifiedDiffHunkHeader(line: string): { oldStart: number } | null {
  const match = /^@@\s+-(\d+)(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@(?:\s+.*)?$/u.exec(line);
  if (match === null) return null;
  const oldStart = Number(match[1] ?? "0");
  if (!Number.isSafeInteger(oldStart) || oldStart < 0) return null;
  return { oldStart };
}

function splitContentLines(content: string): string[] {
  const lines = content.split("\n");
  if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
  return lines;
}

function detectApplyPatchTextFormat(content: string, encodingStyle: ApplyPatchV2ReadFile["encodingStyle"] = content.startsWith("\uFEFF") ? "utf8-bom" : "utf8"): Pick<ApplyPatchV2BulkReplacementWritePlan, "newlineStyle" | "encodingStyle"> {
  const crlfCount = (content.match(/\r\n/gu) ?? []).length;
  const bareLfCount = (content.match(/(?<!\r)\n/gu) ?? []).length;
  const loneCrCount = (content.match(/\r(?!\n)/gu) ?? []).length;
  const newlineStyle = crlfCount > 0 && bareLfCount === 0 && loneCrCount === 0
    ? "crlf"
    : bareLfCount > 0 && crlfCount === 0 && loneCrCount === 0
      ? "lf"
      : crlfCount === 0 && bareLfCount === 0 && loneCrCount === 0
        ? "none"
        : "mixed";
  return {
    newlineStyle,
    encodingStyle,
  };
}

function decodeApplyPatchV2ReadFile(contentBuffer: Buffer): ApplyPatchV2ReadFile {
  try {
    const content = new TextDecoder("utf-8", { fatal: true }).decode(contentBuffer);
    return {
      content,
      bytes: contentBuffer,
      encodingStyle: content.startsWith("\uFEFF") ? "utf8-bom" : "utf8",
    };
  } catch {
    return {
      content: contentBuffer.toString("latin1"),
      bytes: contentBuffer,
      encodingStyle: "legacy-single-byte",
    };
  }
}

function normalizeApplyPatchV2ReadFile(file: ApplyPatchV2ReadFile | string): ApplyPatchV2ReadFile {
  return typeof file === "string" ? decodeApplyPatchV2ReadFile(Buffer.from(file, "utf8")) : file;
}

function encodeApplyPatchText(content: string, encodingStyle: ApplyPatchV2ReadFile["encodingStyle"], path: string): Buffer {
  if (encodingStyle !== "legacy-single-byte") return Buffer.from(content, "utf8");
  for (const character of content) {
    if ((character.codePointAt(0) ?? 0) > 0xff) {
      throw new ApplyPatchV2Error("legacy single-byte localized replacement only supports byte-preserving text", {
        path,
        encodingStyle,
        replacementRequirement: "ASCII or existing single-byte characters",
      });
    }
  }
  return Buffer.from(content, "latin1");
}

function preserveReplacementNewlines(replacements: Replacement[], newlineStyle: ApplyPatchV2BulkReplacementWritePlan["newlineStyle"]): Replacement[] {
  if (newlineStyle !== "crlf") return replacements;
  return replacements.map(([start, oldLength, newLines]) => [
    start,
    oldLength,
    newLines.map((line) => line.endsWith("\r") ? line : `${line}\r`),
  ]);
}

function joinLinesWithFinalNewline(lines: string[]): string {
  if (lines.length === 0) return "";
  return `${lines.join("\n")}\n`;
}

function computeReplacements(filePath: string, originalLines: string[], chunks: UpdateChunk[]): Replacement[] {
  const replacements: Replacement[] = [];
  let lineIndex = 0;
  for (const [chunkIndex, chunk] of chunks.entries()) {
    if (chunk.changeContext !== null) {
      const foundContext = seekSequence(originalLines, [chunk.changeContext], lineIndex, false);
      if (foundContext === null) {
        throw new ApplyPatchV2Error("failed to find update context", { path: filePath, chunk: chunkIndex + 1, context: chunk.changeContext });
      }
      lineIndex = foundContext + 1;
    }
    if (chunk.oldLines.length === 0) {
      const insertAt = originalLines.length > 0 && originalLines[originalLines.length - 1] === "" ? originalLines.length - 1 : originalLines.length;
      replacements.push([insertAt, 0, chunk.newLines]);
      continue;
    }

    let pattern = chunk.oldLines;
    const preferredStart = chunk.sourceStartLine === null ? lineIndex : Math.max(lineIndex, chunk.sourceStartLine - 1);
    let found = seekSequenceWithFallback(originalLines, pattern, preferredStart, lineIndex, chunk.isEndOfFile);
    let newLines = chunk.newLines;
    if (found === null && pattern[pattern.length - 1] === "") {
      pattern = pattern.slice(0, -1);
      newLines = newLines[newLines.length - 1] === "" ? newLines.slice(0, -1) : newLines;
      found = seekSequenceWithFallback(originalLines, pattern, preferredStart, lineIndex, chunk.isEndOfFile);
    }
    if (found === null) {
      throw new ApplyPatchV2Error("failed to find expected lines", {
        path: filePath,
        chunk: chunkIndex + 1,
        expected: chunk.oldLines.join("\n"),
        diagnostics: expectedLineDiagnostics(originalLines, chunk, preferredStart),
      });
    }
    replacements.push([found, pattern.length, preserveMatchedContextLines(originalLines, found, newLines, chunk.contextLinePairs, pattern.length)]);
    lineIndex = found + pattern.length;
  }
  assertNonOverlappingReplacements(filePath, replacements, originalLines.length);
  return replacements;
}

function expectedLineDiagnostics(originalLines: string[], chunk: UpdateChunk, preferredStart: number): Record<string, unknown> {
  const firstExpectedLine = chunk.oldLines.find((line) => line.trim().length > 0) ?? "";
  const firstExpectedLineCandidates = firstExpectedLine.length === 0 ? [] : candidateLineNumbers(originalLines, firstExpectedLine, 8);
  const prefix = bestPrefixMatch(originalLines, chunk.oldLines, firstExpectedLine, preferredStart);
  const missingAddedPrefixes = likelyMissingAddedPrefixes(chunk, prefix.matchedLines);
  return {
    firstExpectedLine,
    firstExpectedLineCandidates,
    firstExpectedLineCandidatesTruncated: firstExpectedLine.length > 0 && candidateLineNumbers(originalLines, firstExpectedLine, 9).length > firstExpectedLineCandidates.length,
    bestPrefixMatchedLines: prefix.matchedLines,
    bestPrefixStartLine: prefix.startLine,
    likelyMissingAddedPrefixes: missingAddedPrefixes,
    likelyStaleOrOversizedContext: !missingAddedPrefixes && likelyStaleOrOversizedContext(chunk, prefix.matchedLines),
  };
}

function candidateLineNumbers(lines: string[], expectedLine: string, limit: number): number[] {
  const result: number[] = [];
  for (let index = 0; index < lines.length; index += 1) {
    if (lineEquivalent(lines[index] ?? "", expectedLine)) {
      result.push(index + 1);
      if (result.length >= limit) break;
    }
  }
  return result;
}

function bestPrefixMatch(lines: string[], expectedLines: string[], firstExpectedLine: string, preferredStart: number): { startLine: number | null; matchedLines: number } {
  let best = { startLine: null as number | null, matchedLines: 0 };
  if (expectedLines.length === 0) return best;
  for (let index = Math.max(0, preferredStart); index < lines.length; index += 1) {
    if (firstExpectedLine.length > 0 && !lineEquivalent(lines[index] ?? "", firstExpectedLine)) continue;
    let matched = 0;
    while (index + matched < lines.length && matched < expectedLines.length && lineEquivalent(lines[index + matched] ?? "", expectedLines[matched] ?? "")) matched += 1;
    if (matched > best.matchedLines) best = { startLine: index + 1, matchedLines: matched };
    if (matched === expectedLines.length) break;
  }
  if (best.matchedLines > 0 || preferredStart <= 0) return best;
  for (let index = 0; index < Math.min(preferredStart, lines.length); index += 1) {
    if (firstExpectedLine.length > 0 && !lineEquivalent(lines[index] ?? "", firstExpectedLine)) continue;
    let matched = 0;
    while (index + matched < lines.length && matched < expectedLines.length && lineEquivalent(lines[index + matched] ?? "", expectedLines[matched] ?? "")) matched += 1;
    if (matched > best.matchedLines) best = { startLine: index + 1, matchedLines: matched };
  }
  return best;
}

function likelyMissingAddedPrefixes(chunk: UpdateChunk, bestPrefixMatchedLines: number): boolean {
  if (chunk.deletedLineCount > 0) return false;
  if (chunk.oldLines.length < 8) return false;
  if (chunk.addedLineCount > 2) return false;
  if (chunk.contextLineCount < 8) return false;
  return bestPrefixMatchedLines > 0 && bestPrefixMatchedLines < chunk.oldLines.length;
}

function likelyStaleOrOversizedContext(chunk: UpdateChunk, bestPrefixMatchedLines: number): boolean {
  if (chunk.oldLines.length < 4) return false;
  if (bestPrefixMatchedLines < 2 || bestPrefixMatchedLines >= chunk.oldLines.length) return false;
  return chunk.addedLineCount > 0 || chunk.deletedLineCount > 0 || chunk.contextLineCount >= 4;
}

function lineEquivalent(left: string, right: string): boolean {
  return left === right || left.trimEnd() === right.trimEnd() || left.trim() === right.trim() || normalizeLine(left) === normalizeLine(right);
}

function preserveMatchedContextLines(originalLines: string[], found: number, newLines: string[], contextLinePairs: UpdateChunk["contextLinePairs"], matchedOldLength: number): string[] {
  if (contextLinePairs.length === 0) return newLines;
  const result = [...newLines];
  for (const pair of contextLinePairs) {
    if (pair.oldIndex >= matchedOldLength || pair.newIndex >= result.length) continue;
    const originalLine = originalLines[found + pair.oldIndex];
    if (originalLine !== undefined) result[pair.newIndex] = originalLine;
  }
  return result;
}

function seekSequenceWithFallback(lines: string[], pattern: string[], preferredStart: number, fallbackStart: number, eof: boolean): number | null {
  const preferred = seekSequence(lines, pattern, preferredStart, eof);
  if (preferred !== null || preferredStart === fallbackStart) return preferred;
  return seekSequence(lines, pattern, fallbackStart, eof);
}

function applyReplacements(lines: string[], replacements: Replacement[]): string[] {
  const result = [...lines];
  for (const [start, oldLen, newSegment] of [...replacements].reverse()) {
    result.splice(start, oldLen, ...newSegment);
  }
  return result;
}

function assertNonOverlappingReplacements(filePath: string, replacements: Replacement[], lineCount: number): void {
  const sorted = [...replacements].sort((left, right) => left[0] - right[0]);
  let previousEnd = 0;
  for (const [start, oldLen] of sorted) {
    if (start < 0 || oldLen < 0 || start + oldLen > lineCount) {
      throw new ApplyPatchV2Error("computed replacement is outside file bounds", {
        path: filePath,
        start,
        oldLen,
        lineCount,
      });
    }
    if (start < previousEnd) {
      throw new ApplyPatchV2Error("computed replacements overlap", {
        path: filePath,
        start,
        previousEnd,
      });
    }
    previousEnd = Math.max(previousEnd, start + oldLen);
  }
}

function seekSequence(lines: string[], pattern: string[], start: number, eof: boolean): number | null {
  if (pattern.length === 0) return start;
  if (pattern.length > lines.length) return null;
  const searchStart = eof && lines.length >= pattern.length ? lines.length - pattern.length : start;
  const attempts: Array<(value: string) => string> = [
    (value) => value,
    (value) => value.trimEnd(),
    (value) => value.trim(),
    normalizeLine,
  ];
  for (const normalize of attempts) {
    for (let index = searchStart; index <= lines.length - pattern.length; index += 1) {
      let ok = true;
      for (let offset = 0; offset < pattern.length; offset += 1) {
        if (normalize(lines[index + offset] ?? "") !== normalize(pattern[offset] ?? "")) {
          ok = false;
          break;
        }
      }
      if (ok) return index;
    }
  }
  return null;
}

function normalizeLine(value: string): string {
  return value.trim().replace(/[\u2010-\u2015\u2212]/gu, "-")
    .replace(/[\u2018\u2019\u201A\u201B]/gu, "'")
    .replace(/[\u201C\u201D\u201E\u201F]/gu, "\"")
    .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/gu, " ");
}
