import { randomBytes } from "node:crypto";
import { basename, join, resolve } from "node:path";
import {
  downloadSshFileVerified,
  type SshFileTransferCommandBuilders,
  type SshRemoteCommandExecutor,
  type SshVerifiedDownloadResult,
} from "./ssh-file-transfer";
import type { ParsedSshInvocation, ParsedSshRoute, SshCaptureResult } from "./ssh";

interface SshPlaywrightOptions {
  localDir: string;
  remoteDir: string | null;
  keepRemote: boolean;
  inactivityTimeoutMs?: number;
  pollIntervalMs: number;
  waitTimeoutMs: number;
}

interface RemotePlaywrightManifest {
  runId: string;
  exitCode: number;
  remoteDir: string;
  stdoutPath: string;
  stderrPath: string;
  cliMode: string | null;
  cliCwd: string | null;
  cliBin: string | null;
  defaultScreenshot: string | null;
  stdoutTail: string;
  stderrTail: string;
  artifacts: Array<{
    remotePath: string;
    bytes: number | null;
    sha256: string | null;
  }>;
}

const manifestBegin = "__UNIDESK_PLAYWRIGHT_MANIFEST_BEGIN__";
const manifestEnd = "__UNIDESK_PLAYWRIGHT_MANIFEST_END__";

export function isSshPlaywrightOperation(args: string[]): boolean {
  return (args[0] ?? "") === "playwright";
}

export async function runSshPlaywrightOperation(
  invocation: ParsedSshInvocation,
  args: string[],
  executor: SshRemoteCommandExecutor,
  builders: SshFileTransferCommandBuilders,
): Promise<number> {
  if (invocation.route.plane === "win") {
    throw new Error(`ssh ${invocation.route.raw} playwright is not supported for Windows routes; use a POSIX host/workspace route`);
  }
  if (invocation.route.plane === "k3s" && (invocation.route.namespace === null || invocation.route.resource === null)) {
    throw new Error(`ssh ${invocation.route.raw} playwright requires a workload route, or use a host workspace route`);
  }
  const options = parseSshPlaywrightOptions(args);
  const userScript = await readAllStdin();
  if (userScript.trim().length === 0) {
    throw new Error("ssh playwright requires a stdin heredoc script; example: trans D601 playwright <<'PW'\nplaywright-cli screenshot https://example.com \"$UNIDESK_PLAYWRIGHT_SCREENSHOT\"\nPW");
  }

  const runId = `unidesk-playwright-${safePathSegment(invocation.providerId)}-${Date.now()}-${randomBytes(4).toString("hex")}`;
  const remoteDir = options.remoteDir ?? `/tmp/${runId}`;
  const localDir = resolve(options.localDir);
  const submitCommand = builders.buildRouteCommand(invocation.route, ["sh", "-c", remotePlaywrightSubmitScript(remoteDir), "unidesk-playwright-submit"], { stdin: true });
  const submit = await executor.runRemoteCommand(submitCommand, userScript);
  const submitRecovery = submit.exitCode !== 0 && isRecoverableSubmitTimeout(submit)
    ? {
        recovered: true,
        reason: "submit-short-connection-timeout",
        remoteDir,
        runId,
        exitCode: submit.exitCode,
        stdoutTail: submit.stdout.slice(-1000),
        stderrTail: submit.stderr.slice(-1000),
        next: "submit may have been cut by the 60s trans runtime limit after the background job was launched; polling remote status by remoteDir/runId",
      }
    : null;
  if (submit.exitCode !== 0 && submitRecovery === null) {
    throw new Error(`ssh playwright submit failed: exitCode=${submit.exitCode}; remoteDir=${remoteDir}; runId=${runId}; stdoutTail=${JSON.stringify(submit.stdout.slice(-1000))}; stderrTail=${JSON.stringify(submit.stderr.slice(-1000))}`);
  }
  const manifest = await pollRemoteManifest(invocation.route, executor, builders, remoteDir, runId, options);
  const artifacts: Array<SshVerifiedDownloadResult & { manifestBytes: number | null; manifestSha256: string | null }> = [];
  let downloadFailure: Record<string, unknown> | null = null;

  for (const artifact of manifest.artifacts) {
    const localPath = join(localDir, `${runId}-${safePathSegment(basename(artifact.remotePath) || "artifact")}`);
    try {
      const download = await downloadSshFileVerified(
        invocation,
        executor,
        builders,
        artifact.remotePath,
        localPath,
        options.inactivityTimeoutMs,
      );
      artifacts.push({ ...download, manifestBytes: artifact.bytes, manifestSha256: artifact.sha256 });
    } catch (error) {
      downloadFailure = {
        remotePath: artifact.remotePath,
        message: error instanceof Error ? error.message : String(error),
        name: error instanceof Error ? error.name : undefined,
      };
      break;
    }
  }

  const cleanup = await cleanupRemoteDir(invocation.route, executor, builders, remoteDir, options.keepRemote);
  const ok = manifest.exitCode === 0 && downloadFailure === null;
  const payload = {
    ok,
    command: "ssh playwright",
    route: invocation.route.raw,
    providerId: invocation.providerId,
    runId,
    localDir,
    remote: {
      exitCode: manifest.exitCode,
      remoteDir: manifest.remoteDir,
      stdoutPath: manifest.stdoutPath,
      stderrPath: manifest.stderrPath,
      stdoutTail: manifest.stdoutTail,
      stderrTail: manifest.stderrTail,
      cliMode: manifest.cliMode,
      cliCwd: manifest.cliCwd,
      cliBin: manifest.cliBin,
      defaultScreenshot: manifest.defaultScreenshot,
    },
    artifacts,
    artifactCount: artifacts.length,
    expectedArtifactCount: manifest.artifacts.length,
    cleanup,
    downloadFailure,
    remoteCommand: {
      exitCode: manifest.exitCode,
      submitExitCode: submit.exitCode,
      submitRecovered: submitRecovery !== null,
      submitRecovery,
      submitStdoutBytes: Buffer.byteLength(submit.stdout, "utf8"),
      submitStderrBytes: Buffer.byteLength(submit.stderr, "utf8"),
    },
  };
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
  if (ok) return 0;
  return manifest.exitCode === 0 ? 1 : manifest.exitCode;
}

function parseSshPlaywrightOptions(args: string[]): SshPlaywrightOptions {
  const options: SshPlaywrightOptions = {
    localDir: "/tmp",
    remoteDir: null,
    keepRemote: false,
    pollIntervalMs: 2_000,
    waitTimeoutMs: 180_000,
  };
  for (let index = 1; index < args.length; index += 1) {
    const arg = args[index] ?? "";
    const next = args[index + 1];
    if (arg === "--help" || arg === "-h" || arg === "help") {
      process.stdout.write(`${JSON.stringify(playwrightHelp(), null, 2)}\n`);
      process.exit(0);
    }
    if (arg === "--keep-remote") {
      options.keepRemote = true;
      continue;
    }
    if (arg === "--local-dir") {
      options.localDir = requireValue(arg, next);
      index += 1;
      continue;
    }
    if (arg.startsWith("--local-dir=")) {
      options.localDir = requireValue("--local-dir", arg.slice("--local-dir=".length));
      continue;
    }
    if (arg === "--remote-dir") {
      options.remoteDir = requireAbsoluteRemotePath(arg, next);
      index += 1;
      continue;
    }
    if (arg.startsWith("--remote-dir=")) {
      options.remoteDir = requireAbsoluteRemotePath("--remote-dir", arg.slice("--remote-dir=".length));
      continue;
    }
    if (arg === "--inactivity-timeout-ms") {
      options.inactivityTimeoutMs = parsePositiveInteger(arg, next);
      index += 1;
      continue;
    }
    if (arg.startsWith("--inactivity-timeout-ms=")) {
      options.inactivityTimeoutMs = parsePositiveInteger("--inactivity-timeout-ms", arg.slice("--inactivity-timeout-ms=".length));
      continue;
    }
    if (arg === "--poll-interval-ms") {
      options.pollIntervalMs = parsePositiveInteger(arg, next);
      index += 1;
      continue;
    }
    if (arg.startsWith("--poll-interval-ms=")) {
      options.pollIntervalMs = parsePositiveInteger("--poll-interval-ms", arg.slice("--poll-interval-ms=".length));
      continue;
    }
    if (arg === "--wait-timeout-ms") {
      options.waitTimeoutMs = parsePositiveInteger(arg, next);
      index += 1;
      continue;
    }
    if (arg.startsWith("--wait-timeout-ms=")) {
      options.waitTimeoutMs = parsePositiveInteger("--wait-timeout-ms", arg.slice("--wait-timeout-ms=".length));
      continue;
    }
    throw new Error(`unsupported ssh playwright option: ${arg}`);
  }
  return options;
}

function playwrightHelp(): Record<string, unknown> {
  return {
    ok: true,
    command: "ssh playwright",
    usage: [
      "trans <providerId> playwright [--local-dir /tmp] <<'PW'",
      "playwright-cli screenshot https://example.com \"$UNIDESK_PLAYWRIGHT_SCREENSHOT\" --full-page",
      "PW",
      "trans <providerId>:/absolute/workspace playwright [--local-dir /tmp] <<'PW'",
      "playwright-cli screenshot http://127.0.0.1:18081/ \"$UNIDESK_PLAYWRIGHT_SCREENSHOT\"",
      "PW",
    ],
    behavior: [
      "Reads a POSIX shell heredoc from stdin and runs it on the target route.",
      "Prepends a temporary playwright-cli wrapper to PATH. The wrapper prefers ./scripts/playwright-cli.ts in the route workspace or known UniDesk host workspaces, then ~/.agents/skills/playwright*/scripts/playwright-cli.ts, then a playwright-cli binary.",
      "Submits the remote script as a background job and polls short status commands for the manifest, so multi-step Playwright flows do not occupy one SSH connection past the 60s trans runtime limit.",
      "Sets UNIDESK_PLAYWRIGHT_REMOTE_DIR and UNIDESK_PLAYWRIGHT_SCREENSHOT. Files created under that remote dir with image/pdf extensions are downloaded to --local-dir with byte and sha256 verification.",
    ],
    options: {
      "--local-dir <path>": "Local directory for returned artifacts. Default: /tmp.",
      "--remote-dir <absolute-path>": "Remote artifact directory. Default: /tmp/unidesk-playwright-<provider>-<timestamp>-<id>.",
      "--keep-remote": "Do not remove the remote artifact directory after the transfer.",
      "--inactivity-timeout-ms <n>": "Forwarded to verified artifact download when large screenshots are returned.",
      "--wait-timeout-ms <n>": "Maximum wall-clock time to wait for the remote Playwright job. Default: 180000.",
      "--poll-interval-ms <n>": "Short-query polling interval for the remote job. Default: 2000.",
    },
  };
}

function requireValue(name: string, value: string | undefined): string {
  if (value === undefined || value.length === 0) throw new Error(`ssh playwright ${name} requires a non-empty value`);
  return value;
}

function requireAbsoluteRemotePath(name: string, value: string | undefined): string {
  const pathValue = requireValue(name, value);
  if (!pathValue.startsWith("/")) throw new Error(`ssh playwright ${name} must be an absolute POSIX path`);
  return pathValue;
}

function parsePositiveInteger(name: string, value: string | undefined): number {
  const parsed = Number(requireValue(name, value));
  if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`ssh playwright ${name} must be a positive integer`);
  return parsed;
}

async function readAllStdin(): Promise<string> {
  const chunks: Buffer[] = [];
  for await (const chunk of process.stdin) {
    chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
  }
  return Buffer.concat(chunks).toString("utf8");
}

async function pollRemoteManifest(
  route: ParsedSshRoute,
  executor: SshRemoteCommandExecutor,
  builders: SshFileTransferCommandBuilders,
  remoteDir: string,
  runId: string,
  options: SshPlaywrightOptions,
): Promise<RemotePlaywrightManifest> {
  const deadline = Date.now() + options.waitTimeoutMs;
  let lastStatus: SshCaptureResult | null = null;
  while (Date.now() <= deadline) {
    const command = builders.buildRouteCommand(route, ["sh", "-c", remotePlaywrightStatusScript(), "unidesk-playwright-status", remoteDir]);
    const status = await executor.runRemoteCommand(command);
    lastStatus = status;
    if (status.exitCode === 0 && status.stdout.includes(manifestEnd)) return parseRemoteManifest(status, remoteDir, runId);
    if (status.exitCode !== 0 && !/status\t(?:pending|running)/u.test(status.stdout)) {
      throw new Error(`ssh playwright status failed: exitCode=${status.exitCode}; remoteDir=${remoteDir}; runId=${runId}; stdoutTail=${JSON.stringify(status.stdout.slice(-1000))}; stderrTail=${JSON.stringify(status.stderr.slice(-1000))}`);
    }
    await sleep(options.pollIntervalMs);
  }
  throw new Error(`ssh playwright timed out waiting for remote job after ${options.waitTimeoutMs}ms; remoteDir=${remoteDir}; runId=${runId}; lastStdoutTail=${JSON.stringify(lastStatus?.stdout.slice(-1000) ?? "")}; lastStderrTail=${JSON.stringify(lastStatus?.stderr.slice(-1000) ?? "")}`);
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
}

function isRecoverableSubmitTimeout(submit: SshCaptureResult): boolean {
  if (submit.exitCode === 124) return true;
  const combined = `${submit.stdout}\n${submit.stderr}`;
  return /(?:timed?\s*out|timeout|60s|exitCode=124|signal\s+TERM|signal\s+KILL)/iu.test(combined);
}

function remotePlaywrightSubmitScript(remoteDir: string): string {
  const runner = Buffer.from(remotePlaywrightRunnerScript(remoteDir), "utf8").toString("base64");
  return [
    "set -eu",
    `remote_dir=${shellQuote(remoteDir)}`,
    'mkdir -p -- "$remote_dir"',
    'user_script="$remote_dir/user-script.sh"',
    'runner_script="$remote_dir/runner.sh"',
    'pid_file="$remote_dir/pid"',
    'submit_stdout="$remote_dir/submit.out"',
    'submit_stderr="$remote_dir/submit.err"',
    'if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" >/dev/null 2>&1; then',
    '  printf "status\\trunning\\nremote_dir\\t%s\\npid\\t%s\\n" "$remote_dir" "$(cat "$pid_file")"',
    "  exit 0",
    "fi",
    'cat > "$user_script"',
    'chmod 700 "$user_script"',
    `printf %s ${shellQuote(runner)} | base64 -d >"$runner_script"`,
    'chmod 700 "$runner_script"',
    'nohup sh "$runner_script" "$user_script" >"$submit_stdout" 2>"$submit_stderr" < /dev/null &',
    'pid=$!',
    'printf "%s\\n" "$pid" >"$pid_file"',
    'printf "status\\tsubmitted\\nremote_dir\\t%s\\npid\\t%s\\n" "$remote_dir" "$pid"',
  ].join("\n");
}

function remotePlaywrightStatusScript(): string {
  return [
    "set -eu",
    'remote_dir="$1"',
    'manifest="$remote_dir/manifest.tsv"',
    'pid_file="$remote_dir/pid"',
    'if [ -f "$manifest" ]; then cat "$manifest"; exit 0; fi',
    'if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" >/dev/null 2>&1; then',
    '  printf "status\\trunning\\nremote_dir\\t%s\\npid\\t%s\\n" "$remote_dir" "$(cat "$pid_file")"',
    "  exit 1",
    "fi",
    'if [ -f "$pid_file" ]; then',
    '  printf "status\\texited-without-manifest\\nremote_dir\\t%s\\npid\\t%s\\n" "$remote_dir" "$(cat "$pid_file")"',
    '  if [ -f "$remote_dir/submit.err" ]; then tail -c 1000 "$remote_dir/submit.err" >&2; fi',
    '  if [ -f "$remote_dir/stderr.log" ]; then tail -c 1000 "$remote_dir/stderr.log" >&2; fi',
    "  exit 2",
    "fi",
    'printf "status\\tpending\\nremote_dir\\t%s\\n" "$remote_dir"',
    'if [ -f "$remote_dir/submit.err" ]; then tail -c 1000 "$remote_dir/submit.err" >&2; fi',
    'if [ -f "$remote_dir/stderr.log" ]; then tail -c 1000 "$remote_dir/stderr.log" >&2; fi',
    "exit 1",
  ].join("\n");
}

function remotePlaywrightRunnerScript(remoteDir: string): string {
  return [
    "set -eu",
    `remote_dir=${shellQuote(remoteDir)}`,
    'if [ "$#" -lt 1 ]; then printf "missing user script path\\n" >&2; exit 2; fi',
    'user_script="$1"',
    'mkdir -p -- "$remote_dir/bin"',
    'stdout_file="$remote_dir/stdout.log"',
    'stderr_file="$remote_dir/stderr.log"',
    'artifacts_file="$remote_dir/artifacts.tsv"',
    'manifest_file="$remote_dir/manifest.tsv"',
    "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",
    "}",
    "resolve_playwright_cli() {",
    "  if command -v bun >/dev/null 2>&1; then",
    "    if [ -f ./scripts/playwright-cli.ts ]; then",
    "      UNIDESK_PLAYWRIGHT_CLI_MODE=repo; UNIDESK_PLAYWRIGHT_CLI_CWD=$(pwd); UNIDESK_PLAYWRIGHT_CLI_BIN=; return 0",
    "    fi",
    "    for dir in \"$HOME/workspace/unidesk-dev\" \"$HOME/unidesk\" \"/home/ubuntu/workspace/unidesk-dev\" \"/root/unidesk\"; do",
    "      if [ -f \"$dir/scripts/playwright-cli.ts\" ]; then",
    "        UNIDESK_PLAYWRIGHT_CLI_MODE=repo; UNIDESK_PLAYWRIGHT_CLI_CWD=$dir; UNIDESK_PLAYWRIGHT_CLI_BIN=; return 0",
    "      fi",
    "    done",
    "  fi",
    "  for dir in \"$HOME/.agents/skills/playwright\" \"$HOME/.agents/skills/playwright-cli\" \"$HOME/.codex/skills/playwright\" \"$HOME/.codex/skills/playwright-cli\"; do",
    "    if command -v bun >/dev/null 2>&1 && [ -f \"$dir/scripts/playwright-cli.ts\" ]; then",
    "      UNIDESK_PLAYWRIGHT_CLI_MODE=skill; UNIDESK_PLAYWRIGHT_CLI_CWD=$dir; UNIDESK_PLAYWRIGHT_CLI_BIN=; return 0",
    "    fi",
    "  done",
    "  if command -v playwright-cli >/dev/null 2>&1; then",
    "    UNIDESK_PLAYWRIGHT_CLI_MODE=bin; UNIDESK_PLAYWRIGHT_CLI_CWD=; UNIDESK_PLAYWRIGHT_CLI_BIN=$(command -v playwright-cli); return 0",
    "  fi",
    "  return 42",
    "}",
    "if ! resolve_playwright_cli; then",
    "  printf 'unable to find playwright-cli wrapper: expected ./scripts/playwright-cli.ts, ~/.agents/skills/playwright*/scripts/playwright-cli.ts, or playwright-cli in PATH\\n' >&2",
    "  exit 42",
    "fi",
    "export UNIDESK_PLAYWRIGHT_CLI_MODE UNIDESK_PLAYWRIGHT_CLI_CWD UNIDESK_PLAYWRIGHT_CLI_BIN",
    'cat > "$remote_dir/bin/playwright-cli" <<\'UNIDESK_PLAYWRIGHT_WRAPPER\'',
    "#!/bin/sh",
    "set -eu",
    "case \"${UNIDESK_PLAYWRIGHT_CLI_MODE:-}\" in",
    "  repo|skill) cd \"$UNIDESK_PLAYWRIGHT_CLI_CWD\"; exec bun scripts/playwright-cli.ts \"$@\" ;;",
    "  bin) exec \"$UNIDESK_PLAYWRIGHT_CLI_BIN\" \"$@\" ;;",
    "  *) printf 'UNIDESK playwright wrapper is not resolved\\n' >&2; exit 42 ;;",
    "esac",
    "UNIDESK_PLAYWRIGHT_WRAPPER",
    'chmod 700 "$remote_dir/bin/playwright-cli"',
    "if command -v npx >/dev/null 2>&1; then",
    "  cat > \"$remote_dir/bin/npx.cmd\" <<'UNIDESK_NPX_CMD_WRAPPER'",
    "#!/bin/sh",
    "exec npx \"$@\"",
    "UNIDESK_NPX_CMD_WRAPPER",
    "  chmod 700 \"$remote_dir/bin/npx.cmd\"",
    "fi",
    'export PATH="$remote_dir/bin:$PATH"',
    'export UNIDESK_PLAYWRIGHT_REMOTE_DIR="$remote_dir"',
    'export UNIDESK_PLAYWRIGHT_SCREENSHOT="$remote_dir/screenshot.png"',
    "set +e",
    'sh "$user_script" >"$stdout_file" 2>"$stderr_file"',
    "user_rc=$?",
    ': > "$artifacts_file"',
    'find "$remote_dir" -type f | while IFS= read -r file; do',
    '  case "$file" in "$user_script"|"$stdout_file"|"$stderr_file"|"$artifacts_file"|"$remote_dir/bin/"*) continue ;; esac',
    '  case "$file" in *.png|*.jpg|*.jpeg|*.webp|*.pdf)',
    '    bytes=$(wc -c < "$file" | tr -d "[:space:]")',
    '    digest=$(sha256_file "$file")',
    '    printf "artifact\\t%s\\t%s\\t%s\\n" "$bytes" "$digest" "$file" >> "$artifacts_file"',
    "    ;;",
    "  esac",
    "done",
    "b64_tail() { if [ -f \"$1\" ]; then tail -c 4000 \"$1\" | base64 | tr -d '\\n'; fi; }",
    `{`,
    `printf '%s\\n' ${shellQuote(manifestBegin)}`,
    "printf 'run_id\\t%s\\n' \"${remote_dir##*/}\"",
    "printf 'exit_code\\t%s\\n' \"$user_rc\"",
    "printf 'remote_dir\\t%s\\n' \"$remote_dir\"",
    "printf 'stdout_path\\t%s\\n' \"$stdout_file\"",
    "printf 'stderr_path\\t%s\\n' \"$stderr_file\"",
    "printf 'cli_mode\\t%s\\n' \"$UNIDESK_PLAYWRIGHT_CLI_MODE\"",
    "printf 'cli_cwd\\t%s\\n' \"$UNIDESK_PLAYWRIGHT_CLI_CWD\"",
    "printf 'cli_bin\\t%s\\n' \"$UNIDESK_PLAYWRIGHT_CLI_BIN\"",
    "printf 'default_screenshot\\t%s\\n' \"$UNIDESK_PLAYWRIGHT_SCREENSHOT\"",
    "printf 'stdout_tail_b64\\t%s\\n' \"$(b64_tail \"$stdout_file\")\"",
    "printf 'stderr_tail_b64\\t%s\\n' \"$(b64_tail \"$stderr_file\")\"",
    'cat "$artifacts_file"',
    `printf '%s\\n' ${shellQuote(manifestEnd)}`,
    `} > "$manifest_file"`,
    'exit "$user_rc"',
  ].join("\n");
}

function parseRemoteManifest(result: SshCaptureResult, fallbackRemoteDir: string, fallbackRunId: string): RemotePlaywrightManifest {
  const start = result.stdout.indexOf(manifestBegin);
  const end = result.stdout.indexOf(manifestEnd, start < 0 ? 0 : start);
  if (start < 0 || end < 0) {
    throw new Error(`ssh playwright did not emit a manifest; exitCode=${result.exitCode}; stdoutTail=${JSON.stringify(result.stdout.slice(-1000))}; stderrTail=${JSON.stringify(result.stderr.slice(-1000))}`);
  }
  const body = result.stdout.slice(start + manifestBegin.length, end).trim();
  const manifest: RemotePlaywrightManifest = {
    runId: fallbackRunId,
    exitCode: result.exitCode,
    remoteDir: fallbackRemoteDir,
    stdoutPath: join(fallbackRemoteDir, "stdout.log"),
    stderrPath: join(fallbackRemoteDir, "stderr.log"),
    cliMode: null,
    cliCwd: null,
    cliBin: null,
    defaultScreenshot: null,
    stdoutTail: "",
    stderrTail: "",
    artifacts: [],
  };
  for (const rawLine of body.split(/\r?\n/u)) {
    const line = rawLine.trimEnd();
    if (line.length === 0) continue;
    const parts = line.split("\t");
    const key = parts[0] ?? "";
    if (key === "artifact") {
      const bytes = Number(parts[1] ?? "");
      const sha256 = parts[2] ?? "";
      const remotePath = parts.slice(3).join("\t");
      if (remotePath.length > 0) {
        manifest.artifacts.push({
          remotePath,
          bytes: Number.isSafeInteger(bytes) && bytes >= 0 ? bytes : null,
          sha256: /^[0-9a-f]{64}$/u.test(sha256) ? sha256 : null,
        });
      }
      continue;
    }
    const value = parts.slice(1).join("\t");
    if (key === "run_id" && value.length > 0) manifest.runId = value;
    else if (key === "exit_code") {
      const exitCode = Number(value);
      manifest.exitCode = Number.isInteger(exitCode) ? exitCode : result.exitCode;
    } else if (key === "remote_dir" && value.length > 0) manifest.remoteDir = value;
    else if (key === "stdout_path" && value.length > 0) manifest.stdoutPath = value;
    else if (key === "stderr_path" && value.length > 0) manifest.stderrPath = value;
    else if (key === "cli_mode") manifest.cliMode = value || null;
    else if (key === "cli_cwd") manifest.cliCwd = value || null;
    else if (key === "cli_bin") manifest.cliBin = value || null;
    else if (key === "default_screenshot") manifest.defaultScreenshot = value || null;
    else if (key === "stdout_tail_b64") manifest.stdoutTail = decodeBase64(value);
    else if (key === "stderr_tail_b64") manifest.stderrTail = decodeBase64(value);
  }
  return manifest;
}

async function cleanupRemoteDir(
  route: ParsedSshRoute,
  executor: SshRemoteCommandExecutor,
  builders: SshFileTransferCommandBuilders,
  remoteDir: string,
  keepRemote: boolean,
): Promise<Record<string, unknown>> {
  if (keepRemote) return { attempted: false, kept: true, remoteDir };
  const command = builders.buildRouteCommand(route, ["sh", "-c", "rm -rf -- \"$1\"", "unidesk-playwright-cleanup", remoteDir]);
  const result = await executor.runRemoteCommand(command);
  return {
    attempted: true,
    kept: false,
    remoteDir,
    exitCode: result.exitCode,
    ok: result.exitCode === 0,
    stderrTail: result.stderr.slice(-1000),
  };
}

function decodeBase64(value: string): string {
  if (value.length === 0) return "";
  try {
    return Buffer.from(value, "base64").toString("utf8");
  } catch {
    return "";
  }
}

function safePathSegment(value: string): string {
  const cleaned = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
  return cleaned.length > 0 ? cleaned.slice(0, 120) : "artifact";
}

function shellQuote(value: string): string {
  return `'${value.replace(/'/g, "'\\''")}'`;
}
