import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import type {
  SystemRunApprovalFileOperand,
  SystemRunApprovalPlan,
} from "../infra/exec-approvals.js";
import { resolveCommandResolutionFromArgv } from "../infra/exec-command-resolution.js";
import { isInterpreterLikeSafeBin } from "../infra/exec-safe-bin-runtime-policy.js";
import {
  POSIX_SHELL_WRAPPERS,
  normalizeExecutableToken,
  unwrapKnownDispatchWrapperInvocation,
  unwrapKnownShellMultiplexerInvocation,
} from "../infra/exec-wrapper-resolution.js";
import { sameFileIdentity } from "../infra/file-identity.js";
import {
  POSIX_INLINE_COMMAND_FLAGS,
  resolveInlineCommandMatch,
} from "../infra/shell-inline-command.js";
import { formatExecCommand, resolveSystemRunCommandRequest } from "../infra/system-run-command.js";
import { splitShellArgs } from "../utils/shell-argv.js";

export type ApprovedCwdSnapshot = {
  cwd: string;
  stat: fs.Stats;
};

const MUTABLE_ARGV1_INTERPRETER_PATTERNS = [
  /^(?:node|nodejs)$/,
  /^perl$/,
  /^php$/,
  /^python(?:\d+(?:\.\d+)*)?$/,
  /^ruby$/,
] as const;

const GENERIC_MUTABLE_SCRIPT_RUNNERS = new Set([
  "esno",
  "jiti",
  "ts-node",
  "ts-node-esm",
  "tsx",
  "vite-node",
]);

const BUN_SUBCOMMANDS = new Set([
  "add",
  "audit",
  "completions",
  "create",
  "exec",
  "help",
  "init",
  "install",
  "link",
  "outdated",
  "patch",
  "pm",
  "publish",
  "remove",
  "repl",
  "run",
  "test",
  "unlink",
  "update",
  "upgrade",
  "x",
]);

const BUN_OPTIONS_WITH_VALUE = new Set([
  "--backend",
  "--bunfig",
  "--conditions",
  "--config",
  "--console-depth",
  "--cwd",
  "--define",
  "--elide-lines",
  "--env-file",
  "--extension-order",
  "--filter",
  "--hot",
  "--inspect",
  "--inspect-brk",
  "--inspect-wait",
  "--install",
  "--jsx-factory",
  "--jsx-fragment",
  "--jsx-import-source",
  "--loader",
  "--origin",
  "--port",
  "--preload",
  "--smol",
  "--tsconfig-override",
  "-c",
  "-e",
  "-p",
  "-r",
]);

const DENO_RUN_OPTIONS_WITH_VALUE = new Set([
  "--cached-only",
  "--cert",
  "--config",
  "--env-file",
  "--ext",
  "--harmony-import-attributes",
  "--import-map",
  "--inspect",
  "--inspect-brk",
  "--inspect-wait",
  "--location",
  "--log-level",
  "--lock",
  "--node-modules-dir",
  "--no-check",
  "--preload",
  "--reload",
  "--seed",
  "--strace-ops",
  "--unstable-bare-node-builtins",
  "--v8-flags",
  "--watch",
  "--watch-exclude",
  "-L",
]);

const NODE_OPTIONS_WITH_FILE_VALUE = new Set([
  "-r",
  "--experimental-loader",
  "--import",
  "--loader",
  "--require",
]);

const RUBY_UNSAFE_APPROVAL_FLAGS = new Set(["-I", "-r", "--require"]);

const POSIX_SHELL_OPTIONS_WITH_VALUE = new Set([
  "--init-file",
  "--rcfile",
  "--startup-script",
  "-o",
]);

const NPM_EXEC_OPTIONS_WITH_VALUE = new Set([
  "--cache",
  "--package",
  "--prefix",
  "--script-shell",
  "--userconfig",
  "--workspace",
  "-p",
  "-w",
]);

const NPM_EXEC_FLAG_OPTIONS = new Set([
  "--no",
  "--quiet",
  "--ws",
  "--workspaces",
  "--yes",
  "-q",
  "-y",
]);

type FileOperandCollection = {
  hits: number[];
  sawOptionValueFile: boolean;
};

function normalizeString(value: unknown): string | null {
  if (typeof value !== "string") {
    return null;
  }
  const trimmed = value.trim();
  return trimmed ? trimmed : null;
}

function pathComponentsFromRootSync(targetPath: string): string[] {
  const absolute = path.resolve(targetPath);
  const parts: string[] = [];
  let cursor = absolute;
  while (true) {
    parts.unshift(cursor);
    const parent = path.dirname(cursor);
    if (parent === cursor) {
      return parts;
    }
    cursor = parent;
  }
}

function isWritableByCurrentProcessSync(candidate: string): boolean {
  try {
    fs.accessSync(candidate, fs.constants.W_OK);
    return true;
  } catch {
    return false;
  }
}

function hasMutableSymlinkPathComponentSync(targetPath: string): boolean {
  for (const component of pathComponentsFromRootSync(targetPath)) {
    try {
      if (!fs.lstatSync(component).isSymbolicLink()) {
        continue;
      }
      const parentDir = path.dirname(component);
      if (isWritableByCurrentProcessSync(parentDir)) {
        return true;
      }
    } catch {
      return true;
    }
  }
  return false;
}

function shouldPinExecutableForApproval(params: {
  shellCommand: string | null;
  wrapperChain: string[] | undefined;
}): boolean {
  if (params.shellCommand !== null) {
    return false;
  }
  return (params.wrapperChain?.length ?? 0) === 0;
}

function hashFileContentsSync(filePath: string): string {
  return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
}

function looksLikePathToken(token: string): boolean {
  return (
    token.startsWith(".") ||
    token.startsWith("/") ||
    token.startsWith("\\") ||
    token.includes("/") ||
    token.includes("\\") ||
    path.extname(token).length > 0
  );
}

function resolvesToExistingFileSync(rawOperand: string, cwd: string | undefined): boolean {
  if (!rawOperand) {
    return false;
  }
  try {
    return fs.statSync(path.resolve(cwd ?? process.cwd(), rawOperand)).isFile();
  } catch {
    return false;
  }
}

function unwrapArgvForMutableOperand(argv: string[]): { argv: string[]; baseIndex: number } {
  let current = argv;
  let baseIndex = 0;
  while (true) {
    const dispatchUnwrap = unwrapKnownDispatchWrapperInvocation(current);
    if (dispatchUnwrap.kind === "unwrapped") {
      baseIndex += current.length - dispatchUnwrap.argv.length;
      current = dispatchUnwrap.argv;
      continue;
    }
    const shellMultiplexerUnwrap = unwrapKnownShellMultiplexerInvocation(current);
    if (shellMultiplexerUnwrap.kind === "unwrapped") {
      baseIndex += current.length - shellMultiplexerUnwrap.argv.length;
      current = shellMultiplexerUnwrap.argv;
      continue;
    }
    const packageManagerUnwrap = unwrapKnownPackageManagerExecInvocation(current);
    if (packageManagerUnwrap) {
      baseIndex += current.length - packageManagerUnwrap.length;
      current = packageManagerUnwrap;
      continue;
    }
    return { argv: current, baseIndex };
  }
}

function unwrapKnownPackageManagerExecInvocation(argv: string[]): string[] | null {
  const executable = normalizePackageManagerExecToken(argv[0] ?? "");
  switch (executable) {
    case "npm":
      return unwrapNpmExecInvocation(argv);
    case "npx":
    case "bunx":
      return unwrapDirectPackageExecInvocation(argv);
    case "pnpm":
      return unwrapPnpmExecInvocation(argv);
    default:
      return null;
  }
}

function normalizePackageManagerExecToken(token: string): string {
  const normalized = normalizeExecutableToken(token);
  if (!normalized) {
    return normalized;
  }
  return normalized.replace(/\.(?:c|m)?js$/i, "");
}

function unwrapPnpmExecInvocation(argv: string[]): string[] | null {
  let idx = 1;
  while (idx < argv.length) {
    const token = argv[idx]?.trim() ?? "";
    if (!token) {
      idx += 1;
      continue;
    }
    if (token === "--") {
      idx += 1;
      continue;
    }
    if (!token.startsWith("-")) {
      if (token !== "exec" || idx + 1 >= argv.length) {
        return null;
      }
      const tail = argv.slice(idx + 1);
      return tail[0] === "--" ? (tail.length > 1 ? tail.slice(1) : null) : tail;
    }
    if ((token === "-C" || token === "--dir" || token === "--filter") && !token.includes("=")) {
      idx += 2;
      continue;
    }
    idx += 1;
  }
  return null;
}

function unwrapDirectPackageExecInvocation(argv: string[]): string[] | null {
  let idx = 1;
  while (idx < argv.length) {
    const token = argv[idx]?.trim() ?? "";
    if (!token) {
      idx += 1;
      continue;
    }
    if (!token.startsWith("-")) {
      return argv.slice(idx);
    }
    const [flag] = token.toLowerCase().split("=", 2);
    if (flag === "-c" || flag === "--call") {
      return null;
    }
    if (NPM_EXEC_OPTIONS_WITH_VALUE.has(flag)) {
      idx += token.includes("=") ? 1 : 2;
      continue;
    }
    if (NPM_EXEC_FLAG_OPTIONS.has(flag)) {
      idx += 1;
      continue;
    }
    return null;
  }
  return null;
}

function unwrapNpmExecInvocation(argv: string[]): string[] | null {
  let idx = 1;
  while (idx < argv.length) {
    const token = argv[idx]?.trim() ?? "";
    if (!token) {
      idx += 1;
      continue;
    }
    if (!token.startsWith("-")) {
      if (token !== "exec") {
        return null;
      }
      idx += 1;
      break;
    }
    if (
      (token === "-C" || token === "--prefix" || token === "--userconfig") &&
      !token.includes("=")
    ) {
      idx += 2;
      continue;
    }
    idx += 1;
  }
  if (idx >= argv.length) {
    return null;
  }
  const tail = argv.slice(idx);
  if (tail[0] === "--") {
    return tail.length > 1 ? tail.slice(1) : null;
  }
  return unwrapDirectPackageExecInvocation(["npx", ...tail]);
}

function resolvePosixShellScriptOperandIndex(argv: string[]): number | null {
  if (
    resolveInlineCommandMatch(argv, POSIX_INLINE_COMMAND_FLAGS, {
      allowCombinedC: true,
    }).valueTokenIndex !== null
  ) {
    return null;
  }
  let afterDoubleDash = false;
  for (let i = 1; i < argv.length; i += 1) {
    const token = argv[i]?.trim() ?? "";
    if (!token) {
      continue;
    }
    if (token === "-") {
      return null;
    }
    if (!afterDoubleDash && token === "--") {
      afterDoubleDash = true;
      continue;
    }
    if (!afterDoubleDash && token === "-s") {
      return null;
    }
    if (!afterDoubleDash && token.startsWith("-")) {
      const [flag] = token.toLowerCase().split("=", 2);
      if (POSIX_SHELL_OPTIONS_WITH_VALUE.has(flag)) {
        if (!token.includes("=")) {
          i += 1;
        }
        continue;
      }
      continue;
    }
    return i;
  }
  return null;
}

function resolveOptionFilteredFileOperandIndex(params: {
  argv: string[];
  startIndex: number;
  cwd: string | undefined;
  optionsWithValue?: ReadonlySet<string>;
}): number | null {
  let afterDoubleDash = false;
  for (let i = params.startIndex; i < params.argv.length; i += 1) {
    const token = params.argv[i]?.trim() ?? "";
    if (!token) {
      continue;
    }
    if (afterDoubleDash) {
      return resolvesToExistingFileSync(token, params.cwd) ? i : null;
    }
    if (token === "--") {
      afterDoubleDash = true;
      continue;
    }
    if (token === "-") {
      return null;
    }
    if (token.startsWith("-")) {
      if (!token.includes("=") && params.optionsWithValue?.has(token)) {
        i += 1;
      }
      continue;
    }
    return resolvesToExistingFileSync(token, params.cwd) ? i : null;
  }
  return null;
}

function resolveOptionFilteredPositionalIndex(params: {
  argv: string[];
  startIndex: number;
  optionsWithValue?: ReadonlySet<string>;
}): number | null {
  let afterDoubleDash = false;
  for (let i = params.startIndex; i < params.argv.length; i += 1) {
    const token = params.argv[i]?.trim() ?? "";
    if (!token) {
      continue;
    }
    if (afterDoubleDash) {
      return i;
    }
    if (token === "--") {
      afterDoubleDash = true;
      continue;
    }
    if (token === "-") {
      return null;
    }
    if (token.startsWith("-")) {
      if (!token.includes("=") && params.optionsWithValue?.has(token)) {
        i += 1;
      }
      continue;
    }
    return i;
  }
  return null;
}

function collectExistingFileOperandIndexes(params: {
  argv: string[];
  startIndex: number;
  cwd: string | undefined;
  optionsWithFileValue?: ReadonlySet<string>;
}): FileOperandCollection {
  let afterDoubleDash = false;
  const hits: number[] = [];
  for (let i = params.startIndex; i < params.argv.length; i += 1) {
    const token = params.argv[i]?.trim() ?? "";
    if (!token) {
      continue;
    }
    if (afterDoubleDash) {
      if (resolvesToExistingFileSync(token, params.cwd)) {
        hits.push(i);
      }
      continue;
    }
    if (token === "--") {
      afterDoubleDash = true;
      continue;
    }
    if (token === "-") {
      return { hits: [], sawOptionValueFile: false };
    }
    if (token.startsWith("-")) {
      const [flag, inlineValue] = token.split("=", 2);
      if (params.optionsWithFileValue?.has(flag.toLowerCase())) {
        if (inlineValue && resolvesToExistingFileSync(inlineValue, params.cwd)) {
          hits.push(i);
          return { hits, sawOptionValueFile: true };
        }
        const nextToken = params.argv[i + 1]?.trim() ?? "";
        if (!inlineValue && nextToken && resolvesToExistingFileSync(nextToken, params.cwd)) {
          hits.push(i + 1);
          return { hits, sawOptionValueFile: true };
        }
      }
      continue;
    }
    if (resolvesToExistingFileSync(token, params.cwd)) {
      hits.push(i);
    }
  }
  return { hits, sawOptionValueFile: false };
}

function resolveGenericInterpreterScriptOperandIndex(params: {
  argv: string[];
  cwd: string | undefined;
  optionsWithFileValue?: ReadonlySet<string>;
}): number | null {
  const collection = collectExistingFileOperandIndexes({
    argv: params.argv,
    startIndex: 1,
    cwd: params.cwd,
    optionsWithFileValue: params.optionsWithFileValue,
  });
  if (collection.sawOptionValueFile) {
    return null;
  }
  return collection.hits.length === 1 ? collection.hits[0] : null;
}

function resolveBunScriptOperandIndex(params: {
  argv: string[];
  cwd: string | undefined;
}): number | null {
  const directIndex = resolveOptionFilteredPositionalIndex({
    argv: params.argv,
    startIndex: 1,
    optionsWithValue: BUN_OPTIONS_WITH_VALUE,
  });
  if (directIndex === null) {
    return null;
  }
  const directToken = params.argv[directIndex]?.trim() ?? "";
  if (directToken === "run") {
    return resolveOptionFilteredFileOperandIndex({
      argv: params.argv,
      startIndex: directIndex + 1,
      cwd: params.cwd,
      optionsWithValue: BUN_OPTIONS_WITH_VALUE,
    });
  }
  if (BUN_SUBCOMMANDS.has(directToken)) {
    return null;
  }
  if (!looksLikePathToken(directToken)) {
    return null;
  }
  return directIndex;
}

function resolveDenoRunScriptOperandIndex(params: {
  argv: string[];
  cwd: string | undefined;
}): number | null {
  if ((params.argv[1]?.trim() ?? "") !== "run") {
    return null;
  }
  return resolveOptionFilteredFileOperandIndex({
    argv: params.argv,
    startIndex: 2,
    cwd: params.cwd,
    optionsWithValue: DENO_RUN_OPTIONS_WITH_VALUE,
  });
}

function hasRubyUnsafeApprovalFlag(argv: string[]): boolean {
  let afterDoubleDash = false;
  for (let i = 1; i < argv.length; i += 1) {
    const token = argv[i]?.trim() ?? "";
    if (!token) {
      continue;
    }
    if (afterDoubleDash) {
      return false;
    }
    if (token === "--") {
      afterDoubleDash = true;
      continue;
    }
    if (token === "-I" || token === "-r") {
      return true;
    }
    if (token.startsWith("-I") || token.startsWith("-r")) {
      return true;
    }
    if (RUBY_UNSAFE_APPROVAL_FLAGS.has(token.toLowerCase())) {
      return true;
    }
  }
  return false;
}

function isMutableScriptRunner(executable: string): boolean {
  return GENERIC_MUTABLE_SCRIPT_RUNNERS.has(executable) || isInterpreterLikeSafeBin(executable);
}

function resolveMutableFileOperandIndex(argv: string[], cwd: string | undefined): number | null {
  const unwrapped = unwrapArgvForMutableOperand(argv);
  const executable = normalizeExecutableToken(unwrapped.argv[0] ?? "");
  if (!executable) {
    return null;
  }
  if ((POSIX_SHELL_WRAPPERS as ReadonlySet<string>).has(executable)) {
    const shellIndex = resolvePosixShellScriptOperandIndex(unwrapped.argv);
    return shellIndex === null ? null : unwrapped.baseIndex + shellIndex;
  }
  if (MUTABLE_ARGV1_INTERPRETER_PATTERNS.some((pattern) => pattern.test(executable))) {
    const operand = unwrapped.argv[1]?.trim() ?? "";
    if (operand && operand !== "-" && !operand.startsWith("-")) {
      return unwrapped.baseIndex + 1;
    }
  }
  if (executable === "bun") {
    const bunIndex = resolveBunScriptOperandIndex({
      argv: unwrapped.argv,
      cwd,
    });
    if (bunIndex !== null) {
      return unwrapped.baseIndex + bunIndex;
    }
  }
  if (executable === "deno") {
    const denoIndex = resolveDenoRunScriptOperandIndex({
      argv: unwrapped.argv,
      cwd,
    });
    if (denoIndex !== null) {
      return unwrapped.baseIndex + denoIndex;
    }
  }
  if (executable === "ruby" && hasRubyUnsafeApprovalFlag(unwrapped.argv)) {
    return null;
  }
  if (!isMutableScriptRunner(executable)) {
    return null;
  }
  const genericIndex = resolveGenericInterpreterScriptOperandIndex({
    argv: unwrapped.argv,
    cwd,
    optionsWithFileValue:
      executable === "node" || executable === "nodejs" ? NODE_OPTIONS_WITH_FILE_VALUE : undefined,
  });
  return genericIndex === null ? null : unwrapped.baseIndex + genericIndex;
}

function shellPayloadNeedsStableBinding(shellCommand: string, cwd: string | undefined): boolean {
  const argv = splitShellArgs(shellCommand);
  if (!argv || argv.length === 0) {
    return false;
  }
  const snapshot = resolveMutableFileOperandSnapshotSync({
    argv,
    cwd,
    shellCommand: null,
  });
  if (!snapshot.ok) {
    return true;
  }
  if (snapshot.snapshot) {
    return true;
  }
  const firstToken = argv[0]?.trim() ?? "";
  return resolvesToExistingFileSync(firstToken, cwd);
}

function requiresStableInterpreterApprovalBindingWithShellCommand(params: {
  argv: string[];
  shellCommand: string | null;
  cwd: string | undefined;
}): boolean {
  if (params.shellCommand !== null) {
    return shellPayloadNeedsStableBinding(params.shellCommand, params.cwd);
  }
  const unwrapped = unwrapArgvForMutableOperand(params.argv);
  const executable = normalizeExecutableToken(unwrapped.argv[0] ?? "");
  if (!executable) {
    return false;
  }
  if ((POSIX_SHELL_WRAPPERS as ReadonlySet<string>).has(executable)) {
    return false;
  }
  return isMutableScriptRunner(executable);
}

export function resolveMutableFileOperandSnapshotSync(params: {
  argv: string[];
  cwd: string | undefined;
  shellCommand: string | null;
}): { ok: true; snapshot: SystemRunApprovalFileOperand | null } | { ok: false; message: string } {
  const argvIndex = resolveMutableFileOperandIndex(params.argv, params.cwd);
  if (argvIndex === null) {
    if (
      requiresStableInterpreterApprovalBindingWithShellCommand({
        argv: params.argv,
        shellCommand: params.shellCommand,
        cwd: params.cwd,
      })
    ) {
      return {
        ok: false,
        message: "SYSTEM_RUN_DENIED: approval cannot safely bind this interpreter/runtime command",
      };
    }
    return { ok: true, snapshot: null };
  }
  const rawOperand = params.argv[argvIndex]?.trim();
  if (!rawOperand) {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval requires a stable script operand",
    };
  }
  const resolvedPath = path.resolve(params.cwd ?? process.cwd(), rawOperand);
  let realPath: string;
  let stat: fs.Stats;
  try {
    realPath = fs.realpathSync(resolvedPath);
    stat = fs.statSync(realPath);
  } catch {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval requires an existing script operand",
    };
  }
  if (!stat.isFile()) {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval requires a file script operand",
    };
  }
  return {
    ok: true,
    snapshot: {
      argvIndex,
      path: realPath,
      sha256: hashFileContentsSync(realPath),
    },
  };
}

function resolveCanonicalApprovalCwdSync(cwd: string):
  | {
      ok: true;
      snapshot: ApprovedCwdSnapshot;
    }
  | { ok: false; message: string } {
  const requestedCwd = path.resolve(cwd);
  let cwdLstat: fs.Stats;
  let cwdStat: fs.Stats;
  let cwdReal: string;
  let cwdRealStat: fs.Stats;
  try {
    cwdLstat = fs.lstatSync(requestedCwd);
    cwdStat = fs.statSync(requestedCwd);
    cwdReal = fs.realpathSync(requestedCwd);
    cwdRealStat = fs.statSync(cwdReal);
  } catch {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval requires an existing canonical cwd",
    };
  }
  if (!cwdStat.isDirectory()) {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval requires cwd to be a directory",
    };
  }
  if (hasMutableSymlinkPathComponentSync(requestedCwd)) {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval requires canonical cwd (no symlink path components)",
    };
  }
  if (cwdLstat.isSymbolicLink()) {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval requires canonical cwd (no symlink cwd)",
    };
  }
  if (
    !sameFileIdentity(cwdStat, cwdLstat) ||
    !sameFileIdentity(cwdStat, cwdRealStat) ||
    !sameFileIdentity(cwdLstat, cwdRealStat)
  ) {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval cwd identity mismatch",
    };
  }
  return {
    ok: true,
    snapshot: {
      cwd: cwdReal,
      stat: cwdStat,
    },
  };
}

export function revalidateApprovedCwdSnapshot(params: { snapshot: ApprovedCwdSnapshot }): boolean {
  const current = resolveCanonicalApprovalCwdSync(params.snapshot.cwd);
  if (!current.ok) {
    return false;
  }
  return sameFileIdentity(params.snapshot.stat, current.snapshot.stat);
}

export function revalidateApprovedMutableFileOperand(params: {
  snapshot: SystemRunApprovalFileOperand;
  argv: string[];
  cwd: string | undefined;
}): boolean {
  const operand = params.argv[params.snapshot.argvIndex]?.trim();
  if (!operand) {
    return false;
  }
  const resolvedPath = path.resolve(params.cwd ?? process.cwd(), operand);
  let realPath: string;
  try {
    realPath = fs.realpathSync(resolvedPath);
  } catch {
    return false;
  }
  if (realPath !== params.snapshot.path) {
    return false;
  }
  try {
    return hashFileContentsSync(realPath) === params.snapshot.sha256;
  } catch {
    return false;
  }
}

export function hardenApprovedExecutionPaths(params: {
  approvedByAsk: boolean;
  argv: string[];
  shellCommand: string | null;
  cwd: string | undefined;
}):
  | {
      ok: true;
      argv: string[];
      argvChanged: boolean;
      cwd: string | undefined;
      approvedCwdSnapshot: ApprovedCwdSnapshot | undefined;
    }
  | { ok: false; message: string } {
  if (!params.approvedByAsk) {
    return {
      ok: true,
      argv: params.argv,
      argvChanged: false,
      cwd: params.cwd,
      approvedCwdSnapshot: undefined,
    };
  }

  let hardenedCwd = params.cwd;
  let approvedCwdSnapshot: ApprovedCwdSnapshot | undefined;
  if (hardenedCwd) {
    const canonicalCwd = resolveCanonicalApprovalCwdSync(hardenedCwd);
    if (!canonicalCwd.ok) {
      return canonicalCwd;
    }
    hardenedCwd = canonicalCwd.snapshot.cwd;
    approvedCwdSnapshot = canonicalCwd.snapshot;
  }

  if (params.argv.length === 0) {
    return {
      ok: true,
      argv: params.argv,
      argvChanged: false,
      cwd: hardenedCwd,
      approvedCwdSnapshot,
    };
  }

  const resolution = resolveCommandResolutionFromArgv(params.argv, hardenedCwd);
  if (
    !shouldPinExecutableForApproval({
      shellCommand: params.shellCommand,
      wrapperChain: resolution?.wrapperChain,
    })
  ) {
    // Preserve wrapper semantics for approval-based execution. Pinning the
    // effective executable while keeping wrapper argv shape can shift positional
    // arguments and execute a different command than approved.
    return {
      ok: true,
      argv: params.argv,
      argvChanged: false,
      cwd: hardenedCwd,
      approvedCwdSnapshot,
    };
  }

  const pinnedExecutable = resolution?.resolvedRealPath ?? resolution?.resolvedPath;
  if (!pinnedExecutable) {
    return {
      ok: false,
      message: "SYSTEM_RUN_DENIED: approval requires a stable executable path",
    };
  }

  if (pinnedExecutable === params.argv[0]) {
    return {
      ok: true,
      argv: params.argv,
      argvChanged: false,
      cwd: hardenedCwd,
      approvedCwdSnapshot,
    };
  }

  const argv = [...params.argv];
  argv[0] = pinnedExecutable;
  return {
    ok: true,
    argv,
    argvChanged: true,
    cwd: hardenedCwd,
    approvedCwdSnapshot,
  };
}

export function buildSystemRunApprovalPlan(params: {
  command?: unknown;
  rawCommand?: unknown;
  cwd?: unknown;
  agentId?: unknown;
  sessionKey?: unknown;
}): { ok: true; plan: SystemRunApprovalPlan } | { ok: false; message: string } {
  const command = resolveSystemRunCommandRequest({
    command: params.command,
    rawCommand: params.rawCommand,
  });
  if (!command.ok) {
    return { ok: false, message: command.message };
  }
  if (command.argv.length === 0) {
    return { ok: false, message: "command required" };
  }
  const hardening = hardenApprovedExecutionPaths({
    approvedByAsk: true,
    argv: command.argv,
    shellCommand: command.shellPayload,
    cwd: normalizeString(params.cwd) ?? undefined,
  });
  if (!hardening.ok) {
    return { ok: false, message: hardening.message };
  }
  const commandText = formatExecCommand(hardening.argv);
  const commandPreview =
    command.previewText?.trim() && command.previewText.trim() !== commandText
      ? command.previewText.trim()
      : null;
  const mutableFileOperand = resolveMutableFileOperandSnapshotSync({
    argv: hardening.argv,
    cwd: hardening.cwd,
    shellCommand: command.shellPayload,
  });
  if (!mutableFileOperand.ok) {
    return { ok: false, message: mutableFileOperand.message };
  }
  return {
    ok: true,
    plan: {
      argv: hardening.argv,
      cwd: hardening.cwd ?? null,
      commandText,
      commandPreview,
      agentId: normalizeString(params.agentId),
      sessionKey: normalizeString(params.sessionKey),
      mutableFileOperand: mutableFileOperand.snapshot ?? undefined,
    },
  };
}
