// Copyright 2018-2026 the Deno authors. MIT license.
// Copyright Joyent and Node contributors. All rights reserved. MIT license.

// deno-lint-ignore-file no-this-alias no-unused-vars no-explicit-any

import { core, primordials } from "ext:core/mod.js";
const {
  ArrayIsArray,
  ArrayPrototypeFilter,
  ArrayPrototypeForEach,
  ArrayPrototypeIndexOf,
  ArrayPrototypeJoin,
  ArrayPrototypeMap,
  ArrayPrototypePop,
  ArrayPrototypePush,
  ArrayPrototypeShift,
  ArrayPrototypeSort,
  ArrayPrototypeSplice,
  ArrayPrototypeUnshift,
  ArrayBufferIsView,
  Boolean,
  ErrorPrototype,
  FunctionPrototypeBind,
  FunctionPrototypeCall,
  MathMax,
  MathMin,
  NumberIsNaN,
  NumberParseFloat,
  ObjectCreate,
  ObjectDefineProperty,
  ObjectGetOwnPropertyDescriptor,
  ObjectGetOwnPropertyNames,
  ObjectGetPrototypeOf,
  ObjectKeys,
  ObjectPrototypeHasOwnProperty,
  ObjectPrototypeIsPrototypeOf,
  ReflectApply,
  RegExpPrototypeExec,
  RegExpPrototypeTest,
  SafeArrayIterator,
  SafeRegExp,
  SafeSet,
  SetPrototypeAdd,
  SetPrototypeHas,
  String,
  StringPrototypeCharAt,
  StringPrototypeEndsWith,
  StringPrototypeIncludes,
  StringPrototypeIndexOf,
  StringPrototypeRepeat,
  StringPrototypeReplace,
  StringPrototypeSearch,
  StringPrototypeSlice,
  StringPrototypeSplit,
  StringPrototypeStartsWith,
  StringPrototypeToLowerCase,
  StringPrototypeTrim,
  Symbol,
  SyntaxError,
} = primordials;

import { Interface } from "ext:deno_node/_readline.mjs";
const { commonPrefix } = core.loadExtScript(
  "ext:deno_node/internal/readline/utils.mjs",
);
const { inspect } = core.loadExtScript(
  "ext:deno_node/internal/util/inspect.mjs",
);
const {
  ERR_INVALID_REPL_EVAL_CONFIG,
  ERR_INVALID_REPL_INPUT,
  ERR_MISSING_ARGS,
} = core.loadExtScript("ext:deno_node/internal/errors.ts");
const { validateFunction } = core.loadExtScript(
  "ext:deno_node/internal/validators.mjs",
);
const { shouldColorize } = core.loadExtScript(
  "ext:deno_node/internal/util.mjs",
);
const vm = core.loadExtScript("ext:deno_node/vm.js").default;
import process from "node:process";
import path from "node:path";
import fs from "node:fs";
import { Console } from "node:console";
import Module from "node:module";
const { EventEmitter } = core.loadExtScript("ext:deno_node/_events.mjs");

export const REPL_MODE_SLOPPY = Symbol("repl-sloppy");
export const REPL_MODE_STRICT = Symbol("repl-strict");

const kBufferedCommandSymbol = Symbol("bufferedCommand");
const kLoadingSymbol = Symbol("loading");
const kContextId = Symbol("contextId");
const kStandaloneREPL = Symbol("standaloneREPL");

// Multiline prompt indicator (matches Node's `| `).
const kMultilinePrompt = "| ";

// Per-instance unique resource name, matching Node's `REPL{n}` scheme so that
// stack traces reproduce the resource ID expected by node_compat tests.
let nextREPLResourceNumber = 1;
function getREPLResourceName(): string {
  return `REPL${nextREPLResourceNumber++}`;
}

// REPLs that are "active" route async (uncaught) errors through their
// `_handleError`. We use `process.setUncaughtExceptionCaptureCallback` so
// user code inspecting `process.listenerCount('uncaughtException')` sees 0
// (matching Node, which uses `domain` and never installs a process
// listener). The capture callback is a single slot - we save the previous
// value when adding the first REPL hook and restore it when the last
// REPL exits.
const _activeREPLs: REPLServer[] = [];
let _captureHookInstalled = false;
let _prevCaptureCallback: any = null;
function _replCaptureCallback(err: any) {
  const repl = _activeREPLs[_activeREPLs.length - 1];
  if (repl) {
    // Always print the error via the REPL output. Node's REPL installs a
    // default `_domain.on('error', e => repl._handleError(e))` listener so
    // that domain-routed errors still surface in the REPL.
    try {
      repl._handleError(err);
    } catch {
      // ignore inspect failures
    }
    // Mirror Node's domain integration: also emit on `_domain` so user
    // listeners (e.g. test-repl-tab-complete-nested-repls's
    // `_domain.on('error', err => { throw err; })`) run. If a listener
    // re-throws, the throw escapes from emit so callers like
    // `child_process.spawn` see a non-zero exit code.
    const dom = (repl as any)._domain;
    if (
      dom && typeof dom.listenerCount === "function" &&
      dom.listenerCount("error") > 0
    ) {
      dom.emit("error", err);
    }
    return;
  }
  if (typeof _prevCaptureCallback === "function") {
    _prevCaptureCallback(err);
  } else {
    // Re-throw so deno_core's default uncaught handler runs.
    throw err;
  }
}
function _installCaptureHook() {
  if (_captureHookInstalled) return;
  _captureHookInstalled = true;
  const p = process as any;
  if (
    typeof p.hasUncaughtExceptionCaptureCallback === "function" &&
    p.hasUncaughtExceptionCaptureCallback()
  ) {
    _prevCaptureCallback = (p as any)._uncaughtExceptionCaptureFn ?? null;
    // Clear before re-setting (the API throws if already set).
    p.setUncaughtExceptionCaptureCallback(null);
  }
  p.setUncaughtExceptionCaptureCallback(_replCaptureCallback);
}
function _uninstallCaptureHookIfIdle() {
  if (_activeREPLs.length !== 0 || !_captureHookInstalled) return;
  _captureHookInstalled = false;
  const p = process as any;
  p.setUncaughtExceptionCaptureCallback(null);
  if (typeof _prevCaptureCallback === "function") {
    p.setUncaughtExceptionCaptureCallback(_prevCaptureCallback);
  }
  _prevCaptureCallback = null;
}

// Node's REPL refuses user-installed `uncaughtException` listeners so the
// REPL's own handler isn't shadowed. Mirror that with a counted
// `newListener` guard installed once and removed when the last non-
// standalone REPL exits. The REPL's own handler is named
// `_replUncaughtHandler`, matching the only allowed function name.
let _newListenerGuardCount = 0;
function _newListenerGuard(event: string, listener: any) {
  if (
    event === "uncaughtException" &&
    listener && listener.name !== "_replUncaughtHandler"
  ) {
    throw new ERR_INVALID_REPL_INPUT(
      "Listeners for `uncaughtException` cannot be used in the REPL",
    );
  }
}
function _addNewListenerGuard() {
  if (_newListenerGuardCount++ === 0) {
    process.prependListener("newListener", _newListenerGuard);
  }
}
function _removeNewListenerGuard() {
  if (--_newListenerGuardCount === 0) {
    process.removeListener("newListener", _newListenerGuard);
  }
}

const reAtFrame = new SafeRegExp(/^\s+at\s/);
const reExtFrame = new SafeRegExp(/\bext:[a-z_]+\//);
const reNodeParenFrame = new SafeRegExp(/\(node:[a-z_]+:/);
const reNodeFrame = new SafeRegExp(/\bnode:[a-z_]+:\d+:\d+\)?$/);
const reOpenParen = new SafeRegExp(/\(/);

// Drop frames that originate from the REPL polyfill / deno_core internals,
// plus the trailing top-level eval frame, so the stack matches Node's
// `overrideStackTrace`-filtered output.
function filterReplStack(stack: string): string {
  const rawLines = StringPrototypeSplit(stack, "\n");
  const filtered: string[] = [];
  for (const line of new SafeArrayIterator(rawLines)) {
    if (!RegExpPrototypeTest(reAtFrame, line)) {
      ArrayPrototypePush(filtered, line);
      continue;
    }
    if (RegExpPrototypeTest(reExtFrame, line)) continue;
    if (RegExpPrototypeTest(reNodeParenFrame, line)) continue;
    if (RegExpPrototypeTest(reNodeFrame, line)) continue;
    ArrayPrototypePush(filtered, line);
  }
  // Mirror Node: from the bottom up, drop the last "anonymous" frame
  // (one without a function name, e.g. "    at FILE:line:col") and
  // everything below it. That removes the synthetic top-level eval frame
  // (Node uses overrideStackTrace + ArrayPrototypeFindLastIndex). The
  // calling code is responsible for dealing with the bracketed inspect
  // form when this leaves zero frames.
  let lastAnon = -1;
  for (let i = filtered.length - 1; i >= 0; i--) {
    const ln = filtered[i];
    if (!RegExpPrototypeTest(reAtFrame, ln)) continue;
    if (!RegExpPrototypeTest(reOpenParen, ln)) {
      lastAnon = i;
      break;
    }
  }
  if (lastAnon !== -1) {
    filtered.length = lastAnon;
  }
  return ArrayPrototypeJoin(filtered, "\n");
}

// This is the default "writer" value
const writer = (obj: unknown) => inspect(obj, writer.options);
writer.options = { ...inspect.defaultOptions, showProxy: true };

// ANSI cursor control helpers for preview
function _cursorTo(stream: any, x: number) {
  stream.write(`\x1b[${x + 1}G`);
}

function _moveCursor(stream: any, dx: number, dy: number) {
  let data = "";
  if (dx < 0) data += `\x1b[${-dx}D`;
  else if (dx > 0) data += `\x1b[${dx}C`;
  if (dy < 0) data += `\x1b[${-dy}A`;
  else if (dy > 0) data += `\x1b[${dy}B`;
  if (data) stream.write(data);
}

function _clearLine(stream: any, dir?: number) {
  if (dir !== undefined && dir < 0) stream.write("\x1b[1K");
  else if (dir !== undefined && dir > 0) stream.write("\x1b[0K");
  else stream.write("\x1b[2K");
}

// Match Node's REPL: hide the legacy `__define*Getter|Setter__` and
// `__lookup*Getter|Setter__` prototype helpers from completion. `__proto__`
// is intentionally kept since it is widely used.
const _replHiddenProtoNames = new SafeSet([
  "__defineGetter__",
  "__defineSetter__",
  "__lookupGetter__",
  "__lookupSetter__",
]);

const reDigits = new SafeRegExp(/^\d+$/);
const reIdentifier = new SafeRegExp(/^[a-zA-Z_$][\w$]*$/);

function _getPropertyNames(obj: any): string[] {
  if (!obj) return [];
  try {
    // Skip getOwnPropertyNames for huge indexed collections; arrays and
    // typed arrays with millions of elements would otherwise allocate
    // millions of name strings and trip a v8 internal assertion.
    const len = (obj as any)?.length;
    const tooBigIndexed = typeof len === "number" && len > 100_000 &&
      (ArrayIsArray(obj) || ArrayBufferIsView(obj));
    if (tooBigIndexed) {
      const symbolKeyed = ArrayPrototypeFilter(
        ObjectKeys(obj),
        (k: string) => !RegExpPrototypeTest(reDigits, k),
      );
      return ArrayPrototypeFilter(symbolKeyed, (name: string) => {
        if (SetPrototypeHas(_replHiddenProtoNames, name)) return false;
        return RegExpPrototypeTest(reIdentifier, name);
      });
    }
    return ArrayPrototypeFilter(
      ObjectGetOwnPropertyNames(obj),
      (name: string) => {
        if (SetPrototypeHas(_replHiddenProtoNames, name)) return false;
        return RegExpPrototypeTest(reIdentifier, name);
      },
    );
  } catch {
    return [];
  }
}

export class Recoverable extends SyntaxError {
  err: Error;
  constructor(err: Error) {
    super();
    this.err = err;
  }
}

/**
 * Check if code looks like it might be an object literal
 * that needs to be wrapped in parens to eval as an expression.
 */
function isObjectLiteral(code: string): boolean {
  const trimmed = StringPrototypeTrim(code);
  return StringPrototypeStartsWith(trimmed, "{") &&
    !StringPrototypeStartsWith(trimmed, "{\\");
}

interface PreviewSession {
  isSafe(expression: string): boolean;
  close(): void;
}

// The actual implementation lives in an IIFE-loaded script so it can
// see the snapshot-time `core.ops` capture -- ES-module polyfills like
// this file only see the post-`removeImportedOps` view, which doesn't
// include `op_node_repl_inspector_connect`. See `_repl_preview.js`.
const { createPreviewSession: _createPreviewSession } = core.loadExtScript(
  "ext:deno_node/_repl_preview.js",
) as { createPreviewSession(): PreviewSession | null };

const reUnexpectedEndOfInput = new SafeRegExp(/Unexpected end of input/);
const reUnexpectedToken = new SafeRegExp(/Unexpected token/);
const reUnterminatedString = new SafeRegExp(/Unterminated string/);
const reUnterminatedTemplate = new SafeRegExp(/Unterminated template/);
const reTrailingSemicolon = new SafeRegExp(/;\s*$/);
const reBlankString = new SafeRegExp(/^\s*$/);
const reTrailingNewline = new SafeRegExp(/\n$/);
const reReplCommand = new SafeRegExp(/^\s*\.(\w*)$/);
const reVarDeclaration = new SafeRegExp(
  /^\s*(?:let|const|var)\s+[a-zA-Z_$][\w$]*$/,
);
const reRequireImport = new SafeRegExp(
  /(?:^|[\s;({,])(require|import)\s*\(\s*(['"`])((?:(?!\2|\\).)*)$/,
);
const reExprChain = new SafeRegExp(
  /(?:^|[\s;({\[,!~+\-*/&|^%<>?:=])((?:[a-zA-Z_$][\w$]*\??\.)*(?:[a-zA-Z_$][\w$]*)?)\.?$/,
);
const reNewlineChars = new SafeRegExp(/[\r\n\v]/);
const reLeadingWhitespace = new SafeRegExp(/^\s+/);
const reReplKeyword = new SafeRegExp(/^\.([^\s]+)\s*(.*)$/);
const reStackFrames = new SafeRegExp(/^\s+at\s.*\n?/gm);
const reReplFilename = new SafeRegExp(/^REPL\d+:\d+\r?\n/);
const reReplLowerFilename = new SafeRegExp(/^repl:\d+\r?\n/);
const reUnexpectedTokenName = new SafeRegExp(
  /Unexpected (?:token|identifier|string|number|keyword|reserved\s+word) '(.+?)'/,
);
const reErrorBraces = new SafeRegExp(/^\[(.*)\](?: (\{[\s\S]*\}))?$/);
const reLineSplit = new SafeRegExp(/(?<=\n)/);
const reErrorName = new SafeRegExp(/^\[?([A-Z][a-z0-9_]*)*Error/);

/**
 * Check if a syntax error is recoverable (i.e., the user might continue typing).
 */
function isRecoverableError(e: unknown, code: string): boolean {
  if (!e || typeof e !== "object" || (e as Error).name !== "SyntaxError") {
    return false;
  }
  const message = (e as Error).message;

  // Check for common recoverable patterns
  if (RegExpPrototypeTest(reUnexpectedEndOfInput, message)) return true;
  if (RegExpPrototypeTest(reUnexpectedToken, message)) {
    // Check if the code has unbalanced braces/brackets/parens
    let depth = 0;
    for (let i = 0; i < code.length; i++) {
      const c = code[i];
      if (c === "{" || c === "(" || c === "[") depth++;
      else if (c === "}" || c === ")" || c === "]") depth--;
    }
    if (depth > 0) return true;
  }
  if (RegExpPrototypeTest(reUnterminatedString, message)) return true;
  if (RegExpPrototypeTest(reUnterminatedTemplate, message)) return true;

  return false;
}

function _turnOnEditorMode(repl: REPLServer) {
  repl.editorMode = true;
  FunctionPrototypeCall(Interface.prototype.setPrompt, repl, "");
}

function _turnOffEditorMode(repl: REPLServer) {
  repl.editorMode = false;
  repl.setPrompt(repl._initialPrompt);
}

function defineDefaultCommands(repl: REPLServer) {
  repl.defineCommand("break", {
    help: "Sometimes you get stuck, this gets you out",
    action: function (this: REPLServer) {
      this.clearBufferedCommand();
      this.displayPrompt();
    },
  });

  let clearMessage: string;
  if (repl.useGlobal) {
    clearMessage = "Alias for .break";
  } else {
    clearMessage = "Break, and also clear the local context";
  }
  repl.defineCommand("clear", {
    help: clearMessage,
    action: function (this: REPLServer) {
      this.clearBufferedCommand();
      if (!this.useGlobal) {
        this.output.write("Clearing context...\n");
        this.resetContext();
      }
      this.displayPrompt();
    },
  });

  repl.defineCommand("exit", {
    help: "Exit the REPL",
    action: function (this: REPLServer) {
      this.close();
    },
  });

  repl.defineCommand("help", {
    help: "Print this help message",
    action: function (this: REPLServer) {
      const names = ArrayPrototypeSort(ObjectKeys(this.commands));
      const longestNameLength = MathMax(
        ...new SafeArrayIterator(
          ArrayPrototypeMap(names, (name) => name.length),
        ),
      );
      ArrayPrototypeForEach(names, (name) => {
        const cmd = this.commands[name];
        const spaces = StringPrototypeRepeat(
          " ",
          longestNameLength - name.length + 3,
        );
        const line = `.${name}${cmd.help ? spaces + cmd.help : ""}\n`;
        this.output.write(line);
      });
      this.output.write(
        "\nPress Ctrl+C to abort current expression, " +
          "Ctrl+D to exit the REPL\n",
      );
      this.displayPrompt();
    },
  });

  repl.defineCommand("save", {
    help: "Save all evaluated commands in this REPL session to a file",
    action: function (this: REPLServer, file: string) {
      try {
        if (file === "") {
          throw new ERR_MISSING_ARGS("file");
        }
        fs.writeFileSync(file, ArrayPrototypeJoin(this.lines, "\n"));
        this.output.write(`Session saved to: ${file}\n`);
      } catch (error) {
        if ((error as { code?: string })?.code === "ERR_MISSING_ARGS") {
          this.output.write(`${(error as Error).message}\n`);
        } else {
          this.output.write(`Failed to save: ${file}\n`);
        }
      }
      this.displayPrompt();
    },
  });

  repl.defineCommand("load", {
    help: "Load JS from a file into the REPL session",
    action: function (this: REPLServer, file: string) {
      try {
        if (file === "") {
          throw new ERR_MISSING_ARGS("file");
        }
        const stats = fs.statSync(file);
        if (stats && stats.isFile()) {
          _turnOnEditorMode(this);
          this[kLoadingSymbol] = true;
          const data = fs.readFileSync(file, "utf8");
          this.write(data);
          this[kLoadingSymbol] = false;
          _turnOffEditorMode(this);
          this.write("\n");
        } else {
          this.output.write(
            `Failed to load: ${file} is not a valid file\n`,
          );
        }
      } catch (error) {
        if ((error as { code?: string })?.code === "ERR_MISSING_ARGS") {
          this.output.write(`${(error as Error).message}\n`);
        } else {
          this.output.write(`Failed to load: ${file}\n`);
        }
      }
      this.displayPrompt();
    },
  });

  repl.defineCommand("editor", {
    help: "Enter editor mode",
    action: function (this: REPLServer) {
      if (!this.terminal) {
        this.displayPrompt();
        return;
      }
      _turnOnEditorMode(this);
      this.output.write(
        "// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n",
      );
    },
  });
}

function _memory(this: REPLServer, cmd: string) {
  this.lines = this.lines || [];
  this.lines.level = this.lines.level || [];

  if (cmd) {
    const len = this.lines.level.length ? this.lines.level.length - 1 : 0;
    ArrayPrototypePush(this.lines, StringPrototypeRepeat("  ", len) + cmd);
  } else {
    ArrayPrototypePush(this.lines, "");
  }

  if (!cmd) {
    this.lines.level = [];
    return;
  }

  const countMatches = (regex: RegExp, str: string) => {
    let count = 0;
    while (RegExpPrototypeExec(regex, str) !== null) count++;
    return count;
  };

  const dw = countMatches(new SafeRegExp(/[{(]/g), cmd);
  const up = countMatches(new SafeRegExp(/[})]/g), cmd);
  let depth = dw - up;

  if (depth) {
    FunctionPrototypeCall(function workIt() {
      const self = this as REPLServer;
      if (depth > 0) {
        ArrayPrototypePush(self.lines.level, {
          line: self.lines.length - 1,
          depth: depth,
        });
      } else if (depth < 0) {
        const curr = ArrayPrototypePop(self.lines.level);
        if (curr) {
          const tmp = curr.depth + depth;
          if (tmp < 0) {
            depth += curr.depth;
            FunctionPrototypeCall(workIt, self);
          } else if (tmp > 0) {
            curr.depth += depth;
            ArrayPrototypePush(self.lines.level, curr);
          }
        }
      }
    }, this);
  }
}

type REPLCommand = {
  help?: string;
  action: (this: REPLServer, ...args: any[]) => void;
};

export class REPLServer extends (Interface as any) {
  constructor(
    prompt?: any,
    stream?: any,
    eval_?: any,
    useGlobal?: boolean,
    ignoreUndefined?: boolean,
    replMode?: symbol,
  ) {
    let options: Record<string, unknown>;
    if (prompt !== null && typeof prompt === "object") {
      options = { ...prompt };
      stream = options.stream || options.socket;
      eval_ = options.eval;
      useGlobal = options.useGlobal as boolean | undefined;
      ignoreUndefined = options.ignoreUndefined as boolean | undefined;
      prompt = options.prompt;
      replMode = options.replMode as symbol | undefined;
    } else {
      options = {};
    }

    if (!options.input && !options.output) {
      stream = stream || process;
      options.input = stream.stdin || stream;
      options.output = stream.stdout || stream;
    }

    if (options.terminal === undefined) {
      options.terminal = !!(options.output as { isTTY?: boolean })?.isTTY;
    }
    options.terminal = !!options.terminal;
    // The preview re-runs the typed input on every keystroke. To match
    // Node's behavior and avoid visibly firing side effects (e.g. typing
    // the closing paren of `console.log("hi")` printing "hi" before
    // Enter), the preview path is gated by the V8 inspector's
    // `Runtime.evaluate({ throwOnSideEffect: true })` -- see
    // `_createPreviewSession`. If the inspector probe isn't available
    // (`previewSession` ends up null below), preview stays off rather
    // than falling back to an unprotected `vm.Script` eval.
    // See: https://github.com/denoland/deno/issues/34360
    //
    // The `preview` default matches Node: off when a custom `eval` is
    // supplied (consumers like `@babel/node` get clean output), on
    // otherwise. A consumer that wants previews with a custom eval can
    // pass `preview: true` explicitly.
    const previewRequested = !!options.terminal &&
      (options.preview !== undefined ? !!options.preview : !eval_);
    const previewSession = previewRequested ? _createPreviewSession() : null;
    const usePreview = previewSession !== null;

    if (options.terminal && options.useColors === undefined) {
      options.useColors = shouldColorize(options.output);
    }

    // Default prompt
    if (prompt === undefined) {
      prompt = "> ";
    }

    super({
      input: options.input,
      output: options.output,
      completer: options.completer || completer,
      terminal: options.terminal,
      historySize: options.historySize,
      prompt,
    });

    // Create a fake domain for compatibility. Node uses domains for error
    // handling, but we use a simpler EventEmitter-based approach.
    this._domain = new EventEmitter();

    // Deprecated inputStream/outputStream properties (DEP0141)
    ObjectDefineProperty(this, "inputStream", {
      __proto__: null,
      get: () => this.input,
      set: (val: any) => {
        this.input = val;
      },
      enumerable: false,
      configurable: true,
    });
    ObjectDefineProperty(this, "outputStream", {
      __proto__: null,
      get: () => this.output,
      set: (val: any) => {
        this.output = val;
      },
      enumerable: false,
      configurable: true,
    });

    this.allowBlockingCompletions = !!options.allowBlockingCompletions;
    this.useColors = !!options.useColors;
    this._isStandalone =
      !!(options as Record<symbol, unknown>)[kStandaloneREPL];
    this.useGlobal = !!useGlobal;
    this.ignoreUndefined = !!ignoreUndefined;
    this.replMode = replMode || REPL_MODE_SLOPPY;
    this.underscoreAssigned = false;
    this.last = undefined;
    this.underscoreErrAssigned = false;
    this.lastError = undefined;
    this.breakEvalOnSigint = !!options.breakEvalOnSigint;
    this.editorMode = false;
    this[kContextId] = undefined;
    this._initialPrompt = prompt as string;
    // Stable resource name like Node's "REPL1"/"REPL2"/... - used as the
    // filename for compiled scripts so stack traces match Node's output.
    this._resourceName = getREPLResourceName();

    if (this.breakEvalOnSigint && eval_) {
      throw new ERR_INVALID_REPL_EVAL_CONFIG();
    }

    if ((options as Record<symbol, unknown>)[kStandaloneREPL]) {
      _module.exports.repl = this;
    } else {
      // Non-standalone REPLs install the user-listener guard and route
      // async errors via `process._fatalException`. The fatal-exception
      // hook is global; the guard is refcounted so multiple concurrent
      // REPLs co-exist and the process is left clean once the last exits.
      // We use `_fatalException` (rather than process.on) so user code
      // inspecting `process.listenerCount('uncaughtException')` sees 0,
      // matching Node's domain-based REPL.
      _installCaptureHook();
      _addNewListenerGuard();
      ArrayPrototypePush(_activeREPLs, this);
      this._processListenersAttached = true;
      this.once("exit", () => {
        const idx = ArrayPrototypeIndexOf(_activeREPLs, this);
        if (idx !== -1) ArrayPrototypeSplice(_activeREPLs, idx, 1);
        if (this._processListenersAttached) {
          this._processListenersAttached = false;
          _removeNewListenerGuard();
        }
        _uninstallCaptureHookIfIdle();
      });
    }

    eval_ = eval_ || defaultEval;

    const self = this;

    // Pause taking in new input, and store the keys in a buffer.

    const pausedBuffer: any[] = [];
    let paused = false;
    function pause() {
      paused = true;
    }

    function unpause() {
      if (!paused) return;
      paused = false;

      let entry: any;
      const tmpCompletionEnabled = self.isCompletionEnabled;
      while ((entry = ArrayPrototypeShift(pausedBuffer)) !== undefined) {
        const type = entry[0];
        const payload = entry[1];
        const isCompletionEnabled = entry[2];
        switch (type) {
          case "key": {
            const d = payload[0];
            const key = payload[1];
            self.isCompletionEnabled = isCompletionEnabled;
            self._ttyWrite(d, key);
            break;
          }
          case "close":
            self.emit("exit");
            break;
        }
        if (paused) {
          break;
        }
      }
      self.isCompletionEnabled = tmpCompletionEnabled;
    }

    function defaultEval(
      code: string,
      context: any,
      _file: string,
      cb: (err: Error | null, result?: any) => void,
    ) {
      let result;
      let err: Error | null = null;
      let wrappedCmd = false;
      const input = code;

      // Empty input
      if (code === "\n") return cb(null);

      // If it looks like an object literal (starts with { and no trailing ;),
      // try wrapping in parens first to treat as expression.
      // This matches Node.js behavior: wrap first, fallback to unwrapped.
      if (
        isObjectLiteral(code) &&
        !RegExpPrototypeTest(reTrailingSemicolon, StringPrototypeTrim(code))
      ) {
        code = `(${StringPrototypeTrim(code)})\n`;
        wrappedCmd = true;
      }

      if (err === null) {
        let wrappedErr: Error | undefined;
        while (true) {
          try {
            if (
              self.replMode === REPL_MODE_STRICT &&
              !RegExpPrototypeTest(reBlankString, code)
            ) {
              code = `'use strict'; void 0;\n${code}`;
            }
            const script = new vm.Script(code, { filename: _file });
            if (self.useGlobal) {
              result = script.runInThisContext({ displayErrors: false });
            } else {
              result = script.runInContext(context, {
                displayErrors: false,
              });
            }
          } catch (e) {
            if (wrappedCmd) {
              // Wrapped version failed, try original
              wrappedCmd = false;
              code = input;
              wrappedErr = e as Error;
              continue;
            }
            // Use the unwrapped error unless it's a SyntaxError and the
            // wrapped version also had a SyntaxError (in which case we
            // prefer the unwrapped SyntaxError for better messaging).
            const error = e as Error;
            if (isRecoverableError(error, code)) {
              err = new Recoverable(error);
            } else {
              // Attach source context for SyntaxErrors so _handleError
              // can display source lines like Node.js does.
              if (
                error != null && typeof error === "object" &&
                error.name === "SyntaxError"
              ) {
                (error as any)._replSourceCode = StringPrototypeReplace(
                  input,
                  reTrailingNewline,
                  "",
                );
              }
              err = error;
            }
          }
          break;
        }
      }

      cb(err, result);
    }

    self.eval = function REPLEval(
      code: string,
      context: any,
      file: string,
      cb: (err: Error | null, result?: any) => void,
    ) {
      eval_(code, context, file, cb);
    };

    self.clearBufferedCommand();

    function completer(text: string, cb: any) {
      const callback = self.editorMode ? self.completeOnEditorMode(cb) : cb;
      _doComplete(text, callback);
    }

    // Wire the real completer into both `this.completer` (used by readline
    // and by user code via `replServer.completer(...)`) and the local
    // `completer` reference. `super(...)` set this to the outer stub.
    if (!options.completer) {
      self.completer = completer;
    }

    function _doComplete(
      line: string,
      callback: (err: Error | null, result: [string[], string]) => void,
    ) {
      const completionGroups: string[][] = [];
      let completeOn = "";
      let filter = "";

      // Handle REPL commands (.break, .clear, etc.). Node returns command
      // names without the leading dot and a completeOn matching the typed
      // suffix (also without the dot), so e.g. completing ".b" produces
      // [['break'], 'b'].
      const cmdMatch = RegExpPrototypeExec(reReplCommand, line);
      if (cmdMatch) {
        ArrayPrototypePush(completionGroups, ObjectKeys(self.commands));
        completeOn = cmdMatch[1];
        if (cmdMatch[1].length) {
          filter = cmdMatch[1];
        }
        completionGroupsLoaded();
        return;
      }

      // Variable declarations like `let a`, `const x`, `var foo` should
      // not produce identifier completions for the binding name.
      if (RegExpPrototypeTest(reVarDeclaration, line)) {
        callback(null, [[], line]);
        return;
      }

      // Module-name completions for `require('...` and `import('...`.
      // Returns the list of public builtin modules (and, for `import(...)`,
      // their `node:` URL forms). Third-party node_modules-on-disk lookup
      // is left to a future pass.
      const modMatch = RegExpPrototypeExec(reRequireImport, line);
      if (modMatch) {
        const subject = modMatch[3];
        const fromModule: string[] =
          ArrayIsArray((Module as any).builtinModules)
            ? (Module as any).builtinModules
            : [];
        const fromModulePublic = ArrayPrototypeFilter(
          fromModule,
          (m: string) => !StringPrototypeStartsWith(m, "_"),
        );
        const unprefixed = ArrayPrototypeFilter(
          fromModulePublic,
          (m: string) => !StringPrototypeStartsWith(m, "node:"),
        );
        const alreadyPrefixed = ArrayPrototypeFilter(
          fromModulePublic,
          (m: string) => StringPrototypeStartsWith(m, "node:"),
        );
        const knownInModule = new SafeSet(fromModule);
        const replExtras = ArrayPrototypeFilter(
          builtinModules as string[],
          (m: string) =>
            !SetPrototypeHas(knownInModule, m) &&
            !StringPrototypeStartsWith(m, "_"),
        );
        // Group A: unprefixed builtins + user-pushed extras. Pushing a
        // single new entry must produce only one extra completion (no extra
        // group separator), so merge replExtras into this group.
        ArrayPrototypePush(completionGroups, [
          ...new SafeArrayIterator(unprefixed),
          ...new SafeArrayIterator(replExtras),
        ]);
        // Group B: every `node:` URL form. Includes both the originally-
        // prefixed names from `Module.builtinModules` (e.g. `node:sqlite`)
        // and the `node:`-prefixed forms of the unprefixed builtins.
        ArrayPrototypePush(completionGroups, [
          ...new SafeArrayIterator(
            ArrayPrototypeMap(unprefixed, (m: string) => `node:${m}`),
          ),
          ...new SafeArrayIterator(alreadyPrefixed),
        ]);
        completeOn = subject;
        filter = subject;
        completionGroupsLoaded();
        return;
      }

      // Match identifier chains: foo, foo.bar, foo.bar.baz, etc.
      const exprMatch = RegExpPrototypeExec(reExprChain, line);

      let expr = "";

      if (exprMatch) {
        const matchStr = exprMatch[1];
        completeOn = matchStr;

        if (StringPrototypeEndsWith(matchStr, ".")) {
          expr = StringPrototypeSlice(matchStr, 0, -1);
          filter = "";
        } else if (StringPrototypeIncludes(matchStr, ".")) {
          const bits = StringPrototypeSplit(matchStr, ".");
          filter = ArrayPrototypePop(bits)!;
          expr = ArrayPrototypeJoin(bits, ".");
        } else {
          filter = matchStr;
        }
      } else if (line.length === 0) {
        completeOn = "";
        filter = "";
      } else {
        callback(null, [[], line]);
        return;
      }

      if (expr) {
        // Member expression completion
        const chaining = ".";
        let evalExpr = expr;
        if (StringPrototypeEndsWith(expr, "?")) {
          // `expr` already contains the trailing `?`; strip it for eval
          // and keep the chaining as a plain `.` so we render
          // `console?.log` rather than `console??.log`.
          evalExpr = StringPrototypeSlice(expr, 0, -1);
        }

        const wrappedExpr = `try { ${evalExpr} } catch {}`;
        self.eval(
          wrappedExpr,
          self.context,
          "repl",
          (_e: any, obj: any) => {
            if (obj != null) {
              const memberGroups: string[][] = [];
              try {
                let p;
                if (
                  (typeof obj === "object" && obj !== null) ||
                  typeof obj === "function"
                ) {
                  ArrayPrototypePush(memberGroups, _getPropertyNames(obj));
                  p = ObjectGetPrototypeOf(obj);
                } else {
                  p = obj.constructor ? obj.constructor.prototype : null;
                }
                let sentinel = 5;
                while (p !== null && sentinel-- > 0) {
                  ArrayPrototypePush(memberGroups, _getPropertyNames(p));
                  p = ObjectGetPrototypeOf(p);
                }
              } catch {
                // Proxy without getOwnPropertyNames
              }

              if (memberGroups.length) {
                const prefix = expr + chaining;
                for (const group of new SafeArrayIterator(memberGroups)) {
                  ArrayPrototypePush(
                    completionGroups,
                    ArrayPrototypeMap(group, (m: string) => `${prefix}${m}`),
                  );
                }
                if (filter) {
                  filter = `${prefix}${filter}`;
                }
              }
            }
            completionGroupsLoaded();
          },
        );
      } else {
        // Global completion - walk context prototype chain
        if (self.context) {
          try {
            let obj = self.context;
            let sentinel = 5;
            while (obj !== null && sentinel-- > 0) {
              try {
                ArrayPrototypePush(completionGroups, _getPropertyNames(obj));
              } catch {
                // ignore
              }
              obj = ObjectGetPrototypeOf(obj);
            }
          } catch {
            // ignore
          }
        }
        // Also include the JS-level globals (Object, Array, Uint8Array,
        // ...). When `useGlobal` is false, our `vm.createContext()` returns
        // an object whose prototype doesn't expose the built-in
        // constructors as own properties, so completion misses them.
        if (!self.useGlobal) {
          try {
            ArrayPrototypePush(completionGroups, _getPropertyNames(globalThis));
          } catch {
            // ignore
          }
        }

        // JS keywords
        if (filter !== "") {
          ArrayPrototypePush(completionGroups, [
            "async",
            "await",
            "break",
            "case",
            "catch",
            "const",
            "continue",
            "debugger",
            "default",
            "delete",
            "do",
            "else",
            "export",
            "false",
            "finally",
            "for",
            "function",
            "if",
            "import",
            "in",
            "instanceof",
            "let",
            "new",
            "null",
            "return",
            "switch",
            "this",
            "throw",
            "true",
            "try",
            "typeof",
            "undefined",
            "var",
            "void",
            "while",
            "with",
            "yield",
          ]);
        }

        completionGroupsLoaded();
      }

      function completionGroupsLoaded() {
        // Filter by prefix
        if (completionGroups.length && filter) {
          const lowerFilter = StringPrototypeToLowerCase(filter);
          const filtered: string[][] = [];
          for (const group of new SafeArrayIterator(completionGroups)) {
            const fg = ArrayPrototypeFilter(
              group,
              (str) =>
                StringPrototypeStartsWith(
                  StringPrototypeToLowerCase(str),
                  lowerFilter,
                ),
            );
            if (fg.length) ArrayPrototypePush(filtered, fg);
          }
          completionGroups.length = 0;
          ArrayPrototypePush(
            completionGroups,
            ...new SafeArrayIterator(filtered),
          );
        }

        // Deduplicate and collect
        const completions: string[] = [];
        const seen = new SafeSet<string>();
        SetPrototypeAdd(seen, "");

        for (const group of new SafeArrayIterator(completionGroups)) {
          ArrayPrototypeSort(group, (a, b) => (b > a ? 1 : -1));
          const prevSize = seen.size;
          for (const item of new SafeArrayIterator(group)) {
            if (!SetPrototypeHas(seen, item)) {
              ArrayPrototypeUnshift(completions, item);
              SetPrototypeAdd(seen, item);
            }
          }
          if (seen.size !== prevSize) {
            ArrayPrototypeUnshift(completions, "");
          }
        }

        if (completions.length > 0 && completions[0] === "") {
          ArrayPrototypeShift(completions);
        }

        callback(null, [completions, completeOn]);
      }
    }

    // Preview state
    let inputPreview: string | null = null;
    let completionPreview: string | null = null;
    let previewCompletionCounter = 0;
    let escaped: string | null = null;

    function _getDisplayPosHelper(str: string) {
      if (typeof self._getDisplayPos === "function") {
        return self._getDisplayPos(str);
      }
      return { rows: 0, cols: str.length };
    }

    function getPreviewPos() {
      const displayPos = _getDisplayPosHelper(
        `${self.getPrompt()}${self.line}`,
      );
      const cursorPos = self.line.length !== self.cursor
        ? (typeof self.getCursorPos === "function"
          ? self.getCursorPos()
          : { rows: 0, cols: self.getPrompt().length + self.cursor })
        : displayPos;
      return { displayPos, cursorPos };
    }

    function isCursorAtInputEnd() {
      return self.cursor === self.line.length;
    }

    function showCompletionPreview(line: string) {
      previewCompletionCounter++;
      const count = previewCompletionCounter;

      self.completer(line, (err: any, data: any) => {
        if (count !== previewCompletionCounter) return;
        if (err) return;

        const rawCompletions = data[0];
        const completeOn = data[1];
        if (!rawCompletions || rawCompletions.length === 0) return;

        const completions = ArrayPrototypeFilter(rawCompletions, Boolean);
        if (completions.length === 0) return;

        const prefix = commonPrefix(completions);
        if (prefix.length <= completeOn.length) return;

        const suffix = StringPrototypeSlice(prefix, completeOn.length);
        completionPreview = suffix;

        const result = self.useColors
          ? `\x1b[90m${suffix}\x1b[39m`
          : ` // ${suffix}`;

        const { cursorPos, displayPos } = getPreviewPos();
        if (self.line.length !== self.cursor) {
          _cursorTo(self.output, displayPos.cols);
          _moveCursor(self.output, 0, displayPos.rows - cursorPos.rows);
        }
        self.output.write(result);
        _cursorTo(self.output, cursorPos.cols);
        const totalLine = `${self.getPrompt()}${self.line}${suffix}`;
        const newPos = _getDisplayPosHelper(totalLine);
        const rows = newPos.rows - cursorPos.rows -
          (newPos.cols === 0 ? 1 : 0);
        if (rows > 0) _moveCursor(self.output, 0, -rows);
      });
    }

    function showPreview(showCompletion = true) {
      if (!usePreview) return;
      if (inputPreview !== null || !self.isCompletionEnabled) return;

      const line = StringPrototypeTrim(self.line);
      if (line === "") return;

      // Show completion preview (inline dim suffix)
      if (showCompletion) {
        showCompletionPreview(self.line);
      }

      // Don't show eval preview in multiline mode
      if (self[kBufferedCommandSymbol]) return;

      // Evaluate for input preview
      let previewLine = line;
      if (
        completionPreview !== null && isCursorAtInputEnd() &&
        escaped !== self.line
      ) {
        previewLine += completionPreview;
      }

      let previewCode = previewLine + "\n";
      // Apply object literal wrapping for preview too
      if (
        isObjectLiteral(previewCode) &&
        !RegExpPrototypeTest(
          reTrailingSemicolon,
          StringPrototypeTrim(previewCode),
        )
      ) {
        previewCode = `(${StringPrototypeTrim(previewCode)})\n`;
      }

      // Probe via the inspector first: if v8 reports a side effect
      // (or a syntax/reference error / runtime throw / timeout), don't
      // preview. This is what stops typed input like `console.log("hi")`
      // from printing "hi" before the user presses Enter.
      if (!previewSession!.isSafe(previewCode)) return;

      // Probe passed -- safe to evaluate normally for the actual value
      // so `inspect()` below sees the real JS object (with prototype,
      // class info, getters, etc.) rather than the CDP wire shape.
      let result;
      try {
        const script = new vm.Script(previewCode, { filename: "repl" });
        if (self.useGlobal) {
          result = script.runInThisContext({
            displayErrors: false,
            timeout: 500,
          });
        } else {
          result = script.runInContext(self.context, {
            displayErrors: false,
            timeout: 500,
          });
        }
      } catch {
        // Probe says safe but the actual eval threw -- e.g. the probe
        // ran against globalThis and the expression references a REPL
        // var that only exists in `self.context`. Silently skip.
        return;
      }

      if (result === undefined && self.ignoreUndefined) return;

      let inspected = inspect(result, {
        colors: false,
        showProxy: true,
        breakLength: Infinity,
        compact: true,
        maxArrayLength: 10,
        depth: 1,
      });
      if (inspected === line) return;

      // Truncate at newline
      const nlIdx = StringPrototypeSearch(inspected, reNewlineChars);
      if (nlIdx !== -1) inspected = StringPrototypeSlice(inspected, 0, nlIdx);

      // Limit length
      const maxCols = MathMin(
        (self as any).columns || 80,
        250,
      );
      if (inspected.length > maxCols) {
        inspected = StringPrototypeSlice(inspected, 0, maxCols - 4) + "...";
      }

      inputPreview = inspected;

      const preview = self.useColors
        ? `\x1b[90m${inspected}\x1b[39m`
        : `// ${inspected}`;

      const { cursorPos, displayPos } = getPreviewPos();
      const rows = displayPos.rows - cursorPos.rows;
      if (rows > 0) _moveCursor(self.output, 0, rows);
      self.output.write(`\n${preview}`);
      _cursorTo(self.output, cursorPos.cols);
      _moveCursor(self.output, 0, -rows - 1);
    }

    function clearPreview(key: any) {
      if (!usePreview) return;

      // Clear input preview
      if (inputPreview !== null) {
        const { displayPos, cursorPos } = getPreviewPos();
        const rows = displayPos.rows - cursorPos.rows + 1;
        _moveCursor(self.output, 0, rows);
        _clearLine(self.output);
        _moveCursor(self.output, 0, -rows);
        inputPreview = null;
      }

      // Clear completion preview
      if (completionPreview !== null) {
        const move = self.line.length !== self.cursor;
        let pos: any;
        let rows = 0;
        if (move) {
          pos = getPreviewPos();
          _cursorTo(self.output, pos.displayPos.cols);
          rows = pos.displayPos.rows - pos.cursorPos.rows;
          _moveCursor(self.output, 0, rows);
        }
        const totalLine = `${self.getPrompt()}${self.line}${completionPreview}`;
        const newPos = _getDisplayPosHelper(totalLine);
        if (
          newPos.rows === 0 ||
          (move && pos.displayPos.rows === newPos.rows)
        ) {
          _clearLine(self.output, 1);
        } else {
          self.output.write("\x1b[0J");
        }
        if (move) {
          _cursorTo(self.output, pos.cursorPos.cols);
          _moveCursor(self.output, 0, -rows);
        }

        // Auto-accept completion on Enter
        if (key && !key.ctrl && !key.shift) {
          if (key.name === "escape") {
            if (escaped === null && key.meta) {
              escaped = self.line;
            }
          } else if (
            (key.name === "return" || key.name === "enter") &&
            !key.meta &&
            escaped !== self.line &&
            isCursorAtInputEnd()
          ) {
            self._insertString(completionPreview);
          }
        }

        completionPreview = null;
      }

      if (escaped !== self.line) {
        escaped = null;
      }
    }

    self.resetContext();

    this.commands = ObjectCreate(null);
    defineDefaultCommands(this);

    // Figure out which "writer" function to use
    self.writer = options.writer || writer;

    if (self.writer === writer) {
      writer.options.colors = self.useColors;
    }

    function _parseREPLKeyword(
      this: REPLServer,
      keyword: string,
      rest: string,
    ): boolean {
      const cmd = this.commands[keyword];
      if (cmd) {
        FunctionPrototypeCall(cmd.action, this, rest);
        return true;
      }
      return false;
    }

    self.on("close", function emitExit() {
      if (paused) {
        ArrayPrototypePush(pausedBuffer, ["close"]);
        return;
      }
      if (previewSession !== null) {
        try {
          previewSession.close();
        } catch { /* ignore */ }
      }
      self.emit("exit");
    });

    let sawSIGINT = false;
    let sawCtrlD = false;
    self.on("SIGINT", function onSigInt() {
      const empty = self.line.length === 0;
      self.clearLine();
      _turnOffEditorMode(self);

      const cmd = self[kBufferedCommandSymbol];
      if (!(cmd && cmd.length > 0) && empty) {
        if (sawSIGINT) {
          self.close();
          sawSIGINT = false;
          return;
        }
        self.output.write(
          "(To exit, press Ctrl+C again or Ctrl+D or type .exit)\n",
        );
        sawSIGINT = true;
      } else {
        sawSIGINT = false;
      }

      self.clearBufferedCommand();
      self.lines.level = [];
      self.displayPrompt();
    });

    self.on("line", function onLine(cmd: string) {
      cmd = cmd || "";
      sawSIGINT = false;

      if (self.editorMode) {
        self[kBufferedCommandSymbol] += cmd + "\n";

        // code alignment
        const matches = self._sawKeyPress && !self[kLoadingSymbol]
          ? RegExpPrototypeExec(reLeadingWhitespace, cmd)
          : null;
        if (matches) {
          const prefix = matches[0];
          self.write(prefix);
          self.line = prefix;
          self.cursor = prefix.length;
        }
        FunctionPrototypeCall(_memory, self, cmd);
        return;
      }

      // Check REPL keywords and empty lines against a trimmed line input.
      const trimmedCmd = StringPrototypeTrim(cmd);

      if (trimmedCmd) {
        if (
          StringPrototypeCharAt(trimmedCmd, 0) === "." &&
          StringPrototypeCharAt(trimmedCmd, 1) !== "." &&
          NumberIsNaN(NumberParseFloat(trimmedCmd))
        ) {
          const matches = RegExpPrototypeExec(reReplKeyword, trimmedCmd);
          const keyword = matches?.[1];
          const rest = matches?.[2];
          if (
            keyword &&
            FunctionPrototypeCall(
                _parseREPLKeyword,
                self,
                keyword,
                rest || "",
              ) === true
          ) {
            return;
          }
          if (!self[kBufferedCommandSymbol]) {
            self.output.write("Invalid REPL keyword\n");
            finish(null);
            return;
          }
        }
      }

      const evalCmd = self[kBufferedCommandSymbol] + cmd + "\n";

      self.eval(evalCmd, self.context, self._resourceName, finish);

      function finish(e: Error | null, ret?: any) {
        FunctionPrototypeCall(_memory, self, cmd);

        if (
          e !== null && e !== undefined &&
          !self[kBufferedCommandSymbol] &&
          StringPrototypeStartsWith(StringPrototypeTrim(cmd), "npm ") &&
          !ObjectPrototypeIsPrototypeOf(Recoverable.prototype, e)
        ) {
          self.output.write(
            "npm should be run outside of the " +
              "Node.js REPL, in your normal shell.\n" +
              "(Press Ctrl+D to exit.)\n",
          );
          self.displayPrompt();
          return;
        }

        // If error was SyntaxError and not JSON.parse error
        if (
          ObjectPrototypeIsPrototypeOf(Recoverable.prototype, e) && !sawCtrlD
        ) {
          // Start multiline
          self[kBufferedCommandSymbol] += cmd + "\n";
          self.displayPrompt();
          return;
        }

        if (e !== null && e !== undefined) {
          self._handleError((e as Recoverable).err || e);
        }

        // Clear buffer if no SyntaxErrors
        self.clearBufferedCommand();
        sawCtrlD = false;

        // If we got any output - print it (if no error)
        if (
          (e === null || e === undefined) &&
          arguments.length === 2 &&
          (!self.ignoreUndefined || ret !== undefined)
        ) {
          if (!self.underscoreAssigned) {
            self.last = ret;
          }
          self.output.write(self.writer(ret) + "\n");
        }

        if (!self.closed && !e) {
          self.displayPrompt();
        }
      }
    });

    self.on("SIGCONT", function onSigCont() {
      if (self.editorMode) {
        self.output.write(`${self._initialPrompt}.editor\n`);
        self.output.write(
          "// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n",
        );
        self.output.write(`${self[kBufferedCommandSymbol]}\n`);
        self.prompt(true);
      } else {
        self.displayPrompt(true);
      }
    });

    // Wrap readline tty to enable editor mode and pausing.
    const ttyWrite = FunctionPrototypeBind(self._ttyWrite, self);

    self._ttyWrite = (d: any, key: any) => {
      key = key || {};
      if (
        paused &&
        !(self.breakEvalOnSigint && key.ctrl && key.name === "c")
      ) {
        ArrayPrototypePush(pausedBuffer, [
          "key",
          [d, key],
          self.isCompletionEnabled,
        ]);
        return;
      }
      if (!self.editorMode || !self.terminal) {
        // Before exiting, make sure to clear the line.
        if (
          key.ctrl &&
          key.name === "d" &&
          self.cursor === 0 &&
          self.line.length === 0
        ) {
          self.clearLine();
        }
        clearPreview(key);
        ttyWrite(d, key);
        const showCompletion = key.name !== "escape";
        showPreview(showCompletion);
        return;
      }

      // Editor mode
      if (key.ctrl && !key.shift) {
        switch (key.name) {
          case "d": // End editor mode
            _turnOffEditorMode(self);
            sawCtrlD = true;
            ttyWrite(d, { name: "return" });
            break;
          case "n": // Override next history item
          case "p": // Override previous history item
            break;
          default:
            ttyWrite(d, key);
        }
      } else {
        switch (key.name) {
          case "up": // Override previous history item
          case "down": // Override next history item
            break;
          case "tab":
            // Prevent double tab behavior
            self._previousKey = null;
            ttyWrite(d, key);
            break;
          default:
            ttyWrite(d, key);
        }
      }
    };

    self.displayPrompt();
  }

  setupHistory(
    arg1?: any,
    cb?: (err: Error | null, repl: REPLServer) => void,
  ) {
    // Newer API: setupHistory({ filePath, size, onHistoryFileLoaded })
    let onLoaded = cb;
    if (typeof arg1 === "object" && arg1 !== null) {
      onLoaded = arg1.onHistoryFileLoaded;
    }
    if (typeof onLoaded === "function") {
      onLoaded(null, this);
    }
  }

  clearBufferedCommand() {
    this[kBufferedCommandSymbol] = "";
  }

  _handleError(e: Error) {
    let errStack = "";

    if (typeof e === "object" && e !== null) {
      const isError = ObjectPrototypeIsPrototypeOf(ErrorPrototype, e) ||
        (typeof (e as any).name === "string" &&
          typeof (e as any).stack === "string");
      if (isError && (e as any).stack) {
        if (e.name === "SyntaxError") {
          // Remove stack trace
          errStack = StringPrototypeReplace(
            StringPrototypeReplace(
              StringPrototypeReplace(e.stack, reStackFrames, ""),
              reReplFilename,
              "",
            ),
            reReplLowerFilename,
            "",
          );

          // Deno's V8 doesn't include source context in SyntaxError stacks
          // like Node.js does. Add it if we have the source code attached.
          if ((e as any)._replSourceCode) {
            const srcLine = (e as any)._replSourceCode;
            // Try to determine caret position from the error message.
            // Node.js uses V8's Message.GetStartColumn() which we don't have.
            let col = 0;
            const tokenMatch = RegExpPrototypeExec(
              reUnexpectedTokenName,
              e.message,
            );
            if (tokenMatch) {
              const idx = StringPrototypeIndexOf(srcLine, tokenMatch[1]);
              if (idx !== -1) col = idx;
            }
            const caret = StringPrototypeRepeat(" ", col) + "^";
            errStack = `${srcLine}\n${caret}\n\n${errStack}`;
          }
        } else {
          // For non-syntax errors, keep user stack frames but drop frames
          // that originate from the REPL/runtime internals, plus the trailing
          // top-level eval frame, so the trace matches Node's output
          // (which uses overrideStackTrace). Mutate `e.stack` so the
          // subsequent `writer(e)` call (inspect) renders the filtered stack
          // alongside any custom Error properties.
          (e as any).stack = filterReplStack(e.stack);
        }
        if (e.name !== "SyntaxError") {
          // Some node-style Errors (NodeTypeError etc.) install `toString`
          // as an own property; inspect would render that as a noisy
          // `[Function]` entry inside the `{ ... }` block AND wrap the
          // whole error in `[ ... ]`. Temporarily hide it (and any
          // similarly own-shadowed prototype methods) for the inspect call.
          const hidden: { key: string; desc: PropertyDescriptor }[] = [];
          for (const key of new SafeArrayIterator(["toString"])) {
            const desc = ObjectGetOwnPropertyDescriptor(e, key);
            if (desc) {
              ArrayPrototypePush(hidden, { key, desc });
              delete (e as any)[key];
            }
          }
          try {
            errStack = this.writer(e);
          } finally {
            for (const { key, desc } of new SafeArrayIterator(hidden)) {
              ObjectDefineProperty(e, key, desc);
            }
          }
        }
      }

      if (!errStack) {
        errStack = this.writer(e);
      }

      // Remove one line error braces to keep the old style in place. inspect
      // wraps an Error with no `at` frames in `[ErrorName: msg]`, optionally
      // followed by ` { extra: props }` when the Error has own enumerable
      // props. Node's REPL only strips the outer `[...]`, leaving the props
      // section intact.
      if (errStack[0] === "[") {
        const m = RegExpPrototypeExec(reErrorBraces, errStack);
        if (m) {
          errStack = m[2] ? `${m[1]} ${m[2]}` : m[1];
        }
      }
    }

    if (!this.underscoreErrAssigned) {
      this.lastError = e;
    }

    if (errStack === "") {
      errStack = this.writer(e);
    }

    const lines = StringPrototypeSplit(errStack, reLineSplit);
    let matched = false;

    errStack = "";
    ArrayPrototypeForEach(lines, (line: string) => {
      if (
        !matched &&
        RegExpPrototypeTest(reErrorName, line)
      ) {
        errStack += writer.options.breakLength >= line.length
          ? `Uncaught ${line}`
          : `Uncaught:\n${line}`;
        matched = true;
      } else {
        errStack += line;
      }
    });
    if (!matched) {
      const ln = lines.length === 1 ? " " : ":\n";
      errStack = `Uncaught${ln}${errStack}`;
    }
    // Normalize line endings.
    errStack += StringPrototypeEndsWith(errStack, "\n") ? "" : "\n";
    this.output.write(errStack);
    this.clearBufferedCommand();
    this.lines.level = [];
    if (!this.closed) {
      this.displayPrompt();
    }
  }

  close() {
    if (this.closed || this._closing) return;
    this._closing = true;
    // Drop the `newListener` guard synchronously so user code can register
    // its own `uncaughtException` listener on the closed REPL (matches
    // Node, which removes the guard via 'exit'). The capture-callback
    // hook stays installed so async errors that were already pending in
    // the user's REPL eval still flow through `_handleError` to the REPL
    // output stream.
    if (this._processListenersAttached) {
      this._processListenersAttached = false;
      _removeNewListenerGuard();
    }
    const self = this;
    process.nextTick(() => {
      try {
        // @ts-ignore - calling parent close
        FunctionPrototypeCall(Interface.prototype.close, self);
      } catch {
        // May fail if input stream already destroyed
      }
      self._closing = false;
    });
  }

  createContext() {
    let context: any;
    if (this.useGlobal) {
      context = globalThis;
    } else {
      context = vm.createContext();
      // Match Node: copy non-builtin own properties from globalThis into the
      // new context so user-defined globals are visible at REPL start.
      const builtinNames = new SafeSet(
        (vm as any).runInNewContext?.(
          "Object.getOwnPropertyNames(globalThis)",
        ) ?? [],
      );
      for (
        const name of new SafeArrayIterator(
          ObjectGetOwnPropertyNames(globalThis),
        )
      ) {
        if (SetPrototypeHas(builtinNames, name)) continue;
        try {
          const desc = ObjectGetOwnPropertyDescriptor(globalThis, name);
          if (desc) ObjectDefineProperty(context, name, desc);
        } catch {
          // Non-configurable / non-writable props just get skipped.
        }
      }
      context.global = context;
      let _console;
      try {
        _console = new Console(this.output);
      } catch {
        _console = console;
      }
      ObjectDefineProperty(context, "console", {
        __proto__: null,
        configurable: true,
        writable: true,
        value: _console,
      });
    }

    // Set up module and require in the context
    try {
      // `path` is the node:path module, not an Array; `path.join` is the
      // path joiner.
      // deno-lint-ignore deno-internal/prefer-primordials
      const replPath = path.join(process.cwd(), "repl");
      const replRequire = Module.createRequire(replPath);
      ObjectDefineProperty(context, "require", {
        __proto__: null,
        configurable: true,
        writable: true,
        value: replRequire,
      });
    } catch {
      // createRequire may fail in some environments
    }

    ObjectDefineProperty(context, "module", {
      __proto__: null,
      configurable: true,
      writable: true,
      value: { exports: {} },
    });

    // Mirror Node's addBuiltinLibsToObject: expose each non-underscored,
    // non-slashed builtin module as a lazy getter on the REPL context. This
    // is what makes `util.inspect(...)` work in the REPL without requiring
    // an explicit `require('util')`.
    const ctxRequire = (context as any).require;
    if (typeof ctxRequire === "function") {
      for (const name of new SafeArrayIterator(builtinModules)) {
        if (
          name[0] === "_" || StringPrototypeIncludes(name, "/") ||
          ObjectPrototypeHasOwnProperty(context, name)
        ) {
          continue;
        }
        const setReal = (val: unknown) => {
          delete (context as any)[name];
          (context as any)[name] = val;
        };
        try {
          ObjectDefineProperty(context, name, {
            __proto__: null,
            get: () => {
              try {
                const mod = ctxRequire(name);
                ObjectDefineProperty(context, name, {
                  __proto__: null,
                  configurable: true,
                  writable: true,
                  value: mod,
                });
                return mod;
              } catch {
                return undefined;
              }
            },
            set: setReal,
            configurable: true,
            enumerable: false,
          });
        } catch {
          // Property may already be non-configurable; skip silently.
        }
      }
    }

    return context;
  }

  resetContext() {
    this.context = this.createContext();
    this.underscoreAssigned = false;
    this.underscoreErrAssigned = false;
    this.lines = [];
    this.lines.level = [];

    ObjectDefineProperty(this.context, "_", {
      __proto__: null,
      configurable: true,
      get: () => this.last,
      set: (value) => {
        this.last = value;
        if (!this.underscoreAssigned) {
          this.underscoreAssigned = true;
          this.output.write("Expression assignment to _ now disabled.\n");
        }
      },
    });

    ObjectDefineProperty(this.context, "_error", {
      __proto__: null,
      configurable: true,
      get: () => this.lastError,
      set: (value) => {
        this.lastError = value;
        if (!this.underscoreErrAssigned) {
          this.underscoreErrAssigned = true;
          this.output.write(
            "Expression assignment to _error now disabled.\n",
          );
        }
      },
    });

    // Allow REPL extensions to extend the new context
    this.emit("reset", this.context);
  }

  displayPrompt(preserveCursor?: boolean) {
    let prompt = this._initialPrompt;
    if (this[kBufferedCommandSymbol].length) {
      prompt = kMultilinePrompt;
    }

    super.setPrompt(prompt);
    this.prompt(preserveCursor);
  }

  setPrompt(prompt: string) {
    this._initialPrompt = prompt;
    super.setPrompt(prompt);
  }

  complete(...args: any[]) {
    ReflectApply(this.completer, this, args);
  }

  completeOnEditorMode(callback: any) {
    return (err: Error | null, results: any) => {
      if (err) return callback(err);

      const completions = results[0];
      const completeOn = results[1] ?? "";
      let result = ArrayPrototypeFilter(completions, Boolean);

      if (completeOn && result.length !== 0) {
        result = [commonPrefix(result)];
      }

      callback(null, [result, completeOn]);
    };
  }

  defineCommand(
    keyword: string,
    cmd: REPLCommand | ((this: REPLServer, ...args: unknown[]) => void),
  ) {
    if (typeof cmd === "function") {
      cmd = { action: cmd };
    } else {
      validateFunction(cmd.action, "cmd.action");
    }
    this.commands[keyword] = cmd;
  }
}

function completer(this: REPLServer, _text: string, cb: any) {
  cb(null, [[], _text]);
}

export function start(
  prompt?: any,
  source?: any,
  eval_?: any,
  useGlobal?: boolean,
  ignoreUndefined?: boolean,
  replMode?: symbol,
) {
  return new REPLServer(
    prompt,
    source,
    eval_,
    useGlobal,
    ignoreUndefined,
    replMode,
  );
}

export const builtinModules = [
  "assert",
  "async_hooks",
  "buffer",
  "child_process",
  "cluster",
  "console",
  "constants",
  "crypto",
  "dgram",
  "diagnostics_channel",
  "dns",
  "domain",
  "events",
  "fs",
  "http",
  "http2",
  "https",
  "inspector",
  "module",
  "net",
  "os",
  "path",
  "perf_hooks",
  "process",
  "punycode",
  "querystring",
  "readline",
  "repl",
  "stream",
  "string_decoder",
  "sys",
  "timers",
  "tls",
  "trace_events",
  "tty",
  "url",
  "util",
  "v8",
  "vm",
  "wasi",
  "worker_threads",
  "zlib",
];

export const _builtinLibs = builtinModules;

// Module-level reference for standalone REPL tracking
const _module = { exports: {} as Record<string, unknown> };

export default {
  REPLServer,
  builtinModules,
  _builtinLibs,
  start,
  writer,
  REPL_MODE_SLOPPY,
  REPL_MODE_STRICT,
  Recoverable,
};
