text: 'use strict';
  
  const {
    MathMin,
    Set,
    Symbol,
  } = primordials;
  
  const { tokTypes: tt, Parser: AcornParser } =
    require('internal/deps/acorn/acorn/dist/acorn');
  const privateMethods =
    require('internal/deps/acorn-plugins/acorn-private-methods/index');
  const classFields =
    require('internal/deps/acorn-plugins/acorn-class-fields/index');
  const numericSeparator =
    require('internal/deps/acorn-plugins/acorn-numeric-separator/index');
  const staticClassFeatures =
    require('internal/deps/acorn-plugins/acorn-static-class-features/index');
  
  const { sendInspectorCommand } = require('internal/util/inspector');
  
  const {
    ERR_INSPECTOR_NOT_AVAILABLE
  } = require('internal/errors').codes;
  
  const {
    clearLine,
    clearScreenDown,
    cursorTo,
    moveCursor,
  } = require('readline');
  
  const {
    commonPrefix,
    kSubstringSearch,
  } = require('internal/readline/utils');
  
  const {
    getStringWidth,
    inspect,
  } = require('internal/util/inspect');
  
  let debug = require('internal/util/debuglog').debuglog('repl', (fn) => {
    debug = fn;
  });
  
  const previewOptions = {
    colors: false,
    depth: 1,
    showHidden: false
  };
  
  // If the error is that we've unexpectedly ended the input,
  // then let the user try to recover by adding more input.
  // Note: `e` (the original exception) is not used by the current implementation,
  // but may be needed in the future.
  function isRecoverableError(e, code) {
    // For similar reasons as `defaultEval`, wrap expressions starting with a
    // curly brace with parenthesis.  Note: only the open parenthesis is added
    // here as the point is to test for potentially valid but incomplete
    // expressions.
    if (/^\s*\{/.test(code) && isRecoverableError(e, `(${code}`)) return true;
  
    let recoverable = false;
  
    // Determine if the point of any error raised is at the end of the input.
    // There are two cases to consider:
    //
    //   1.  Any error raised after we have encountered the 'eof' token.
    //       This prevents us from declaring partial tokens (like '2e') as
    //       recoverable.
    //
    //   2.  Three cases where tokens can legally span lines.  This is
    //       template, comment, and strings with a backslash at the end of
    //       the line, indicating a continuation.  Note that we need to look
    //       for the specific errors of 'unterminated' kind (not, for example,
    //       a syntax error in a ${} expression in a template), and the only
    //       way to do that currently is to look at the message.  Should Acorn
    //       change these messages in the future, this will lead to a test
    //       failure, indicating that this code needs to be updated.
    //
    const RecoverableParser = AcornParser
      .extend(
        privateMethods,
        classFields,
        numericSeparator,
        staticClassFeatures,
        (Parser) => {
          return class extends Parser {
            nextToken() {
              super.nextToken();
              if (this.type === tt.eof)
                recoverable = true;
            }
            raise(pos, message) {
              switch (message) {
                case 'Unterminated template':
                case 'Unterminated comment':
                  recoverable = true;
                  break;
  
                case 'Unterminated string constant':
                  const token = this.input.slice(this.lastTokStart, this.pos);
                  // See https://www.ecma-international.org/ecma-262/#sec-line-terminators
                  if (/\\(?:\r\n?|\n|\u2028|\u2029)$/.test(token)) {
                    recoverable = true;
                  }
              }
              super.raise(pos, message);
            }
          };
        }
      );
  
    // Try to parse the code with acorn.  If the parse fails, ignore the acorn
    // error and return the recoverable status.
    try {
      RecoverableParser.parse(code, { ecmaVersion: 11 });
  
      // Odd case: the underlying JS engine (V8, Chakra) rejected this input
      // but Acorn detected no issue.  Presume that additional text won't
      // address this issue.
      return false;
    } catch {
      return recoverable;
    }
  }
  
  function setupPreview(repl, contextSymbol, bufferSymbol, active) {
    // Simple terminals can't handle previews.
    if (process.env.TERM === 'dumb' || !active) {
      return { showPreview() {}, clearPreview() {} };
    }
  
    let inputPreview = null;
    let lastInputPreview = '';
  
    let previewCompletionCounter = 0;
    let completionPreview = null;
  
    let wrapped = false;
  
    function getPreviewPos() {
      const displayPos = repl._getDisplayPos(`${repl._prompt}${repl.line}`);
      const cursorPos = repl.line.length !== repl.cursor ?
        repl.getCursorPos() :
        displayPos;
      return { displayPos, cursorPos };
    }
  
    const clearPreview = () => {
      if (inputPreview !== null) {
        const { displayPos, cursorPos } = getPreviewPos();
        const rows = displayPos.rows - cursorPos.rows + 1;
        moveCursor(repl.output, 0, rows);
        clearLine(repl.output);
        moveCursor(repl.output, 0, -rows);
        lastInputPreview = inputPreview;
        inputPreview = null;
      }
      if (completionPreview !== null) {
        // Prevent cursor moves if not necessary!
        const move = repl.line.length !== repl.cursor;
        let pos, rows;
        if (move) {
          pos = getPreviewPos();
          cursorTo(repl.output, pos.displayPos.cols);
          rows = pos.displayPos.rows - pos.cursorPos.rows;
          moveCursor(repl.output, 0, rows);
        }
        const totalLine = `${repl._prompt}${repl.line}${completionPreview}`;
        const newPos = repl._getDisplayPos(totalLine);
        // Minimize work for the terminal. It is enough to clear the right part of
        // the current line in case the preview is visible on a single line.
        if (newPos.rows === 0 || (pos && pos.displayPos.rows === newPos.rows)) {
          clearLine(repl.output, 1);
        } else {
          clearScreenDown(repl.output);
        }
        if (move) {
          cursorTo(repl.output, pos.cursorPos.cols);
          moveCursor(repl.output, 0, -rows);
        }
        completionPreview = null;
      }
    };
  
    function showCompletionPreview(line, insertPreview) {
      previewCompletionCounter++;
  
      const count = previewCompletionCounter;
  
      repl.completer(line, (error, data) => {
        // Tab completion might be async and the result might already be outdated.
        if (count !== previewCompletionCounter) {
          return;
        }
  
        if (error) {
          debug('Error while generating completion preview', error);
          return;
        }
  
        // Result and the text that was completed.
        const [rawCompletions, completeOn] = data;
  
        if (!rawCompletions || rawCompletions.length === 0) {
          return;
        }
  
        // If there is a common prefix to all matches, then apply that portion.
        const completions = rawCompletions.filter((e) => e);
        const prefix = commonPrefix(completions);
  
        // No common prefix found.
        if (prefix.length <= completeOn.length) {
          return;
        }
  
        const suffix = prefix.slice(completeOn.length);
  
        if (insertPreview) {
          repl._insertString(suffix);
          return;
        }
  
        completionPreview = suffix;
  
        const result = repl.useColors ?
          `\u001b[90m${suffix}\u001b[39m` :
          ` // ${suffix}`;
  
        const { cursorPos, displayPos } = getPreviewPos();
        if (repl.line.length !== repl.cursor) {
          cursorTo(repl.output, displayPos.cols);
          moveCursor(repl.output, 0, displayPos.rows - cursorPos.rows);
        }
        repl.output.write(result);
        cursorTo(repl.output, cursorPos.cols);
        const totalLine = `${repl._prompt}${repl.line}${suffix}`;
        const newPos = repl._getDisplayPos(totalLine);
        const rows = newPos.rows - cursorPos.rows - (newPos.cols === 0 ? 1 : 0);
        moveCursor(repl.output, 0, -rows);
      });
    }
  
    // This returns a code preview for arbitrary input code.
    function getInputPreview(input, callback) {
      // For similar reasons as `defaultEval`, wrap expressions starting with a
      // curly brace with parenthesis.
      if (input.startsWith('{') && !input.endsWith(';') && !wrapped) {
        input = `(${input})`;
        wrapped = true;
      }
      sendInspectorCommand((session) => {
        session.post('Runtime.evaluate', {
          expression: input,
          throwOnSideEffect: true,
          timeout: 333,
          contextId: repl[contextSymbol],
        }, (error, preview) => {
          if (error) {
            callback(error);
            return;
          }
          const { result } = preview;
          if (result.value !== undefined) {
            callback(null, inspect(result.value, previewOptions));
          // Ignore EvalErrors, SyntaxErrors and ReferenceErrors. It is not clear
          // where they came from and if they are recoverable or not. Other errors
          // may be inspected.
          } else if (preview.exceptionDetails &&
                     (result.className === 'EvalError' ||
                       result.className === 'SyntaxError' ||
                       result.className === 'ReferenceError')) {
            callback(null, null);
          } else if (result.objectId) {
            // The writer options might change and have influence on the inspect
            // output. The user might change e.g., `showProxy`, `getters` or
            // `showHidden`. Use `inspect` instead of `JSON.stringify` to keep
            // `Infinity` and similar intact.
            const inspectOptions = inspect({
              ...repl.writer.options,
              colors: false,
              depth: 1,
              compact: true,
              breakLength: Infinity
            }, previewOptions);
            session.post('Runtime.callFunctionOn', {
              functionDeclaration: `(v) => util.inspect(v, ${inspectOptions})`,
              objectId: result.objectId,
              arguments: [result]
            }, (error, preview) => {
              if (error) {
                callback(error);
              } else {
                callback(null, preview.result.value);
              }
            });
          } else {
            // Either not serializable or undefined.
            callback(null, result.unserializableValue || result.type);
          }
        });
      }, () => callback(new ERR_INSPECTOR_NOT_AVAILABLE()));
    }
  
    const showPreview = () => {
      // Prevent duplicated previews after a refresh.
      if (inputPreview !== null || !repl.isCompletionEnabled) {
        return;
      }
  
      const line = repl.line.trim();
  
      // Do not preview in case the line only contains whitespace.
      if (line === '') {
        return;
      }
  
      // Add the autocompletion preview.
      // TODO(BridgeAR): Trigger the input preview after the completion preview.
      // That way it's possible to trigger the input prefix including the
      // potential completion suffix. To do so, we also have to change the
      // behavior of `enter` and `escape`:
      // Enter should automatically add the suffix to the current line as long as
      // escape was not pressed. We might even remove the preview in case any
      // cursor movement is triggered.
      const insertPreview = false;
      showCompletionPreview(repl.line, insertPreview);
  
      // Do not preview if the command is buffered.
      if (repl[bufferSymbol]) {
        return;
      }
  
      const inputPreviewCallback = (error, inspected) => {
        if (inspected === null) {
          return;
        }
  
        wrapped = false;
  
        // Ignore the output if the value is identical to the current line and the
        // former preview is not identical to this preview.
        if (line === inspected && lastInputPreview !== inspected) {
          return;
        }
  
        if (error) {
          debug('Error while generating preview', error);
          return;
        }
        // Do not preview `undefined` if colors are deactivated or explicitly
        // requested.
        if (inspected === 'undefined' &&
            (!repl.useColors || repl.ignoreUndefined)) {
          return;
        }
  
        inputPreview = inspected;
  
        // Limit the output to maximum 250 characters. Otherwise it becomes a)
        // difficult to read and b) non terminal REPLs would visualize the whole
        // output.
        let maxColumns = MathMin(repl.columns, 250);
  
        // Support unicode characters of width other than one by checking the
        // actual width.
        if (inspected.length * 2 >= maxColumns &&
            getStringWidth(inspected) > maxColumns) {
          maxColumns -= 4 + (repl.useColors ? 0 : 3);
          let res = '';
          for (const char of inspected) {
            maxColumns -= getStringWidth(char);
            if (maxColumns < 0)
              break;
            res += char;
          }
          inspected = `${res}...`;
        }
  
        // Line breaks are very rare and probably only occur in case of error
        // messages with line breaks.
        const lineBreakPos = inspected.indexOf('\n');
        if (lineBreakPos !== -1) {
          inspected = `${inspected.slice(0, lineBreakPos)}`;
        }
  
        const result = repl.useColors ?
          `\u001b[90m${inspected}\u001b[39m` :
          `// ${inspected}`;
  
        const { cursorPos, displayPos } = getPreviewPos();
        const rows = displayPos.rows - cursorPos.rows;
        moveCursor(repl.output, 0, rows);
        repl.output.write(`\n${result}`);
        cursorTo(repl.output, cursorPos.cols);
        moveCursor(repl.output, 0, -rows - 1);
      };
  
      getInputPreview(line, inputPreviewCallback);
      if (wrapped) {
        getInputPreview(line, inputPreviewCallback);
      }
      wrapped = false;
    };
  
    // -------------------------------------------------------------------------//
    // Replace multiple interface functions. This is required to fully support  //
    // previews without changing readlines behavior.                            //
    // -------------------------------------------------------------------------//
  
    // Refresh prints the whole screen again and the preview will be removed
    // during that procedure. Print the preview again. This also makes sure
    // the preview is always correct after resizing the terminal window.
    const originalRefresh = repl._refreshLine.bind(repl);
    repl._refreshLine = () => {
      inputPreview = null;
      originalRefresh();
      showPreview();
    };
  
    let insertCompletionPreview = true;
    // Insert the longest common suffix of the current input in case the user
    // moves to the right while already being at the current input end.
    const originalMoveCursor = repl._moveCursor.bind(repl);
    repl._moveCursor = (dx) => {
      const currentCursor = repl.cursor;
      originalMoveCursor(dx);
      if (currentCursor + dx > repl.line.length &&
          typeof repl.completer === 'function' &&
          insertCompletionPreview) {
        const insertPreview = true;
        showCompletionPreview(repl.line, insertPreview);
      }
    };
  
    // This is the only function that interferes with the completion insertion.
    // Monkey patch it to prevent inserting the completion when it shouldn't be.
    const originalClearLine = repl.clearLine.bind(repl);
    repl.clearLine = () => {
      insertCompletionPreview = false;
      originalClearLine();
      insertCompletionPreview = true;
    };
  
    return { showPreview, clearPreview };
  }
  
  function setupReverseSearch(repl) {
    // Simple terminals can't use reverse search.
    if (process.env.TERM === 'dumb') {
      return { reverseSearch() { return false; } };
    }
  
    const alreadyMatched = new Set();
    const labels = {
      r: 'bck-i-search: ',
      s: 'fwd-i-search: '
    };
    let isInReverseSearch = false;
    let historyIndex = -1;
    let input = '';
    let cursor = -1;
    let dir = 'r';
    let lastMatch = -1;
    let lastCursor = -1;
    let promptPos;
  
    function checkAndSetDirectionKey(keyName) {
      if (!labels[keyName]) {
        return false;
      }
      if (dir !== keyName) {
        // Reset the already matched set in case the direction is changed. That
        // way it's possible to find those entries again.
        alreadyMatched.clear();
        dir = keyName;
      }
      return true;
    }
  
    function goToNextHistoryIndex() {
      // Ignore this entry for further searches and continue to the next
      // history entry.
      alreadyMatched.add(repl.history[historyIndex]);
      historyIndex += dir === 'r' ? 1 : -1;
      cursor = -1;
    }
  
    function search() {
      // Just print an empty line in case the user removed the search parameter.
      if (input === '') {
        print(repl.line, `${labels[dir]}_`);
        return;
      }
      // Fix the bounds in case the direction has changed in the meanwhile.
      if (dir === 'r') {
        if (historyIndex < 0) {
          historyIndex = 0;
        }
      } else if (historyIndex >= repl.history.length) {
        historyIndex = repl.history.length - 1;
      }
      // Check the history entries until a match is found.
      while (historyIndex >= 0 && historyIndex < repl.history.length) {
        let entry = repl.history[historyIndex];
        // Visualize all potential matches only once.
        if (alreadyMatched.has(entry)) {
          historyIndex += dir === 'r' ? 1 : -1;
          continue;
        }
        // Match the next entry either from the start or from the end, depending
        // on the current direction.
        if (dir === 'r') {
          // Update the cursor in case it's necessary.
          if (cursor === -1) {
            cursor = entry.length;
          }
          cursor = entry.lastIndexOf(input, cursor - 1);
        } else {
          cursor = entry.indexOf(input, cursor + 1);
        }
        // Match not found.
        if (cursor === -1) {
          goToNextHistoryIndex();
        // Match found.
        } else {
          if (repl.useColors) {
            const start = entry.slice(0, cursor);
            const end = entry.slice(cursor + input.length);
            entry = `${start}\x1B[4m${input}\x1B[24m${end}`;
          }
          print(entry, `${labels[dir]}${input}_`, cursor);
          lastMatch = historyIndex;
          lastCursor = cursor;
          // Explicitly go to the next history item in case no further matches are
          // possible with the current entry.
          if ((dir === 'r' && cursor === 0) ||
              (dir === 's' && entry.length === cursor + input.length)) {
            goToNextHistoryIndex();
          }
          return;
        }
      }
      print(repl.line, `failed-${labels[dir]}${input}_`);
    }
  
    function print(outputLine, inputLine, cursor = repl.cursor) {
      // TODO(BridgeAR): Resizing the terminal window hides the overlay. To fix
      // that, readline must be aware of this information. It's probably best to
      // add a couple of properties to readline that allow to do the following:
      // 1. Add arbitrary data to the end of the current line while not counting
      //    towards the line. This would be useful for the completion previews.
      // 2. Add arbitrary extra lines that do not count towards the regular line.
      //    This would be useful for both, the input preview and the reverse
      //    search. It might be combined with the first part?
      // 3. Add arbitrary input that is "on top" of the current line. That is
      //    useful for the reverse search.
      // 4. To trigger the line refresh, functions should be used to pass through
      //    the information. Alternatively, getters and setters could be used.
      //    That might even be more elegant.
      // The data would then be accounted for when calling `_refreshLine()`.
      // This function would then look similar to:
      //   repl.overlay(outputLine);
      //   repl.addTrailingLine(inputLine);
      //   repl.setCursor(cursor);
      // More potential improvements: use something similar to stream.cork().
      // Multiple cursor moves on the same tick could be prevented in case all
      // writes from the same tick are combined and the cursor is moved at the
      // tick end instead of after each operation.
      let rows = 0;
      if (lastMatch !== -1) {
        const line = repl.history[lastMatch].slice(0, lastCursor);
        rows = repl._getDisplayPos(`${repl._prompt}${line}`).rows;
        cursorTo(repl.output, promptPos.cols);
      } else if (isInReverseSearch && repl.line !== '') {
        rows = repl.getCursorPos().rows;
        cursorTo(repl.output, promptPos.cols);
      }
      if (rows !== 0)
        moveCursor(repl.output, 0, -rows);
  
      if (isInReverseSearch) {
        clearScreenDown(repl.output);
        repl.output.write(`${outputLine}\n${inputLine}`);
      } else {
        repl.output.write(`\n${inputLine}`);
      }
  
      lastMatch = -1;
  
      // To know exactly how many rows we have to move the cursor back we need the
      // cursor rows, the output rows and the input rows.
      const prompt = repl._prompt;
      const cursorLine = `${prompt}${outputLine.slice(0, cursor)}`;
      const cursorPos = repl._getDisplayPos(cursorLine);
      const outputPos = repl._getDisplayPos(`${prompt}${outputLine}`);
      const inputPos = repl._getDisplayPos(inputLine);
      const inputRows = inputPos.rows - (inputPos.cols === 0 ? 1 : 0);
  
      rows = -1 - inputRows - (outputPos.rows - cursorPos.rows);
  
      moveCursor(repl.output, 0, rows);
      cursorTo(repl.output, cursorPos.cols);
    }
  
    function reset(string) {
      isInReverseSearch = string !== undefined;
  
      // In case the reverse search ends and a history entry is found, reset the
      // line to the found entry.
      if (!isInReverseSearch) {
        if (lastMatch !== -1) {
          repl.line = repl.history[lastMatch];
          repl.cursor = lastCursor;
          repl.historyIndex = lastMatch;
        }
  
        lastMatch = -1;
  
        // Clear screen and write the current repl.line before exiting.
        cursorTo(repl.output, promptPos.cols);
        moveCursor(repl.output, 0, promptPos.rows);
        clearScreenDown(repl.output);
        if (repl.line !== '') {
          repl.output.write(repl.line);
          if (repl.line.length !== repl.cursor) {
            const { cols, rows } = repl.getCursorPos();
            cursorTo(repl.output, cols);
            moveCursor(repl.output, 0, rows);
          }
        }
      }
  
      input = string || '';
      cursor = -1;
      historyIndex = repl.historyIndex;
      alreadyMatched.clear();
    }
  
    function reverseSearch(string, key) {
      if (!isInReverseSearch) {
        if (key.ctrl && checkAndSetDirectionKey(key.name)) {
          historyIndex = repl.historyIndex;
          promptPos = repl._getDisplayPos(`${repl._prompt}`);
          print(repl.line, `${labels[dir]}_`);
          isInReverseSearch = true;
        }
      } else if (key.ctrl && checkAndSetDirectionKey(key.name)) {
        search();
      } else if (key.name === 'backspace' ||
          (key.ctrl && (key.name === 'h' || key.name === 'w'))) {
        reset(input.slice(0, input.length - 1));
        search();
        // Special handle <ctrl> + c and escape. Those should only cancel the
        // reverse search. The original line is visible afterwards again.
      } else if ((key.ctrl && key.name === 'c') || key.name === 'escape') {
        lastMatch = -1;
        reset();
        return true;
        // End search in case either enter is pressed or if any non-reverse-search
        // key (combination) is pressed.
      } else if (key.ctrl ||
                 key.meta ||
                 key.name === 'return' ||
                 key.name === 'enter' ||
                 typeof string !== 'string' ||
                 string === '') {
        reset();
        repl[kSubstringSearch] = '';
      } else {
        reset(`${input}${string}`);
        search();
      }
      return isInReverseSearch;
    }
  
    return { reverseSearch };
  }
  
  module.exports = {
    isRecoverableError,
    kStandaloneREPL: Symbol('kStandaloneREPL'),
    setupPreview,
    setupReverseSearch
  };
  