import { createHash } from "node:crypto";
import { createServer } from "node:http";
import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";

import { HWPOD_NODE_OPS_CONTRACT_VERSION } from "./hwpod-node-ops-contract.ts";

const NODE_VERSION = "0.1.0-thin-node-ops";
const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024;
const BOOLEAN_OPTIONS = new Set(["help", "h", "json"]);

type ParsedArgs = Record<string, unknown> & { _: string[] };

export async function mainHwpodNode(argv = process.argv.slice(2), options: any = {}) {
  const stdinText = options.stdinText ?? await readCliStdinForRun(argv);
  const result = await runHwpodNode(argv, { ...options, stdinText });
  if (result?.keepAlive) return;
  console.log(JSON.stringify(result.payload, null, 2));
  process.exitCode = result.exitCode;
}

async function readCliStdinForRun(argv: string[]) {
  const command = argv.find((token) => !token.startsWith("-")) || "help";
  const hasInlinePlan = argv.some((token) => token === "--plan-json" || token === "--plan" || token.startsWith("--plan-json=") || token.startsWith("--plan="));
  if (command !== "run" || hasInlinePlan || process.stdin.isTTY) return undefined;
  const chunks: Buffer[] = [];
  for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
  const textValue = Buffer.concat(chunks).toString("utf8");
  return textValue.trim() ? textValue : undefined;
}

export async function runHwpodNode(argv: string[], options: { stdinText?: string; now?: () => string } = {}) {
  const now = options.now ?? (() => new Date().toISOString());
  try {
    const parsed = parseOptions(argv);
    const command = parsed._[0] || "help";
    if (["help", "--help", "-h"].includes(command)) return result(0, help(), now);
    if (command === "run") {
      const plan = planFromParsed(parsed, options.stdinText);
      return result(0, await executeHwpodNodeOpsPlan(plan, { now }), now);
    }
    if (command === "serve") {
      const server = createHwpodNodeServer({ now });
      const host = text(parsed.host) || "127.0.0.1";
      const port = numberValue(parsed.port) ?? 19678;
      await new Promise((resolve) => server.listen(port, host, resolve));
      console.log(JSON.stringify({ ok: true, action: "hwpod-node.serve", status: "listening", host, port: server.address()?.port ?? port, observedAt: now() }, null, 2));
      return { exitCode: 0, payload: null, keepAlive: true, server };
    }
    if (command === "connect") {
      const nodeId = text(parsed.nodeId ?? parsed.node) || process.env.HWPOD_NODE_ID || os.hostname();
      const connector = connectHwpodNodeWs({
        wsUrl: text(parsed.wsUrl),
        cloudUrl: text(parsed.cloudUrl ?? parsed.apiUrl),
        token: text(parsed.token) || process.env.HWPOD_NODE_WS_TOKEN || process.env.HWLAB_HWPOD_NODE_WS_TOKEN || "",
        nodeId,
        name: text(parsed.name) || nodeId,
        heartbeatIntervalMs: numberValue(parsed.heartbeatIntervalMs) ?? 15000,
        reconnectBaseMs: numberValue(parsed.reconnectBaseMs) ?? 1000,
        reconnectMaxMs: numberValue(parsed.reconnectMaxMs) ?? 30000,
        now
      });
      console.log(JSON.stringify({ ok: true, action: "hwpod-node.connect", status: "connecting", nodeId, wsUrl: connector.wsUrl, observedAt: now() }, null, 2));
      return { exitCode: 0, payload: null, keepAlive: true, connector };
    }
    throw cliError("unsupported_hwpod_node_command", `unsupported hwpod-node command: ${command}`);
  } catch (error) {
    return result(1, failure("hwpod-node", error), now);
  }
}

export function connectHwpodNodeWs(config: any, options: any = {}) {
  const now = config.now ?? options.now ?? (() => new Date().toISOString());
  const nodeId = requiredText(config.nodeId || process.env.HWPOD_NODE_ID || os.hostname(), "nodeId");
  const wsUrl = hwpodNodeWsUrl(config);
  const heartbeatIntervalMs = Math.max(1000, numberValue(config.heartbeatIntervalMs) ?? 15000);
  const reconnectBaseMs = Math.max(250, numberValue(config.reconnectBaseMs) ?? 1000);
  const reconnectMaxMs = Math.max(reconnectBaseMs, numberValue(config.reconnectMaxMs) ?? 30000);
  const reconnect = config.reconnect !== false;
  const executePlan = options.executePlan ?? ((plan: any) => executeHwpodNodeOpsPlan(plan, { now, nodeId }));
  let socket: WebSocket | null = null;
  let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
  let closed = false;
  let attempt = 0;
  let readyResolve: ((value: unknown) => void) | null = null;
  const ready = new Promise((resolve) => { readyResolve = resolve; });

  function connect() {
    if (closed) return;
    socket = new WebSocket(wsUrl);
    socket.addEventListener("open", () => {
      logWsEvent("open");
      attempt = 0;
      sendJson({
        type: "register",
        nodeId,
        name: text(config.name) || nodeId,
        capabilities: ["hwpod-node-ops", "cmd.run", "workspace.ls", "workspace.cat", "workspace.rg", "workspace.apply-patch", "workspace.write", "workspace.replace", "workspace.insert-after", "debug.build", "debug.download", "debug.reset", "io.uart.read"],
        labels: { platform: process.platform, arch: process.arch, hostname: os.hostname(), version: NODE_VERSION, startedAt: now() }
      });
      sendHeartbeat();
      heartbeatTimer = setInterval(sendHeartbeat, heartbeatIntervalMs);
    });
    socket.addEventListener("message", (event: MessageEvent) => {
      void handleWsMessage(event.data).catch((error) => {
        logWsEvent("message_error", { message: error?.message ?? String(error), code: error?.code ?? "hwpod_node_ws_message_failed" });
        sendJson({ type: "hwpod-node-error", nodeId, error: { message: error?.message ?? String(error), code: error?.code ?? "hwpod_node_ws_message_failed" }, observedAt: now() });
      });
    });
    socket.addEventListener("close", (event) => {
      logWsEvent("close", { code: event.code, reason: event.reason || null, reconnect });
      scheduleReconnect();
    });
    socket.addEventListener("error", () => logWsEvent("error", { readyState: socket?.readyState ?? null }));
  }

  function scheduleReconnect() {
    clearTimers();
    if (closed || !reconnect) return;
    attempt += 1;
    const delayMs = Math.min(reconnectMaxMs, reconnectBaseMs * 2 ** Math.min(attempt, 8));
    reconnectTimer = setTimeout(connect, delayMs);
  }

  async function handleWsMessage(raw: any) {
    const textValue = typeof raw === "string" ? raw : await new Response(raw).text();
    const message = JSON.parse(textValue);
    if (message?.type === "ack" && message.requestId === "register" && readyResolve) {
      logWsEvent("registered", { ok: message.ok === true, message: text(message.message) || null });
      readyResolve(message);
      readyResolve = null;
      return;
    }
    if (message?.type !== "hwpod-node-ops") return;
    const requestId = text(message.requestId) || "req_unknown";
    let resultPayload;
    try {
      resultPayload = await executePlan(message.plan);
    } catch (error) {
      resultPayload = failure("hwpod-node.ws.run", error);
    }
    sendJson({ type: "hwpod-node-ops-result", nodeId, requestId, result: resultPayload, observedAt: now() });
  }

  function sendHeartbeat() {
    sendJson({ type: "heartbeat", nodeId, at: now() });
  }

  function sendJson(value: any) {
    if (!socket || socket.readyState !== WebSocket.OPEN) return false;
    socket.send(JSON.stringify(value));
    return true;
  }

  function clearTimers() {
    if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
    if (reconnectTimer !== null) clearTimeout(reconnectTimer);
    heartbeatTimer = null;
    reconnectTimer = null;
  }

  function close() {
    closed = true;
    clearTimers();
    socket?.close();
  }

  function logWsEvent(event: string, extra: Record<string, unknown> = {}) {
    process.stderr.write(`${JSON.stringify({ event: `hwpod-node.ws.${event}`, nodeId, wsUrl: redactUrlToken(wsUrl), observedAt: now(), ...extra })}\n`);
  }

  connect();
  return { wsUrl, nodeId, ready, close };
}

function redactUrlToken(urlValue: string) {
  const parsed = new URL(urlValue);
  if (parsed.searchParams.has("token")) parsed.searchParams.set("token", "<redacted>");
  return parsed.toString();
}

function hwpodNodeWsUrl(config: any) {
  const explicit = text(config.wsUrl);
  if (explicit) return appendToken(explicit, text(config.token));
  const cloudUrl = text(config.cloudUrl).replace(/\/+$/u, "");
  if (!cloudUrl) throw cliError("hwpod_node_ws_url_required", "connect requires --ws-url or --cloud-url");
  const parsed = new URL(cloudUrl);
  parsed.protocol = parsed.protocol === "https:" ? "wss:" : "ws:";
  parsed.pathname = "/v1/hwpod-node/ws";
  parsed.search = "";
  return appendToken(parsed.toString(), text(config.token));
}

function appendToken(urlValue: string, token: string) {
  if (!token) return urlValue;
  const parsed = new URL(urlValue);
  parsed.searchParams.set("token", token);
  return parsed.toString();
}

export function createHwpodNodeServer(options: any = {}) {
  const now = options.now ?? (() => new Date().toISOString());
  const localNodeId = localHwpodNodeId(options);
  return createServer(async (request, response) => {
    try {
      const url = new URL(request.url || "/", "http://hwpod-node.local");
      if (request.method === "GET" && (url.pathname === "/health/live" || url.pathname === "/v1/hwpod-node-ops")) {
        return sendJson(response, 200, {
          ok: true,
          status: "ready",
          serviceId: "hwpod-node",
          contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
          version: NODE_VERSION,
          nodeRole: "single-host-hwpod-node",
          acceptedInput: "hwpod-node-ops",
          specAuthority: "none",
          nodeId: localNodeId,
          observedAt: now()
        });
      }
      if (request.method === "POST" && url.pathname === "/v1/hwpod-node-ops") {
        const body = await readBody(request, options.bodyLimitBytes);
        const plan = body ? JSON.parse(body) : {};
        return sendJson(response, 200, await executeHwpodNodeOpsPlan(plan, { now, nodeId: localNodeId }));
      }
      return sendJson(response, 404, { ok: false, error: { code: "not_found", message: "hwpod-node route not found" } });
    } catch (error) {
      return sendJson(response, 500, failure("hwpod-node.http", error));
    }
  });
}

export async function executeHwpodNodeOpsPlan(plan: any, options: any = {}) {
  const now = options.now ?? (() => new Date().toISOString());
  if (!plan || typeof plan !== "object" || Array.isArray(plan)) throw cliError("invalid_hwpod_node_ops_plan", "plan must be a JSON object");
  if (plan.contractVersion !== HWPOD_NODE_OPS_CONTRACT_VERSION) throw cliError("invalid_hwpod_node_ops_contract", `contractVersion must be ${HWPOD_NODE_OPS_CONTRACT_VERSION}`);
  if (!Array.isArray(plan.ops) || plan.ops.length === 0) throw cliError("invalid_hwpod_node_ops", "ops must be a non-empty array");
  const localNodeId = localHwpodNodeId(options);
  const requestedNodeId = text(plan.nodeId);
  const enforceLocalNodeId = Boolean(text(options.nodeId) || text(process.env.HWPOD_NODE_ID));
  if (enforceLocalNodeId && requestedNodeId && requestedNodeId !== localNodeId) {
    return hwpodNodeMismatchPayload(plan, {
      localNodeId,
      requestedNodeId,
      now,
      summary: `hwpod-node ${localNodeId} cannot execute plan for ${requestedNodeId}`
    });
  }
  const results = [];
  for (const op of plan.ops) {
    results.push(await executeOp(op, { plan, now }));
  }
  const failed = results.some((item) => item.ok === false);
  return {
    ok: !failed,
    status: failed ? "failed" : "completed",
    contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
    serviceId: "hwpod-node",
    nodeVersion: NODE_VERSION,
    nodeRole: "single-host-hwpod-node",
    acceptedInput: "hwpod-node-ops",
    specAuthority: "none",
    planId: plan.planId ?? null,
    hwpodId: plan.hwpodId ?? null,
    nodeId: requestedNodeId || localNodeId,
    localNodeId,
    results,
    observedAt: now()
  };
}

function localHwpodNodeId(options: any = {}) {
  return text(options.nodeId) || text(process.env.HWPOD_NODE_ID) || os.hostname();
}

function hwpodNodeMismatchPayload(plan: any, input: { localNodeId: string; requestedNodeId: string; now: () => string; summary: string }) {
  const ops = Array.isArray(plan.ops) ? plan.ops : [];
  const blocker = {
    code: "hwpod_node_id_mismatch",
    layer: "hwpod-node",
    retryable: true,
    summary: input.summary,
    details: {
      localNodeId: input.localNodeId,
      requestedNodeId: input.requestedNodeId,
      platform: process.platform,
      hostname: os.hostname(),
      cwd: process.cwd(),
      nodeVersion: NODE_VERSION
    }
  };
  return {
    ok: false,
    status: "blocked",
    contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
    serviceId: "hwpod-node",
    nodeVersion: NODE_VERSION,
    nodeRole: "single-host-hwpod-node",
    acceptedInput: "hwpod-node-ops",
    specAuthority: "none",
    planId: plan.planId ?? null,
    hwpodId: plan.hwpodId ?? null,
    nodeId: input.requestedNodeId,
    localNodeId: input.localNodeId,
    results: ops.map((op: any, index: number) => ({
      opId: text(op?.opId) || `op_${index + 1}`,
      op: text(op?.op) || "unknown",
      ok: false,
      status: "blocked",
      blocker
    })),
    blocker,
    observedAt: input.now()
  };
}

async function executeOp(op: any, context: any) {
  const opId = text(op?.opId) || "op_unknown";
  const name = text(op?.op);
  const args = op?.args && typeof op.args === "object" && !Array.isArray(op.args) ? op.args : {};
  try {
    if (name === "node.health") return opOk(opId, name, { platform: process.platform, arch: process.arch, hostname: os.hostname(), cwd: process.cwd() });
    if (name === "node.version") return opOk(opId, name, { version: NODE_VERSION });
    if (name === "node.inventory") return opOk(opId, name, await nodeInventory(args));
    if (name === "workspace.ls") return opOk(opId, name, await workspaceLs(args));
    if (name === "workspace.cat") return opOk(opId, name, await workspaceCat(args));
    if (name === "workspace.rg") return opOk(opId, name, await workspaceRg(args));
    if (name === "workspace.apply-patch") return opOk(opId, name, await workspaceApplyPatch(args));
    if (name === "workspace.write") return opOk(opId, name, await workspaceWrite(args));
    if (name === "workspace.replace") return opOk(opId, name, await workspaceReplace(args));
    if (name === "workspace.insert-after") return opOk(opId, name, await workspaceInsertAfter(args));
    if (name === "cmd.run") {
      const output = await cmdRun(args);
      if (output.blockerCode) return opBlocked(opId, name, output.blockerCode, output.stderr || "cmd.run failed before process start", { output });
      return output.ok ? opOk(opId, name, output) : opFailed(opId, name, output, "hwpod_node_command_failed", `cmd.run node-side command exited with ${output.exitCode}`);
    }
    if (["debug.build", "debug.download", "debug.reset"].includes(name)) {
      const output = await debugCommand(name, args);
      return output.ok ? opOk(opId, name, output) : opFailed(opId, name, output, "hwpod_node_command_failed", `${name} node-side command exited with ${output.exitCode}`);
    }
    if (name === "io.uart.read") {
      const output = await uartRead(args);
      return output.ok ? opOk(opId, name, output) : opBlocked(opId, name, output.blockerCode, output.summary, output.details);
    }
    if (["io.uart.write", "io.uart.jsonrpc"].includes(name)) {
      return opBlocked(opId, name, "hwpod_node_op_not_configured", `${name} requires node-side UART tool binding on this hwpod-node`, uartBindingDiagnostics(name, args));
    }
    return opBlocked(opId, name, "unsupported_hwpod_node_op", `unsupported hwpod-node op: ${name}`);
  } catch (error) {
    return opBlockedFromError(opId, name, error);
  }
}

async function nodeInventory(args: any) {
  const root = workspaceRoot(args);
  const info = await stat(root).catch(() => null);
  return { workspaceRoot: root, workspaceExists: Boolean(info), workspaceIsDirectory: info?.isDirectory?.() ?? false };
}

async function workspaceLs(args: any) {
  const target = resolveWorkspacePath(args, text(args.path) || ".");
  const entries = await readdir(target, { withFileTypes: true });
  return {
    path: target,
    entries: entries.map((entry) => ({ name: entry.name, type: entry.isDirectory() ? "dir" : entry.isFile() ? "file" : "other" }))
  };
}

async function workspaceCat(args: any) {
  const target = resolveWorkspacePath(args, requiredText(args.path, "path"));
  const maxBytes = numberValue(args.maxBytes) ?? 65536;
  const content = await readFile(target, "utf8");
  const truncated = Buffer.byteLength(content, "utf8") > maxBytes;
  return { path: target, content: truncated ? content.slice(0, maxBytes) : content, truncated };
}

async function workspaceRg(args: any) {
  const pattern = requiredText(args.pattern, "pattern");
  const cwd = workspaceRoot(args);
  const searchPath = text(args.path) || ".";
  const target = resolveWorkspacePath(args, searchPath);
  const flags = args.ignoreCase === true ? "iu" : "u";
  let matcher: RegExp;
  try {
    matcher = new RegExp(pattern, flags);
  } catch (error) {
    throw cliError("workspace_rg_invalid_pattern", `invalid workspace.rg pattern: ${pattern}`, { pattern, message: error?.message ?? String(error) });
  }

  const maxFiles = numberValue(args.maxFiles) ?? 5000;
  const maxMatches = numberValue(args.maxMatches) ?? 200;
  const maxBytesPerFile = numberValue(args.maxBytesPerFile) ?? 1024 * 1024;
  const lineLimit = numberValue(args.maxLineBytes) ?? 600;
  const contextLines = Math.max(numberValue(args.context) ?? 0, 0);
  const beforeContext = Math.max(numberValue(args.beforeContext) ?? contextLines, 0);
  const afterContext = Math.max(numberValue(args.afterContext) ?? contextLines, 0);
  const matches: Array<{ path: string; line: number; text: string; before?: Array<{ line: number; text: string }>; after?: Array<{ line: number; text: string }> }> = [];
  const state = { scannedFiles: 0, skippedFiles: 0, skippedDirectories: 0, truncated: false };

  async function scanFile(filePath: string) {
    if (state.truncated || state.scannedFiles >= maxFiles || matches.length >= maxMatches) {
      state.truncated = true;
      return;
    }
    const info = await stat(filePath).catch(() => null);
    if (!info?.isFile?.()) {
      state.skippedFiles += 1;
      return;
    }
    if (info.size > maxBytesPerFile) {
      state.skippedFiles += 1;
      return;
    }
    const buffer = await readFile(filePath).catch(() => null);
    if (!buffer || buffer.includes(0)) {
      state.skippedFiles += 1;
      return;
    }
    state.scannedFiles += 1;
    const displayPath = workspaceDisplayPath(cwd, filePath);
    const lines = buffer.toString("utf8").split(/\r\n|\n|\r/u);
    for (let index = 0; index < lines.length; index += 1) {
      if (matcher.test(lines[index])) {
        matches.push(workspaceSearchMatch(displayPath, lines, index, beforeContext, afterContext, lineLimit));
        if (matches.length >= maxMatches) {
          state.truncated = true;
          return;
        }
      }
    }
  }

  async function walk(directory: string) {
    if (state.truncated) return;
    const entries = await readdir(directory, { withFileTypes: true }).catch(() => null);
    if (!entries) {
      state.skippedDirectories += 1;
      return;
    }
    for (const entry of entries) {
      if (state.truncated) return;
      const child = path.join(directory, entry.name);
      if (entry.isDirectory()) {
        if (isWorkspaceSearchSkippedDirectory(entry.name)) {
          state.skippedDirectories += 1;
          continue;
        }
        await walk(child);
      } else if (entry.isFile()) {
        await scanFile(child);
      } else {
        state.skippedFiles += 1;
      }
    }
  }

  const info = await stat(target).catch(() => null);
  if (!info) throw cliError("workspace_rg_path_not_found", `workspace.rg path not found: ${searchPath}`, { workspacePath: cwd, path: searchPath, target });
  if (info.isDirectory()) await walk(target);
  else await scanFile(target);

  const stdout = renderWorkspaceSearchStdout(matches);
  return {
    ok: true,
    engine: "node-recursive-search",
    cwd,
    path: searchPath,
    target,
    pattern,
    ignoreCase: args.ignoreCase === true,
    exitCode: 0,
    stdout,
    stderr: "",
    scannedFiles: state.scannedFiles,
    skippedFiles: state.skippedFiles,
    skippedDirectories: state.skippedDirectories,
    matchCount: matches.length,
    matches,
    truncated: state.truncated,
    limits: { maxFiles, maxMatches, maxBytesPerFile, maxLineBytes: lineLimit, beforeContext, afterContext }
  };
}

function workspaceSearchMatch(displayPath: string, lines: string[], index: number, beforeContext: number, afterContext: number, lineLimit: number) {
  const before = [];
  const after = [];
  for (let lineIndex = Math.max(0, index - beforeContext); lineIndex < index; lineIndex += 1) {
    before.push({ line: lineIndex + 1, text: truncateText(lines[lineIndex], lineLimit) });
  }
  for (let lineIndex = index + 1; lineIndex <= Math.min(lines.length - 1, index + afterContext); lineIndex += 1) {
    after.push({ line: lineIndex + 1, text: truncateText(lines[lineIndex], lineLimit) });
  }
  const match: { path: string; line: number; text: string; before?: Array<{ line: number; text: string }>; after?: Array<{ line: number; text: string }> } = {
    path: displayPath,
    line: index + 1,
    text: truncateText(lines[index], lineLimit)
  };
  if (before.length) match.before = before;
  if (after.length) match.after = after;
  return match;
}

function renderWorkspaceSearchStdout(matches: Array<{ path: string; line: number; text: string; before?: Array<{ line: number; text: string }>; after?: Array<{ line: number; text: string }> }>) {
  const lines: string[] = [];
  for (const match of matches) {
    for (const context of match.before ?? []) lines.push(`${match.path}-${context.line}-${context.text}`);
    lines.push(`${match.path}:${match.line}:${match.text}`);
    for (const context of match.after ?? []) lines.push(`${match.path}-${context.line}-${context.text}`);
  }
  return lines.join("\n");
}

function isWorkspaceSearchSkippedDirectory(name: string) {
  return new Set([".git", ".worktree", "node_modules", "Objects", "Listings", "Output", "build", "dist"]).has(name);
}

function workspaceDisplayPath(root: string, filePath: string) {
  const relative = path.relative(root, filePath) || path.basename(filePath);
  return relative.replace(/[\\]+/gu, "/");
}

function truncateText(value: string, maxBytes: number) {
  if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
  let output = "";
  for (const char of value) {
    if (Buffer.byteLength(`${output}${char}`, "utf8") > maxBytes) break;
    output += char;
  }
  return `${output}...<truncated>`;
}

async function workspaceApplyPatch(args: any) {
  const patch = text(args.patch) || text(args.patchContent) || (text(args.patchBase64) ? Buffer.from(text(args.patchBase64), "base64").toString("utf8") : "");
  if (!patch) throw cliError("patch_required", "patch, patchContent, or patchBase64 is required", { acceptedArgs: ["patch", "patchContent", "patchBase64"], engine: APPLY_PATCH_ENGINE });
  const cwd = workspaceRoot(args);
  const result = await applyPatchEnvelope(cwd, patch);
  return { cwd, ...result };
}

async function workspaceWrite(args: any) {
  const relativePath = requiredText(args.path, "path");
  const target = resolveWorkspacePath(args, relativePath);
  const before = await readTextFileState(target).catch((error) => {
    if (error?.code === "ENOENT") return null;
    throw error;
  });
  assertExpectedSha(before, args.expectedSha, relativePath);
  const content = contentFromArgs(args, "content");
  const lineEnding = lineEndingFromArgs(args, before?.lineEnding ?? "\n");
  const finalContent = normalizeContentLineEndings(content, lineEnding, args.finalNewline === true);
  const afterSha = sha256Text(finalContent);
  const dryRun = args.dryRun === true;
  if (!dryRun) {
    await mkdir(path.dirname(target), { recursive: true });
    await writeFile(target, finalContent, "utf8");
  }
  return editResult({ action: "write", path: relativePath, before, afterContent: finalContent, dryRun });
}

async function workspaceReplace(args: any) {
  const relativePath = requiredText(args.path, "path");
  const target = resolveWorkspacePath(args, relativePath);
  const before = await readTextFileState(target);
  assertExpectedSha(before, args.expectedSha, relativePath);
  const find = requiredRawString(args.find, "find");
  const replace = rawString(args.replace ?? args.replacement);
  const unescapedFind = String(find).replace(/\\r\\n/gu, "\r\n").replace(/\\n/gu, "\n").replace(/\\r(?!\n)/gu, "\r");
  const normalizedFind = normalizeContentLineEndings(unescapedFind, "\n", false);
  const normalizedReplace = normalizeContentLineEndings(replace, "\n", false);
  const normalizedContent = before.normalizedContent;
  const occurrences = countOccurrences(normalizedContent, normalizedFind);
  if (occurrences === 0) {
    throw cliError("workspace_replace_anchor_not_found", `replace text not found for ${relativePath}`, diagnosticDetails(before, normalizedFind, relativePath));
  }
  if (args.all !== true && occurrences !== 1) {
    throw cliError("workspace_replace_ambiguous", `replace text matched ${occurrences} times for ${relativePath}; pass --all for global replacement`, { path: relativePath, occurrences, fileSha256: before.sha256 });
  }
  const afterNormalized = args.all === true ? normalizedContent.split(normalizedFind).join(normalizedReplace) : normalizedContent.replace(normalizedFind, normalizedReplace);
  const afterContent = normalizeContentLineEndings(afterNormalized, before.lineEnding, false);
  const dryRun = args.dryRun === true;
  if (!dryRun) await writeFile(target, afterContent, "utf8");
  return editResult({ action: "replace", path: relativePath, before, afterContent, dryRun, extra: { occurrences, all: args.all === true } });
}

async function workspaceInsertAfter(args: any) {
  const relativePath = requiredText(args.path, "path");
  const target = resolveWorkspacePath(args, relativePath);
  const before = await readTextFileState(target);
  assertExpectedSha(before, args.expectedSha, relativePath);
  const anchor = requiredRawString(args.anchor, "anchor");
  const insertText = requiredRawString(args.line ?? args.insert ?? args.text, "line");
  const unescapedAnchor = String(anchor).replace(/\\r\\n/gu, "\r\n").replace(/\\n/gu, "\n").replace(/\\r(?!\n)/gu, "\r");
  const normalizedAnchor = normalizeContentLineEndings(unescapedAnchor, "\n", false);
  const normalizedInsert = normalizeContentLineEndings(insertText, "\n", false);
  const lines = splitNormalizedLines(before.normalizedContent, before.hadFinalNewline);
  const anchorLines = splitNormalizedLines(normalizedAnchor, false);
  const match = findInsertAnchor(lines, anchorLines, args.allowMultiple === true);
  if (match.line < 0) {
    throw cliError("workspace_insert_anchor_not_found", `insert anchor not found for ${relativePath}`, diagnosticDetails(before, normalizedAnchor, relativePath));
  }
  if (match.ambiguous && args.allowMultiple !== true) {
    throw cliError("workspace_insert_anchor_ambiguous", `insert anchor matched more than once for ${relativePath}; pass --allow-multiple to use the first match`, { path: relativePath, firstLine: match.line + 1, secondLine: match.secondLine + 1, fileSha256: before.sha256, anchorMatch: match.kind });
  }
  const insertLines = splitNormalizedLines(normalizedInsert, false);
  const outputLines = [...lines.slice(0, match.line + match.length), ...insertLines, ...lines.slice(match.line + match.length)];
  const afterNormalized = `${outputLines.join("\n")}${before.hadFinalNewline ? "\n" : ""}`;
  const afterContent = normalizeContentLineEndings(afterNormalized, before.lineEnding, false);
  const dryRun = args.dryRun === true;
  if (!dryRun) await writeFile(target, afterContent, "utf8");
  return editResult({ action: "insert-after", path: relativePath, before, afterContent, dryRun, extra: { anchorLine: match.line + 1, insertedLines: insertLines.length, anchorMatch: match.kind } });
}

async function debugCommand(op: string, args: any) {
  const commandRuns = commandRunsFromArgs(args);
  if (commandRuns.length > 0) return await runCommandRuns(op, args, commandRuns);
  const command = text(args.commandLine) || text(args.command);
  if (!command) throw cliError("hwpod_node_op_not_configured", `${op} requires an explicit node-side command binding in the compiled hwpod-node-ops plan`);
  const cwd = workspaceRoot(args);
  const output = await spawnShellOutput(command, { cwd, timeoutMs: numberValue(args.timeoutMs) ?? 120000 });
  return { cwd, command, bindingSource: "hwpod-node-ops.command", ...output };
}

function commandRunsFromArgs(args: any) {
  if (Array.isArray(args.commandRuns)) return args.commandRuns;
  if (args.commandRun && typeof args.commandRun === "object" && !Array.isArray(args.commandRun)) return [args.commandRun];
  return [];
}

async function runCommandRuns(op: string, args: any, commandRuns: any[]) {
  const cwd = workspaceRoot(args);
  const outputs = [];
  let exitCode = 0;
  for (let index = 0; index < commandRuns.length; index += 1) {
    const run = normalizeCommandRun(commandRuns[index], index);
    const timeoutMs = numberValue(run.timeoutMs) ?? numberValue(args.timeoutMs) ?? 120000;
    const output = await spawnOutput([run.command, ...run.argv], { cwd, timeoutMs });
    const item = { index, command: [run.command, ...run.argv], commandLine: run.commandLine || null, timeoutMs, ...output };
    outputs.push(item);
    exitCode = output.exitCode;
    if (!output.ok) break;
  }
  return {
    cwd,
    op,
    bindingSource: "hwpod-node-ops.commandRuns",
    commandCount: outputs.length,
    commands: outputs,
    exitCode,
    stdout: outputs.map((item) => item.stdout).join(""),
    stderr: outputs.map((item) => item.stderr).join(""),
    ok: outputs.length > 0 && outputs.every((item) => item.ok)
  };
}

function normalizeCommandRun(value: any, index: number) {
  if (!value || typeof value !== "object" || Array.isArray(value)) throw cliError("invalid_command_run", `commandRuns[${index}] must be an object`);
  const command = requiredText(value.command, `commandRuns[${index}].command`);
  const argv = Array.isArray(value.argv) ? value.argv.map(String) : [];
  return { command, argv, commandLine: text(value.commandLine), timeoutMs: value.timeoutMs };
}

const APPLY_PATCH_ENGINE = "codex-apply-patch-v2-compatible";
const APPLY_PATCH_BEGIN_MARKER = "*** Begin Patch";
const APPLY_PATCH_END_MARKER = "*** End Patch";
const APPLY_PATCH_ENVIRONMENT_MARKER = "*** Environment ID: ";
const APPLY_PATCH_ADD_FILE_MARKER = "*** Add File: ";
const APPLY_PATCH_DELETE_FILE_MARKER = "*** Delete File: ";
const APPLY_PATCH_UPDATE_FILE_MARKER = "*** Update File: ";
const APPLY_PATCH_MOVE_TO_MARKER = "*** Move to: ";
const APPLY_PATCH_EOF_MARKER = "*** End of File";

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

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

type HwpodPatchReplacement = [start: number, oldLength: number, newLines: string[]];

async function applyPatchEnvelope(root: string, patch: string) {
  const parsed = parseHwpodApplyPatchV2(patch);
  const changes: any[] = [];
  const outcomes: any[] = [];
  for (let index = 0; index < parsed.hunks.length; index += 1) {
    const hunk = parsed.hunks[index];
    try {
      const change = await applyParsedPatchHunk(root, hunk);
      changes.push(change);
      outcomes.push({ hunk: index + 1, action: change.action, path: change.path, targetPath: change.targetPath ?? undefined, status: "applied", change });
    } catch (error: any) {
      const failed = {
        hunk: index + 1,
        action: hunk.kind,
        path: hunk.path,
        targetPath: hunk.kind === "update" ? hunk.movePath ?? undefined : undefined,
        status: "failed",
        error: { code: error?.code || "apply_patch_v2_failed", message: error?.message || String(error), details: error?.details }
      };
      outcomes.push(failed);
      throw cliError(error?.code || "apply_patch_v2_failed", error?.message || String(error), { ...(error?.details ?? {}), engine: APPLY_PATCH_ENGINE, partialChanges: changes, outcomes, failed, cause: error?.details });
    }
  }
  if (changes.length === 0) throw cliError("apply_patch_no_changes", "No files were modified.", { engine: APPLY_PATCH_ENGINE });
  return { engine: APPLY_PATCH_ENGINE, changes, hints: parsed.hints, outcomes };
}

async function applyParsedPatchHunk(root: string, hunk: HwpodPatchHunk) {
  if (hunk.kind === "add") {
    const filePath = resolvePatchFile(root, hunk.path);
    await mkdir(path.dirname(filePath), { recursive: true });
    await writeFile(filePath, hunk.content, "utf8");
    return { action: "add", path: hunk.path, lines: splitPatchContentLines(hunk.content).length };
  }
  if (hunk.kind === "delete") {
    await rm(resolvePatchFile(root, hunk.path), { force: false });
    return { action: "delete", path: hunk.path };
  }
  const filePath = resolvePatchFile(root, hunk.path);
  const update = await derivePatchUpdate(filePath, hunk.chunks, hunk.path);
  if (hunk.movePath !== null && hunk.movePath !== hunk.path) {
    const targetPath = resolvePatchFile(root, hunk.movePath);
    await mkdir(path.dirname(targetPath), { recursive: true });
    await writeFile(targetPath, update.afterContent, "utf8");
    await rm(filePath, { force: false });
    return { action: "move", path: hunk.path, targetPath: hunk.movePath, hunks: hunk.chunks.length, replacements: update.replacements };
  }
  await writeFile(filePath, update.afterContent, "utf8");
  return { action: "update", path: hunk.path, hunks: hunk.chunks.length, replacements: update.replacements };
}

function parseHwpodApplyPatchV2(patch: string): { hunks: HwpodPatchHunk[]; hints: string[] } {
  const patchText = stripLenientPatchHeredoc(patch).trim();
  const lines = patchText.length === 0 ? [] : patchText.split(/\r?\n/u);
  const first = lines[0]?.trim();
  const last = lines[lines.length - 1]?.trim();
  if (first !== APPLY_PATCH_BEGIN_MARKER || last !== APPLY_PATCH_END_MARKER) throwInvalidApplyPatchEnvelope(patchText, first, last);

  const hunks: HwpodPatchHunk[] = [];
  const hints: string[] = [];
  let index = 1;
  if ((lines[index] ?? "").trimStart().startsWith(APPLY_PATCH_ENVIRONMENT_MARKER)) {
    const environmentId = (lines[index] ?? "").slice(APPLY_PATCH_ENVIRONMENT_MARKER.length).trim();
    if (!environmentId) throw cliError("invalid_apply_patch_environment", "apply_patch environment_id cannot be empty", { engine: APPLY_PATCH_ENGINE, line: index + 1 });
    index += 1;
  }
  while (index < lines.length - 1) {
    const line = (lines[index] ?? "").trim();
    if (!line) {
      index += 1;
      continue;
    }
    if (line === APPLY_PATCH_BEGIN_MARKER || line === APPLY_PATCH_END_MARKER) {
      pushUniquePatchHint(hints, `ignored nested ${line}`, `apply-patch hint: ignored nested ${line} marker on line ${index + 1}; keep one outer envelope around all hunks.`);
      index += 1;
      continue;
    }
    if (line.startsWith(APPLY_PATCH_ADD_FILE_MARKER)) {
      const filePath = validateApplyPatchPath(line.slice(APPLY_PATCH_ADD_FILE_MARKER.length), index + 1);
      index += 1;
      const added: string[] = [];
      while (index < lines.length - 1 && !isApplyPatchFileHeader(lines[index] ?? "")) {
        const addLine = lines[index] ?? "";
        if (addLine.startsWith("+")) {
          added.push(addLine.length > 1 && addLine.slice(1).trim().length === 0 ? "" : addLine.slice(1));
        } else if (addLine.trimStart().startsWith("@@")) {
          hints.push(`apply-patch hint: accepted @@ inside Add File ${filePath} on line ${index + 1}; Add File does not need @@.`);
        } else 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("");
        } else {
          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: joinPatchLinesWithFinalNewline(added) });
      continue;
    }
    if (line.startsWith(APPLY_PATCH_DELETE_FILE_MARKER)) {
      const filePath = validateApplyPatchPath(line.slice(APPLY_PATCH_DELETE_FILE_MARKER.length), index + 1);
      index += 1;
      const extraLines: number[] = [];
      while (index < lines.length - 1 && !isApplyPatchFileHeader(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 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(APPLY_PATCH_UPDATE_FILE_MARKER)) {
      const filePath = validateApplyPatchPath(line.slice(APPLY_PATCH_UPDATE_FILE_MARKER.length), index + 1);
      index += 1;
      let movePath: string | null = null;
      if ((lines[index] ?? "").startsWith(APPLY_PATCH_MOVE_TO_MARKER)) {
        movePath = validateApplyPatchPath((lines[index] ?? "").slice(APPLY_PATCH_MOVE_TO_MARKER.length), index + 1);
        index += 1;
      }
      const chunks: HwpodPatchChunk[] = [];
      while (index < lines.length - 1 && !isApplyPatchFileHeader(lines[index] ?? "")) {
        if ((lines[index] ?? "").trim().length === 0) {
          index += 1;
          continue;
        }
        const parsed = parseApplyPatchUpdateChunk(lines, index, chunks.length === 0, filePath, hints);
        chunks.push(parsed.chunk);
        index = parsed.nextIndex;
      }
      if (chunks.length === 0) throw cliError("invalid_update_hunk", "update file hunk is empty", { engine: APPLY_PATCH_ENGINE, line: index + 1, path: filePath });
      hunks.push({ kind: "update", path: filePath, movePath, chunks });
      continue;
    }
    throw cliError("unsupported_apply_patch_operation", `unsupported patch operation: ${line}`, { engine: APPLY_PATCH_ENGINE, line: index + 1, text: line });
  }
  return { hunks, hints };
}

function parseApplyPatchUpdateChunk(lines: string[], startIndex: number, allowMissingContext: boolean, filePath: string, hints: string[]) {
  let index = startIndex;
  let changeContext: string | null = null;
  let sourceStartLine: number | null = null;
  const first = lines[index] ?? "";
  if (first === "@@") {
    index += 1;
  } else {
    const unifiedHeader = parseApplyPatchUnifiedHunkHeader(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("@@ ")) {
      changeContext = first.slice("@@ ".length);
      index += 1;
    } else if (!allowMissingContext) {
      throw cliError("invalid_update_hunk", "expected update chunk to start with @@ context marker", { engine: APPLY_PATCH_ENGINE, 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) {
    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 (isApplyPatchFileHeader(line)) break;
    if (parsed > 0 && isApplyPatchUpdateChunkHeader(line)) break;
    if (line === APPLY_PATCH_EOF_MARKER) {
      if (parsed === 0) throw cliError("invalid_update_hunk", "update chunk does not contain any lines", { engine: APPLY_PATCH_ENGINE, 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 {
      pushUniquePatchHint(hints, `unprefixed context ${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 cliError("invalid_update_hunk", "update chunk does not contain any lines", { engine: APPLY_PATCH_ENGINE, line: startIndex + 1 });
  return { chunk: { changeContext, sourceStartLine, oldLines, newLines, contextLinePairs, contextLineCount, addedLineCount, deletedLineCount, isEndOfFile }, nextIndex: index };
}

async function derivePatchUpdate(filePath: string, chunks: HwpodPatchChunk[], relativePath: string) {
  const before = await readTextFileState(filePath);
  const originalLines = splitPatchContentLines(before.normalizedContent);
  const replacements = computePatchReplacements(relativePath, before, originalLines, chunks);
  const afterNormalized = joinPatchLinesWithFinalNewline(applyPatchReplacements(originalLines, replacements));
  const afterContent = normalizeContentLineEndings(afterNormalized, before.lineEnding, false);
  return { before, afterContent, replacements: replacements.length };
}

function computePatchReplacements(relativePath: string, before: any, originalLines: string[], chunks: HwpodPatchChunk[]): HwpodPatchReplacement[] {
  const replacements: HwpodPatchReplacement[] = [];
  let lineIndex = 0;
  for (const [chunkIndex, chunk] of chunks.entries()) {
    if (chunk.changeContext !== null) {
      const foundContext = seekPatchSequence(originalLines, [chunk.changeContext], lineIndex, false);
      if (foundContext === null) throw cliError("apply_patch_context_not_found", `patch context not found for ${relativePath}`, { ...diagnosticDetails(before, chunk.changeContext, relativePath), engine: APPLY_PATCH_ENGINE, chunk: chunkIndex + 1, context: chunk.changeContext });
      lineIndex = foundContext + 1;
    }
    if (chunk.oldLines.length === 0) {
      replacements.push([originalLines.length, 0, chunk.newLines]);
      continue;
    }
    let pattern = chunk.oldLines;
    const preferredStart = chunk.sourceStartLine === null ? lineIndex : Math.max(lineIndex, chunk.sourceStartLine - 1);
    let found = seekPatchSequenceWithFallback(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 = seekPatchSequenceWithFallback(originalLines, pattern, preferredStart, lineIndex, chunk.isEndOfFile);
    }
    if (found === null) {
      const expected = chunk.oldLines.join("\n");
      throw cliError("apply_patch_context_not_found", `patch context not found for ${relativePath}`, {
        ...diagnosticDetails(before, expected, relativePath),
        engine: APPLY_PATCH_ENGINE,
        chunk: chunkIndex + 1,
        expected,
        diagnostics: expectedPatchLineDiagnostics(originalLines, chunk, preferredStart)
      });
    }
    replacements.push([found, pattern.length, preservePatchMatchedContextLines(originalLines, found, newLines, chunk.contextLinePairs, pattern.length)]);
    lineIndex = found + pattern.length;
  }
  assertPatchReplacements(relativePath, replacements, originalLines.length);
  return replacements;
}

function throwInvalidApplyPatchEnvelope(patchText: string, first?: string, last?: string): never {
  const format = detectPatchTextFormat(patchText);
  if (format === "unified-diff") {
    throw cliError("unsupported_apply_patch_format", "hwpod workspace.apply-patch uses the Codex/UniDesk apply_patch v2 envelope; raw unified diff is not accepted", {
      engine: APPLY_PATCH_ENGINE,
      format,
      expectedFirstLine: APPLY_PATCH_BEGIN_MARKER,
      expectedLastLine: APPLY_PATCH_END_MARKER,
      hint: "Wrap edits in *** Begin Patch / *** Update File: <relative-path> / @@ / *** End Patch, or use workspace.write for a whole-file rewrite."
    });
  }
  throw cliError("invalid_apply_patch_envelope", `patch must start with ${APPLY_PATCH_BEGIN_MARKER} and end with ${APPLY_PATCH_END_MARKER}`, { engine: APPLY_PATCH_ENGINE, firstLine: first ?? null, lastLine: last ?? null });
}

function detectPatchTextFormat(patchText: string) {
  const trimmed = patchText.trimStart();
  if (trimmed.startsWith("diff --git") || trimmed.startsWith("--- ") || trimmed.startsWith("@@ ")) return "unified-diff";
  return "unknown";
}

function stripLenientPatchHeredoc(textValue: string) {
  const trimmed = textValue.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 textValue;
}

function validateApplyPatchPath(value: string, line: number) {
  const filePath = value.trim();
  if (!filePath || path.isAbsolute(filePath)) throw cliError("invalid_patch_path", "patch file paths must be relative", { engine: APPLY_PATCH_ENGINE, line, path: filePath });
  return filePath;
}

function isApplyPatchFileHeader(line: string) {
  const trimmed = line.trim();
  return trimmed.startsWith(APPLY_PATCH_ADD_FILE_MARKER) || trimmed.startsWith(APPLY_PATCH_DELETE_FILE_MARKER) || trimmed.startsWith(APPLY_PATCH_UPDATE_FILE_MARKER) || trimmed === APPLY_PATCH_BEGIN_MARKER || trimmed === APPLY_PATCH_END_MARKER;
}

function isApplyPatchUpdateChunkHeader(line: string) {
  return line === "@@" || line.startsWith("@@ ") || parseApplyPatchUnifiedHunkHeader(line) !== null;
}

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

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

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

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

function assertPatchReplacements(relativePath: string, replacements: HwpodPatchReplacement[], lineCount: number) {
  const sorted = [...replacements].sort((left, right) => left[0] - right[0]);
  let previousEnd = 0;
  for (const [start, oldLength] of sorted) {
    if (start < 0 || oldLength < 0 || start + oldLength > lineCount) throw cliError("apply_patch_replacement_out_of_bounds", "computed replacement is outside file bounds", { engine: APPLY_PATCH_ENGINE, path: relativePath, start, oldLength, lineCount });
    if (start < previousEnd) throw cliError("apply_patch_replacement_overlap", "computed replacements overlap", { engine: APPLY_PATCH_ENGINE, path: relativePath, start, previousEnd });
    previousEnd = Math.max(previousEnd, start + oldLength);
  }
}

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

function seekPatchSequence(lines: string[], pattern: string[], start: number, eof: boolean) {
  if (pattern.length === 0) return start;
  if (pattern.length > lines.length) return null;
  const searchStart = eof && lines.length >= pattern.length ? lines.length - pattern.length : Math.max(0, start);
  const attempts: Array<(value: string) => string> = [
    (value) => value,
    (value) => value.trimEnd(),
    (value) => value.trim(),
    normalizePatchLine
  ];
  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 preservePatchMatchedContextLines(originalLines: string[], found: number, newLines: string[], contextLinePairs: HwpodPatchChunk["contextLinePairs"], matchedOldLength: number) {
  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 expectedPatchLineDiagnostics(originalLines: string[], chunk: HwpodPatchChunk, preferredStart: number) {
  const firstExpectedLine = chunk.oldLines.find((line) => line.trim().length > 0) ?? "";
  const firstExpectedLineCandidates = firstExpectedLine ? patchCandidateLineNumbers(originalLines, firstExpectedLine, 8) : [];
  const prefix = bestPatchPrefixMatch(originalLines, chunk.oldLines, firstExpectedLine, preferredStart);
  const missingAddedPrefixes = likelyPatchMissingAddedPrefixes(chunk, prefix.matchedLines);
  return {
    firstExpectedLine,
    firstExpectedLineCandidates,
    firstExpectedLineCandidatesTruncated: firstExpectedLine.length > 0 && patchCandidateLineNumbers(originalLines, firstExpectedLine, 9).length > firstExpectedLineCandidates.length,
    bestPrefixMatchedLines: prefix.matchedLines,
    bestPrefixStartLine: prefix.startLine,
    likelyMissingAddedPrefixes: missingAddedPrefixes,
    likelyStaleOrOversizedContext: !missingAddedPrefixes && likelyPatchStaleOrOversizedContext(chunk, prefix.matchedLines)
  };
}

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

function bestPatchPrefixMatch(lines: string[], expectedLines: string[], firstExpectedLine: string, preferredStart: 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 && !patchLineEquivalent(lines[index] ?? "", firstExpectedLine)) continue;
    let matched = 0;
    while (index + matched < lines.length && matched < expectedLines.length && patchLineEquivalent(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 && !patchLineEquivalent(lines[index] ?? "", firstExpectedLine)) continue;
    let matched = 0;
    while (index + matched < lines.length && matched < expectedLines.length && patchLineEquivalent(lines[index + matched] ?? "", expectedLines[matched] ?? "")) matched += 1;
    if (matched > best.matchedLines) best = { startLine: index + 1, matchedLines: matched };
  }
  return best;
}

function likelyPatchMissingAddedPrefixes(chunk: HwpodPatchChunk, bestPrefixMatchedLines: number) {
  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 likelyPatchStaleOrOversizedContext(chunk: HwpodPatchChunk, bestPrefixMatchedLines: number) {
  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 patchLineEquivalent(left: string, right: string) {
  return left === right || left.trimEnd() === right.trimEnd() || left.trim() === right.trim() || normalizePatchLine(left) === normalizePatchLine(right);
}

function normalizePatchLine(value: 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, " ");
}

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

async function readTextFileState(filePath: string) {
  const content = await readFile(filePath, "utf8");
  const crlfCount = (content.match(/\r\n/gu) || []).length;
  const loneLfCount = (content.replace(/\r\n/gu, "").match(/\n/gu) || []).length;
  const loneCrCount = (content.replace(/\r\n/gu, "").match(/\r/gu) || []).length;
  const lineEnding = crlfCount > 0 && loneLfCount === 0 && loneCrCount === 0 ? "\r\n" : "\n";
  const normalizedContent = content.replace(/\r\n?/gu, "\n");
  return {
    content,
    normalizedContent,
    bytes: Buffer.byteLength(content, "utf8"),
    sha256: sha256Text(content),
    lineEnding,
    lineEndingCounts: { crlf: crlfCount, lf: loneLfCount, cr: loneCrCount },
    hadFinalNewline: normalizedContent.endsWith("\n")
  };
}

function splitNormalizedLines(content: string, hadFinalNewline: boolean) {
  const lines = content.split("\n");
  if (hadFinalNewline) lines.pop();
  return lines;
}

function diagnosticDetails(fileState: any, expected: string, relativePath: string) {
  const expectedLines = splitNormalizedLines(normalizeContentLineEndings(expected, "\n", false), false).filter((line) => line.length > 0);
  const normalizedLines = splitNormalizedLines(fileState.normalizedContent, fileState.hadFinalNewline);
  const candidates = expectedLines.slice(0, 4).flatMap((line) => candidateLines(normalizedLines, line)).slice(0, 12);
  return {
    path: relativePath,
    fileSha256: fileState.sha256,
    fileBytes: fileState.bytes,
    lineEnding: fileState.lineEnding,
    lineEndingCounts: fileState.lineEndingCounts,
    expectedPreview: previewLines(expectedLines.length ? expectedLines : [expected]),
    candidates,
    nodeVersion: NODE_VERSION,
    normalized: true
  };
}

function candidateLines(lines: string[], needle: string) {
  const trimmed = needle.trim();
  if (!trimmed) return [];
  return lines
    .map((line, index) => ({ lineNumber: index + 1, line }))
    .filter((item) => item.line.includes(trimmed) || item.line.trim() === trimmed)
    .slice(0, 4)
    .map((item) => ({ lineNumber: item.lineNumber, preview: item.line.slice(0, 160) }));
}

function previewLines(lines: string[]) {
  return lines.slice(0, 8).map((line, index) => ({ offset: index + 1, text: line.slice(0, 160) }));
}

function editResult({ action, path: relativePath, before, afterContent, dryRun, extra = {} }: any) {
  const afterSha = sha256Text(afterContent);
  const summary = diffSummary(before?.normalizedContent ?? "", afterContent.replace(/\r\n?/gu, "\n"));
  return {
    action,
    path: relativePath,
    dryRun: Boolean(dryRun),
    before: before ? { sha256: before.sha256, bytes: before.bytes, lineEnding: before.lineEnding, lineEndingCounts: before.lineEndingCounts } : null,
    after: { sha256: afterSha, bytes: Buffer.byteLength(afterContent, "utf8"), lineEnding: detectLineEnding(afterContent) },
    diff: summary,
    ...extra
  };
}

function diffSummary(beforeNormalized: string, afterNormalized: string) {
  const beforeLines = splitNormalizedLines(beforeNormalized, beforeNormalized.endsWith("\n"));
  const afterLines = splitNormalizedLines(afterNormalized, afterNormalized.endsWith("\n"));
  let prefix = 0;
  while (prefix < beforeLines.length && prefix < afterLines.length && beforeLines[prefix] === afterLines[prefix]) prefix += 1;
  let suffix = 0;
  while (suffix < beforeLines.length - prefix && suffix < afterLines.length - prefix && beforeLines[beforeLines.length - 1 - suffix] === afterLines[afterLines.length - 1 - suffix]) suffix += 1;
  const removed = beforeLines.slice(prefix, beforeLines.length - suffix);
  const added = afterLines.slice(prefix, afterLines.length - suffix);
  return {
    firstChangedLine: prefix + 1,
    removedLines: removed.length,
    addedLines: added.length,
    preview: [
      ...removed.slice(0, 6).map((line) => `-${line}`),
      ...added.slice(0, 6).map((line) => `+${line}`)
    ]
  };
}

function assertExpectedSha(before: any, expectedSha: unknown, relativePath: string) {
  const expected = text(expectedSha);
  if (!expected) return;
  if (!before) throw cliError("workspace_expected_sha_missing_file", `expectedSha was provided but ${relativePath} does not exist`, { path: relativePath, expectedSha: expected });
  if (before.sha256 !== expected) {
    throw cliError("workspace_expected_sha_mismatch", `expectedSha mismatch for ${relativePath}`, { path: relativePath, expectedSha: expected, actualSha: before.sha256, bytes: before.bytes });
  }
}

function contentFromArgs(args: any, name: string) {
  const direct = typeof args[name] === "string" ? args[name] : typeof args.contentText === "string" ? args.contentText : "";
  if (typeof direct === "string" && direct.length > 0) return direct;
  const base64 = text(args.contentBase64);
  if (base64) return Buffer.from(base64, "base64").toString("utf8");
  throw cliError("content_required", `${name} or contentBase64 is required`);
}

function lineEndingFromArgs(args: any, fallback: string) {
  const value = text(args.lineEnding).toLowerCase();
  if (!value || value === "preserve") return fallback;
  if (value === "crlf" || value === "\\r\\n") return "\r\n";
  if (value === "lf" || value === "\\n") return "\n";
  throw cliError("invalid_line_ending", "lineEnding must be preserve, lf, or crlf", { lineEnding: value });
}

function normalizeContentLineEndings(content: string, lineEnding: string, finalNewline: boolean) {
  const normalized = String(content).replace(/\r\n?/gu, "\n");
  const withFinal = finalNewline && !normalized.endsWith("\n") ? `${normalized}\n` : normalized;
  return lineEnding === "\r\n" ? withFinal.replace(/\n/gu, "\r\n") : withFinal;
}

function detectLineEnding(content: string) {
  const state = content.includes("\r\n") && !content.replace(/\r\n/gu, "").includes("\n") ? "\r\n" : "\n";
  return state;
}

function sha256Text(content: string) {
  return createHash("sha256").update(content, "utf8").digest("hex");
}

function countOccurrences(content: string, needle: string) {
  if (!needle) return 0;
  let count = 0;
  let index = 0;
  for (;;) {
    const found = content.indexOf(needle, index);
    if (found < 0) return count;
    count += 1;
    index = found + needle.length;
  }
}

function requiredRawString(value: unknown, name: string) {
  const normalized = rawString(value);
  if (!normalized) throw cliError("required_option_missing", `${name} is required`, { name });
  return normalized;
}

function rawString(value: unknown) {
  return typeof value === "string" ? value : "";
}

function findLineSequence(lines: string[], sequence: string[], start: number) {
  if (sequence.length === 0) return start;
  for (let index = start; index <= lines.length - sequence.length; index += 1) {
    if (sequence.every((line, offset) => lines[index + offset] === line)) return index;
  }
  return -1;
}

function findInsertAnchor(lines: string[], anchorLines: string[], allowMultiple: boolean) {
  const exact = findLineSequence(lines, anchorLines, 0);
  if (exact >= 0) {
    const secondExact = findLineSequence(lines, anchorLines, exact + 1);
    return { line: exact, secondLine: secondExact, length: anchorLines.length, kind: "line-sequence", ambiguous: secondExact >= 0 && !allowMultiple };
  }
  if (anchorLines.length !== 1) return { line: -1, secondLine: -1, length: anchorLines.length, kind: "line-sequence", ambiguous: false };
  const needle = anchorLines[0].trim();
  if (!needle) return { line: -1, secondLine: -1, length: 1, kind: "line-fragment", ambiguous: false };
  const matches = lines.map((line, index) => ({ line, index })).filter((item) => item.line.includes(needle));
  if (matches.length === 0) return { line: -1, secondLine: -1, length: 1, kind: "line-fragment", ambiguous: false };
  return { line: matches[0].index, secondLine: matches[1]?.index ?? -1, length: 1, kind: "line-fragment", ambiguous: matches.length > 1 && !allowMultiple };
}

function resolvePatchFile(root: string, relativePath: string) {
  if (!relativePath || path.isAbsolute(relativePath)) throw cliError("invalid_patch_path", "patch file paths must be relative", { path: relativePath });
  return resolveWorkspacePath({ workspacePath: root }, relativePath);
}

async function cmdRun(args: any) {
  const command = requiredText(args.command, "command");
  const argv = Array.isArray(args.argv) ? args.argv.map(String) : [];
  const cwd = workspaceRoot(args);
  const output = await spawnOutput([command, ...argv], { cwd, timeoutMs: numberValue(args.timeoutMs) ?? 30000 });
  return { cwd, command: [command, ...argv], ...output };
}

async function uartRead(args: any) {
  const diagnostics = uartBindingDiagnostics("io.uart.read", args);
  const serialMonitorDir = text(args.serialMonitorDir) || text(process.env.HWPOD_SERIAL_MONITOR_DIR) || path.join(os.homedir(), ".agents", "skills", "serial-monitor");
  const commandBase = serialMonitorCommandBase(args);
  const timeoutMs = numberValue(args.timeoutMs) ?? 10000;
  const status = await runSerialMonitor(commandBase, ["monitor", "status"], { cwd: serialMonitorDir, timeoutMs })
    .catch((error) => ({ ok: false, stdout: "", stderr: error?.message || String(error), exitCode: null, command: [...commandBase, "monitor", "status"] }));
  const statusBody = parseJsonMaybe(status.stdout);
  const statusData = objectValue(statusBody?.data);
  const targetPort = text(diagnostics.resolvedPort) || text(diagnostics.requestedPort);
  const targetBaudRate = numberValue(diagnostics.baudRate);
  const activePort = text(statusData.port);
  const activeBaudRate = numberValue(statusData.baudRate);
  const monitoringMatches = status.ok && statusBody?.success === true && statusData.isMonitoring === true
    && (!targetPort || activePort.toLowerCase() === targetPort.toLowerCase())
    && (!targetBaudRate || activeBaudRate === targetBaudRate);
  if (!monitoringMatches) {
    const ports = await runSerialMonitor(commandBase, ["ports"], { cwd: serialMonitorDir, timeoutMs }).catch((error) => ({ ok: false, stdout: "", stderr: error?.message || String(error), exitCode: null, command: [] }));
    return {
      ok: false,
      blockerCode: "hwpod_uart_monitor_not_active",
      summary: `io.uart.read requires serial-monitor to be active on ${targetPort || "the requested UART"}${targetBaudRate ? `/${targetBaudRate}` : ""}`,
      details: {
        ...diagnostics,
        serialMonitor: {
          dir: serialMonitorDir,
          command: commandBase,
          monitorStatus: statusBody ?? { parseOk: false, stdout: status.stdout, stderr: status.stderr, exitCode: status.exitCode },
          ports: parseJsonMaybe(ports.stdout) ?? { parseOk: false, stdout: ports.stdout, stderr: ports.stderr, exitCode: ports.exitCode },
          startCommand: `cd ${serialMonitorDir} && bun scripts/serial-monitor-cli.ts monitor start -p ${targetPort || "<PORT>"}${targetBaudRate ? ` -b ${targetBaudRate}` : ""}`
        }
      }
    };
  }
  const limit = Math.max(1, Math.min(numberValue(args.limit) ?? Math.ceil((numberValue(args.maxBytes) ?? 4096) / 80), 200));
  const maxBytes = numberValue(args.maxBytes) ?? 16384;
  const fetchArgs = ["fetch", "-l", String(limit), "--session-only", ...(text(args.since) ? ["-s", text(args.since)] : [])];
  const fetch = await runSerialMonitor(commandBase, fetchArgs, { cwd: serialMonitorDir, timeoutMs });
  const fetchBody = parseJsonMaybe(fetch.stdout);
  if (!fetch.ok || fetchBody?.success !== true) {
    return {
      ok: false,
      blockerCode: "hwpod_uart_read_failed",
      summary: "serial-monitor fetch failed for io.uart.read",
      details: { ...diagnostics, serialMonitor: { dir: serialMonitorDir, command: commandBase, fetch: fetchBody ?? { parseOk: false, stdout: fetch.stdout, stderr: fetch.stderr, exitCode: fetch.exitCode } } }
    };
  }
  const rows = Array.isArray(fetchBody.data) ? fetchBody.data : [];
  const joined = rows.map((row: any) => text(row?.data)).filter(Boolean).join("\n");
  const truncatedText = joined.length > maxBytes ? joined.slice(0, maxBytes) : joined;
  return {
    ok: true,
    bindingSource: "serial-monitor-cli",
    serialMonitorDir,
    requestedPort: diagnostics.requestedPort,
    resolvedPort: targetPort,
    baudRate: targetBaudRate || activeBaudRate || null,
    command: [...commandBase, ...fetchArgs],
    data: rows,
    count: fetchBody.count ?? rows.length,
    totalCount: fetchBody.totalCount ?? null,
    hasMore: fetchBody.hasMore ?? false,
    text: truncatedText,
    truncated: joined.length > maxBytes || fetchBody.truncated === true,
    monitorStatus: statusBody,
    sourceFile: fetchBody.sourceFile ?? null
  };
}

async function runSerialMonitor(commandBase: string[], argv: string[], { cwd, timeoutMs }: any) {
  const output = await spawnOutput([...commandBase, ...argv], { cwd, timeoutMs });
  return { command: [...commandBase, ...argv], ...output };
}

function serialMonitorCommandBase(args: any) {
  if (Array.isArray(args.serialMonitorCommand) && args.serialMonitorCommand.length > 0) return args.serialMonitorCommand.map(String);
  const configured = text(process.env.HWPOD_SERIAL_MONITOR_COMMAND);
  if (configured) return configured.split(/\s+/u).filter(Boolean);
  return ["bun", "scripts/serial-monitor-cli.ts"];
}

async function spawnShellOutput(command: string, { cwd, timeoutMs }: any) {
  const shellCommand = process.platform === "win32" ? ["cmd.exe", "/d", "/s", "/c", command] : ["sh", "-lc", command];
  const output = await spawnOutput(shellCommand, { cwd, timeoutMs });
  return { shell: shellCommand.slice(0, -1), exitCode: output.exitCode, stdout: output.stdout, stderr: output.stderr, ok: output.ok };
}

async function spawnOutput(command: string[], { cwd, stdinText = "", timeoutMs }: any) {
  const resolution = await resolveHwpodNodeCommand(command[0]);
  const resolvedCommand = [resolution.command, ...command.slice(1)];
  let proc;
  try {
    proc = Bun.spawn(resolvedCommand, { cwd, stdin: stdinText ? "pipe" : "ignore", stdout: "pipe", stderr: "pipe" });
  } catch (error) {
    return {
      exitCode: null,
      stdout: "",
      stderr: error instanceof Error ? error.message : String(error),
      ok: false,
      blockerCode: "hwpod_node_command_spawn_failed",
      commandResolution: resolution,
      spawnDiagnostics: commandSpawnDiagnostics(command, resolvedCommand, cwd, resolution, error)
    };
  }
  if (stdinText && proc.stdin) {
    proc.stdin.write(stdinText);
    proc.stdin.end();
  }
  const timeout = setTimeout(() => proc.kill(), timeoutMs);
  try {
    const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
    return { exitCode, stdout, stderr, ok: exitCode === 0, commandResolution: resolution };
  } finally {
    clearTimeout(timeout);
  }
}

function commandSpawnDiagnostics(command: string[], resolvedCommand: string[], cwd: string, resolution: any, error: unknown) {
  return {
    requestedCommand: command,
    resolvedCommand,
    commandResolution: resolution,
    cwd,
    platform: process.platform,
    hostname: os.hostname(),
    nodeVersion: NODE_VERSION,
    pathPreview: pathPreview(process.env.PATH),
    error: error instanceof Error ? { name: error.name, message: error.message, code: (error as any).code ?? null } : { message: String(error) }
  };
}

function pathPreview(value: unknown) {
  return text(value).split(path.delimiter).filter(Boolean).slice(0, 12);
}

export async function resolveHwpodNodeCommand(command: string, options: any = {}) {
  const requested = requiredText(command, "command");
  const platform = text(options.platform) || process.platform;
  if (isExplicitExecutablePath(requested)) {
    return { requested, command: requested, resolved: false, source: "explicit-path" };
  }
  if (platform !== "win32") {
    return { requested, command: requested, resolved: false, source: "process-path" };
  }
  const candidates = windowsWellKnownCommandCandidates(requested, options.env ?? process.env);
  const exists = options.fileExists ?? fileExists;
  for (const candidate of candidates) {
    if (await exists(candidate)) {
      return { requested, command: candidate, resolved: true, source: "windows-well-known-tool", candidates };
    }
  }
  return { requested, command: requested, resolved: false, source: "process-path", candidates };
}

function isExplicitExecutablePath(command: string) {
  return command.includes("/") || command.includes("\\") || path.isAbsolute(command) || isWindowsAbsolutePath(command);
}

function windowsWellKnownCommandCandidates(command: string, env: Record<string, unknown>) {
  const normalized = command.toLowerCase().replace(/\.exe$/u, "");
  if (normalized !== "git") return [];
  return [
    text(env.HWPOD_NODE_GIT_EXE),
    "C:\\Program Files\\Git\\cmd\\git.exe",
    "C:\\Program Files\\Git\\bin\\git.exe",
    "C:\\Program Files (x86)\\Git\\cmd\\git.exe",
    "C:\\Program Files (x86)\\Git\\bin\\git.exe"
  ].filter(Boolean);
}

async function fileExists(candidate: string) {
  try {
    const info = await stat(candidate);
    return info.isFile() || !info.isDirectory();
  } catch {
    return false;
  }
}

function workspaceRoot(args: any) {
  const workspacePath = text(args.workspacePath) || process.env.HWPOD_WORKSPACE_PATH || process.cwd();
  return isWindowsAbsolutePath(workspacePath) ? normalizeWindowsPath(workspacePath) : path.resolve(workspacePath);
}

function resolveWorkspacePath(args: any, relativePath: string) {
  const root = workspaceRoot(args);
  const target = resolveWorkspaceChild(root, relativePath);
  const separator = windowsPathKind(root) ? "\\" : path.sep;
  if (target !== root && !target.startsWith(`${root}${separator}`)) {
    throw cliError("workspace_path_outside_root", "workspace path must stay inside workspacePath", { workspacePath: root, path: relativePath });
  }
  return target;
}

function resolveWorkspaceChild(root: string, relativePath: string) {
  const normalizedRelative = text(relativePath) || ".";
  if (!windowsPathKind(root)) return path.resolve(root, normalizedRelative);
  if (normalizedRelative === ".") return root;
  if (isWindowsAbsolutePath(normalizedRelative) || normalizedRelative.startsWith("/")) return normalizeWindowsPath(normalizedRelative);
  return `${root.replace(/[\\/]+$/u, "")}\\${normalizedRelative.replace(/[\\/]+/gu, "\\").replace(/^[\\/]+/u, "")}`;
}

function windowsPathKind(value: string) {
  return /^[A-Za-z]:[\\/]/u.test(value) || value.startsWith("\\\\");
}

function isWindowsAbsolutePath(value: string) {
  return windowsPathKind(String(value ?? "").trim());
}

function normalizeWindowsPath(value: string) {
  return String(value).replace(/\//gu, "\\").replace(/[\\]+$/u, "");
}

function planFromParsed(parsed: ParsedArgs, stdinText?: string) {
  if (parsed.planJson) return JSON.parse(String(parsed.planJson));
  if (parsed.plan) return JSON.parse(String(parsed.plan));
  if (stdinText) return JSON.parse(stdinText);
  throw cliError("hwpod_node_plan_required", "pass --plan-json or pipe a hwpod-node-ops plan to hwpod-node run");
}

function help() {
  return {
    ok: true,
    action: "hwpod-node.help",
    status: "succeeded",
    contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
    version: NODE_VERSION,
    role: "Single host-side executor for stable hwpod-node-ops; hwpod-spec, hwpod-cli, hwpod-ctl, and hwpod-compiler stay in the Code Agent workspace.",
    usage: [
      "bun tools/hwpod-node.ts run --plan-json '{...}'",
      "bun tools/hwpod-node.ts serve --host 127.0.0.1 --port 19678",
      "bun tools/hwpod-node.ts connect --cloud-url http://74.48.78.17:19667 --node-id node-d601-f103-v2"
    ]
  };
}

function result(exitCode: number, payload: any, now: () => string) {
  return { exitCode, payload: { ...payload, observedAt: payload?.observedAt ?? now() } };
}

function opOk(opId: string, op: string, output: any) {
  return { opId, op, ok: true, status: "completed", output };
}

function opBlocked(opId: string, op: string, code: string, summary: string, details?: Record<string, unknown>) {
  return { opId, op, ok: false, status: "blocked", blocker: { code, layer: "hwpod-node", retryable: true, summary, details: details ?? undefined } };
}

function opBlockedFromError(opId: string, op: string, error: any) {
  return { opId, op, ok: false, status: "blocked", blocker: { code: error?.code || "hwpod_node_op_failed", layer: "hwpod-node", retryable: true, summary: error?.message || String(error), details: error?.details || undefined } };
}

function opFailed(opId: string, op: string, output: any, code: string, summary: string) {
  return { opId, op, ok: false, status: "failed", output, blocker: { code, layer: "hwpod-node", retryable: false, summary } };
}

function failure(action: string, error: any) {
  return { ok: false, action, status: "failed", error: { code: error?.code || "hwpod_node_error", message: error?.message || String(error), details: error?.details || undefined } };
}

function uartBindingDiagnostics(op: string, args: any) {
  const ioProbe = objectValue(args.ioProbe);
  const uart = objectValue(ioProbe.uart);
  const requestedPort = text(args.port) || text(uart.id) || "uart1";
  const devicePort = text(uart.port) || text(uart.path) || text(ioProbe.port);
  const baudRate = numberValue(args.baudRate ?? args.baudrate ?? uart.baudRate ?? uart.baudrate ?? ioProbe.baudRate ?? ioProbe.baudrate);
  return {
    op,
    requestedPort,
    resolvedPort: devicePort || requestedPort,
    baudRate: baudRate ?? null,
    workspacePath: text(args.workspacePath) || null,
    ioProbe,
    platform: process.platform,
    nodeVersion: NODE_VERSION,
    requiredBinding: {
      kind: "uart-tool",
      acceptedSpecPaths: ["spec.ioProbe.uart.port", "spec.ioProbe.port"],
      nodeEnvHints: ["HWPOD_SERIAL_MONITOR_DIR", "HWPOD_SERIAL_MONITOR_COMMAND", "HWPOD_UART_PORT"]
    },
    supportedFallback: false
  };
}

function parseJsonMaybe(value: string) {
  try {
    return JSON.parse(String(value ?? "").trim());
  } catch {
    return null;
  }
}

function parseOptions(argv: string[]): ParsedArgs {
  const parsed: ParsedArgs = { _: [] };
  for (let index = 0; index < argv.length; index += 1) {
    const token = argv[index];
    if (token === "--") {
      parsed._.push(...argv.slice(index + 1));
      break;
    }
    if (!token.startsWith("-") || token === "-") {
      parsed._.push(token);
      continue;
    }
    if (token === "-h") {
      parsed.h = true;
      parsed.help = true;
      continue;
    }
    const [rawKey, rawInline] = token.slice(2).split(/=(.*)/su, 2);
    const key = rawKey.replace(/-([a-z0-9])/giu, (_match, char) => char.toUpperCase());
    if (BOOLEAN_OPTIONS.has(key)) {
      parsed[key] = rawInline === undefined || rawInline === "" ? true : /^(?:1|true|yes|on)$/iu.test(rawInline);
      continue;
    }
    if (rawInline !== undefined && rawInline !== "") {
      parsed[key] = rawInline;
      continue;
    }
    const next = argv[index + 1];
    if (next === undefined || next.startsWith("--")) {
      parsed[key] = true;
    } else {
      parsed[key] = next;
      index += 1;
    }
  }
  return parsed;
}

function readBody(request: any, limitBytes = DEFAULT_BODY_LIMIT_BYTES) {
  const limit = Number.isInteger(limitBytes) && limitBytes > 0 ? limitBytes : DEFAULT_BODY_LIMIT_BYTES;
  return new Promise<string>((resolve, reject) => {
    let body = "";
    request.setEncoding("utf8");
    request.on("data", (chunk: string) => {
      body += chunk;
      if (Buffer.byteLength(body, "utf8") > limit) request.destroy(new Error(`request body exceeds ${limit} bytes`));
    });
    request.on("end", () => resolve(body));
    request.on("error", reject);
  });
}

function sendJson(response: any, statusCode: number, body: any) {
  response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
  response.end(`${JSON.stringify(body)}\n`);
}

function cliError(code: string, message: string, details: Record<string, unknown> = {}) {
  const error = new Error(message) as Error & { code?: string; details?: Record<string, unknown> };
  error.code = code;
  error.details = details;
  return error;
}

function requiredText(value: unknown, name: string) {
  const normalized = text(value);
  if (!normalized) throw cliError("required_option_missing", `${name} is required`, { name });
  return normalized;
}

function text(value: unknown) {
  return typeof value === "string" && value.trim() ? value.trim() : "";
}

function objectValue(value: any) {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value) ? value : {};
}

function numberValue(value: unknown) {
  const parsed = Number.parseInt(String(value ?? ""), 10);
  return Number.isInteger(parsed) ? parsed : undefined;
}
