text: 'use strict';
  
  // Flags: --expose-internals
  
  const common = require('../common');
  const stream = require('stream');
  const REPL = require('internal/repl');
  const assert = require('assert');
  const fs = require('fs');
  const path = require('path');
  const { inspect } = require('util');
  
  common.skipIfDumbTerminal();
  
  const tmpdir = require('../common/tmpdir');
  tmpdir.refresh();
  
  const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history');
  
  // Create an input stream specialized for testing an array of actions
  class ActionStream extends stream.Stream {
    run(data) {
      const _iter = data[Symbol.iterator]();
      const doAction = () => {
        const next = _iter.next();
        if (next.done) {
          // Close the repl. Note that it must have a clean prompt to do so.
          this.emit('keypress', '', { ctrl: true, name: 'd' });
          return;
        }
        const action = next.value;
  
        if (typeof action === 'object') {
          this.emit('keypress', '', action);
        } else {
          this.emit('data', `${action}`);
        }
        setImmediate(doAction);
      };
      doAction();
    }
    resume() {}
    pause() {}
  }
  ActionStream.prototype.readable = true;
  
  // Mock keys
  const ENTER = { name: 'enter' };
  const UP = { name: 'up' };
  const DOWN = { name: 'down' };
  const LEFT = { name: 'left' };
  const RIGHT = { name: 'right' };
  const DELETE = { name: 'delete' };
  const BACKSPACE = { name: 'backspace' };
  const WORD_LEFT = { name: 'left', ctrl: true };
  const WORD_RIGHT = { name: 'right', ctrl: true };
  const GO_TO_END = { name: 'end' };
  const DELETE_WORD_LEFT = { name: 'backspace', ctrl: true };
  const SIGINT = { name: 'c', ctrl: true };
  
  const prompt = '> ';
  const WAIT = '€';
  
  const prev = process.features.inspector;
  
  let completions = 0;
  
  const tests = [
    { // Creates few history to navigate for
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      test: [ 'let ab = 45', ENTER,
              '555 + 909', ENTER,
              '{key : {key2 :[] }}', ENTER,
              'Array(100).fill(1).map((e, i) => i ** i)', LEFT, LEFT, DELETE,
              '2', ENTER],
      expected: [],
      clean: false
    },
    {
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      test: [UP, UP, UP, UP, UP, DOWN, DOWN, DOWN, DOWN, DOWN],
      expected: [prompt,
                 `${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
                 prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
                   '144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529,' +
                   ' 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, ' +
                   '1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936,' +
                   ' 2025, 2116, 2209,...',
                 `${prompt}{key : {key2 :[] }}`,
                 prev && '\n// { key: { key2: [] } }',
                 `${prompt}555 + 909`,
                 prev && '\n// 1464',
                 `${prompt}let ab = 45`,
                 prompt,
                 `${prompt}let ab = 45`,
                 `${prompt}555 + 909`,
                 prev && '\n// 1464',
                 `${prompt}{key : {key2 :[] }}`,
                 prev && '\n// { key: { key2: [] } }',
                 `${prompt}Array(100).fill(1).map((e, i) => i ** 2)`,
                 prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' +
                   '144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529,' +
                   ' 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, ' +
                   '1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936,' +
                   ' 2025, 2116, 2209,...',
                 prompt].filter((e) => typeof e === 'string'),
      clean: false
    },
    { // Creates more history entries to navigate through.
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      test: [
        '555 + 909', ENTER, // Add a duplicate to the history set.
        'const foo = true', ENTER,
        '555n + 111n', ENTER,
        '5 + 5', ENTER,
        '55 - 13 === 42', ENTER
      ],
      expected: [],
      clean: false
    },
    {
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      checkTotal: true,
      preview: false,
      showEscapeCodes: true,
      test: [
        '55', UP, UP, UP, UP, UP, UP, ENTER
      ],
      expected: [
        '\x1B[1G', '\x1B[0J', prompt, '\x1B[3G',
        // '55'
        '5', '5',
        // UP
        '\x1B[1G', '\x1B[0J',
        '> 55 - 13 === 42', '\x1B[17G',
        // UP - skipping 5 + 5
        '\x1B[1G', '\x1B[0J',
        '> 555n + 111n', '\x1B[14G',
        // UP - skipping const foo = true
        '\x1B[1G', '\x1B[0J',
        '> 555 + 909', '\x1B[12G',
        // UP, UP
        // UPs at the end of the history reset the line to the original input.
        '\x1B[1G', '\x1B[0J',
        '> 55', '\x1B[5G',
        // ENTER
        '\r\n', '55\n',
        '\x1B[1G', '\x1B[0J',
        '> ', '\x1B[3G',
        '\r\n'
      ],
      clean: true
    },
    {
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      skip: !process.features.inspector,
      test: [
        // あ is a full width character with a length of one.
        // 🐕 is a full width character with a length of two.
        // 𐐷 is a half width character with the length of two.
        // '\u0301', '0x200D', '\u200E' are zero width characters.
        `const x1 = '${'あ'.repeat(124)}'`, ENTER, // Fully visible
        ENTER,
        `const y1 = '${'あ'.repeat(125)}'`, ENTER, // Cut off
        ENTER,
        `const x2 = '${'🐕'.repeat(124)}'`, ENTER, // Fully visible
        ENTER,
        `const y2 = '${'🐕'.repeat(125)}'`, ENTER, // Cut off
        ENTER,
        `const x3 = '${'𐐷'.repeat(248)}'`, ENTER, // Fully visible
        ENTER,
        `const y3 = '${'𐐷'.repeat(249)}'`, ENTER, // Cut off
        ENTER,
        `const x4 = 'a${'\u0301'.repeat(1000)}'`, ENTER, // á
        ENTER,
        `const ${'veryLongName'.repeat(30)} = 'I should be previewed'`,
        ENTER,
        'const e = new RangeError("visible\\ninvisible")',
        ENTER,
        'e',
        ENTER,
        'veryLongName'.repeat(30),
        ENTER,
        `${'\x1B[90m \x1B[39m'.repeat(235)} fun`,
        ENTER,
        `${' '.repeat(236)} fun`,
        ENTER
      ],
      expected: [],
      clean: false
    },
    {
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      columns: 250,
      checkTotal: true,
      showEscapeCodes: true,
      skip: !process.features.inspector,
      test: [
        UP,
        UP,
        UP,
        WORD_LEFT,
        UP,
        BACKSPACE,
        'x1',
        BACKSPACE,
        '2',
        BACKSPACE,
        '3',
        BACKSPACE,
        '4',
        DELETE_WORD_LEFT,
        'y1',
        BACKSPACE,
        '2',
        BACKSPACE,
        '3',
        SIGINT
      ],
      // A = Cursor n up
      // B = Cursor n down
      // C = Cursor n forward
      // D = Cursor n back
      // G = Cursor to column n
      // J = Erase in screen; 0 = right; 1 = left; 2 = total
      // K = Erase in line; 0 = right; 1 = left; 2 = total
      expected: [
        // 0. Start
        '\x1B[1G', '\x1B[0J',
        prompt, '\x1B[3G',
        // 1. UP
        // This exceeds the maximum columns (250):
        // Whitespace + prompt + ' // '.length + 'function'.length
        // 236 + 2 + 4 + 8
        '\x1B[1G', '\x1B[0J',
        `${prompt}${' '.repeat(236)} fun`, '\x1B[243G',
        ' // ction', '\x1B[243G',
        ' // ction', '\x1B[243G',
        '\x1B[0K',
        // 2. UP
        '\x1B[1G', '\x1B[0J',
        `${prompt}${' '.repeat(235)} fun`, '\x1B[242G',
        // TODO(BridgeAR): Investigate why the preview is generated twice.
        ' // ction', '\x1B[242G',
        ' // ction', '\x1B[242G',
        // Preview cleanup
        '\x1B[0K',
        // 3. UP
        '\x1B[1G', '\x1B[0J',
        // 'veryLongName'.repeat(30).length === 360
        // prompt.length === 2
        // 360 % 250 + 2 === 112 (+1)
        `${prompt}${'veryLongName'.repeat(30)}`, '\x1B[113G',
        // "// 'I should be previewed'".length + 86 === 112 (+1)
        "\n// 'I should be previewed'", '\x1B[113G', '\x1B[1A',
        // Preview cleanup
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        // 4. WORD LEFT
        // Almost identical as above. Just one extra line.
        // Math.floor(360 / 250) === 1
        '\x1B[1A',
        '\x1B[1G', '\x1B[0J',
        `${prompt}${'veryLongName'.repeat(30)}`, '\x1B[3G', '\x1B[1A',
        '\x1B[1B', "\n// 'I should be previewed'", '\x1B[3G', '\x1B[2A',
        // Preview cleanup
        '\x1B[2B', '\x1B[2K', '\x1B[2A',
        // 5. UP
        '\x1B[1G', '\x1B[0J',
        `${prompt}e`, '\x1B[4G',
        // '// RangeError: visible'.length - 19 === 3 (+1)
        '\n// RangeError: visible', '\x1B[4G', '\x1B[1A',
        // Preview cleanup
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        // 6. Backspace
        '\x1B[1G', '\x1B[0J',
        '> ', '\x1B[3G', 'x', '1',
        `\n// '${'あ'.repeat(124)}'`,
        '\x1B[5G', '\x1B[1A',
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        '\x1B[1G', '\x1B[0J',
        '> x', '\x1B[4G', '2',
        `\n// '${'🐕'.repeat(124)}'`,
        '\x1B[5G', '\x1B[1A',
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        '\x1B[1G', '\x1B[0J',
        '> x', '\x1B[4G', '3',
        `\n// '${'𐐷'.repeat(248)}'`,
        '\x1B[5G', '\x1B[1A',
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        '\x1B[1G', '\x1B[0J',
        '> x', '\x1B[4G', '4',
        `\n// 'a${'\u0301'.repeat(1000)}'`,
        '\x1B[5G', '\x1B[1A',
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        '\x1B[1G', '\x1B[0J',
        '> ', '\x1B[3G', 'y', '1',
        `\n// '${'あ'.repeat(121)}...`,
        '\x1B[5G', '\x1B[1A',
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        '\x1B[1G', '\x1B[0J',
        '> y', '\x1B[4G', '2',
        `\n// '${'🐕'.repeat(121)}...`,
        '\x1B[5G', '\x1B[1A',
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        '\x1B[1G', '\x1B[0J',
        '> y', '\x1B[4G', '3',
        `\n// '${'𐐷'.repeat(242)}...`,
        '\x1B[5G', '\x1B[1A',
        '\x1B[1B', '\x1B[2K', '\x1B[1A',
        '\r\n',
        '\x1B[1G', '\x1B[0J',
        '> ', '\x1B[3G',
        '\r\n'
      ],
      clean: true
    },
    {
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      showEscapeCodes: true,
      skip: !process.features.inspector,
      test: [
        'fu',
        'n',
        RIGHT,
        BACKSPACE,
        LEFT,
        LEFT,
        'A',
        BACKSPACE,
        GO_TO_END,
        BACKSPACE,
        WORD_LEFT,
        WORD_RIGHT,
        ENTER
      ],
      // C = Cursor n forward
      // D = Cursor n back
      // G = Cursor to column n
      // J = Erase in screen; 0 = right; 1 = left; 2 = total
      // K = Erase in line; 0 = right; 1 = left; 2 = total
      expected: [
        // 0.
        // 'f'
        '\x1B[1G', '\x1B[0J', prompt, '\x1B[3G', 'f',
        // 'u'
        'u', ' // nction', '\x1B[5G',
        // 'n' - Cleanup
        '\x1B[0K',
        'n', ' // ction', '\x1B[6G',
        // 1. Right. Cleanup
        '\x1B[0K',
        'ction',
        // 2. Backspace. Refresh
        '\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[10G',
        // Autocomplete and refresh?
        ' // n', '\x1B[10G', ' // n', '\x1B[10G',
        // 3. Left. Cleanup
        '\x1B[0K',
        '\x1B[1D', '\x1B[10G', ' // n', '\x1B[9G',
        // 4. Left. Cleanup
        '\x1B[10G', '\x1B[0K', '\x1B[9G',
        '\x1B[1D', '\x1B[10G', ' // n', '\x1B[8G',
        // 5. 'A' - Cleanup
        '\x1B[10G', '\x1B[0K', '\x1B[8G',
        // Refresh
        '\x1B[1G', '\x1B[0J', `${prompt}functAio`, '\x1B[9G',
        // 6. Backspace. Refresh
        '\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[8G', '\x1B[10G', ' // n',
        '\x1B[8G', '\x1B[10G', ' // n',
        '\x1B[8G', '\x1B[10G',
        // 7. Go to end. Cleanup
        '\x1B[0K', '\x1B[8G', '\x1B[2C',
        'n',
        // 8. Backspace. Refresh
        '\x1B[1G', '\x1B[0J', `${prompt}functio`, '\x1B[10G',
        // Autocomplete
        ' // n', '\x1B[10G', ' // n', '\x1B[10G',
        // 9. Word left. Cleanup
        '\x1B[0K', '\x1B[7D', '\x1B[10G', ' // n', '\x1B[3G', '\x1B[10G',
        // 10. Word right. Cleanup
        '\x1B[0K', '\x1B[3G', '\x1B[7C', ' // n', '\x1B[10G',
        '\x1B[0K',
        // 11. ENTER
        '\r\n',
        'Uncaught ReferenceError: functio is not defined\n',
        '\x1B[1G', '\x1B[0J',
        prompt, '\x1B[3G', '\r\n'
      ],
      clean: true
    },
    {
      // Check changed inspection defaults.
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      skip: !process.features.inspector,
      test: [
        'util.inspect.replDefaults.showHidden',
        ENTER
      ],
      expected: [],
      clean: false
    },
    {
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      skip: !process.features.inspector,
      checkTotal: true,
      test: [
        '[ ]',
        WORD_LEFT,
        WORD_LEFT,
        UP,
        ' = true',
        ENTER,
        '[ ]',
        ENTER
      ],
      expected: [
        prompt,
        '[', ' ', ']',
        '\n// []', '\n// []', '\n// []',
        '> util.inspect.replDefaults.showHidden',
        '\n// false',
        ' ', '=', ' ', 't', 'r', 'u', 'e',
        'true\n',
        '> ', '[', ' ', ']',
        '\n// [ [length]: 0 ]',
        '[ [length]: 0 ]\n',
        '> ',
      ],
      clean: true
    },
    {
      // Check that the completer ignores completions that are outdated.
      env: { NODE_REPL_HISTORY: defaultHistoryPath },
      completer(line, callback) {
        if (line.endsWith(WAIT)) {
          if (completions++ === 0) {
            callback(null, [[`${WAIT}WOW`], line]);
          } else {
            setTimeout(callback, 1000, null, [[`${WAIT}WOW`], line]).unref();
          }
        } else {
          callback(null, [[' Always visible'], line]);
        }
      },
      skip: !process.features.inspector,
      test: [
        WAIT, // The first call is awaited before new input is triggered!
        BACKSPACE,
        's',
        BACKSPACE,
        WAIT, // The second call is not awaited. It won't trigger the preview.
        BACKSPACE,
        's',
        BACKSPACE
      ],
      expected: [
        prompt,
        WAIT,
        ' // WOW',
        prompt,
        's',
        ' // Always visible',
        prompt,
        WAIT,
        prompt,
        's',
        ' // Always visible',
        prompt,
      ],
      clean: true
    }
  ];
  const numtests = tests.length;
  
  const runTestWrap = common.mustCall(runTest, numtests);
  
  function cleanupTmpFile() {
    try {
      // Write over the file, clearing any history
      fs.writeFileSync(defaultHistoryPath, '');
    } catch (err) {
      if (err.code === 'ENOENT') return true;
      throw err;
    }
    return true;
  }
  
  function runTest() {
    const opts = tests.shift();
    if (!opts) return; // All done
  
    const { expected, skip } = opts;
  
    // Test unsupported on platform.
    if (skip) {
      setImmediate(runTestWrap, true);
      return;
    }
    const lastChunks = [];
    let i = 0;
  
    REPL.createInternalRepl(opts.env, {
      input: new ActionStream(),
      output: new stream.Writable({
        write(chunk, _, next) {
          const output = chunk.toString();
  
          if (!opts.showEscapeCodes &&
              (output[0] === '\x1B' || /^[\r\n]+$/.test(output))) {
            return next();
          }
  
          lastChunks.push(output);
  
          if (expected.length && !opts.checkTotal) {
            try {
              assert.strictEqual(output, expected[i]);
            } catch (e) {
              console.error(`Failed test # ${numtests - tests.length}`);
              console.error('Last outputs: ' + inspect(lastChunks, {
                breakLength: 5, colors: true
              }));
              throw e;
            }
            // TODO(BridgeAR): Auto close on last chunk!
            i++;
          }
  
          next();
        }
      }),
      completer: opts.completer,
      prompt,
      useColors: false,
      preview: opts.preview,
      terminal: true
    }, function(err, repl) {
      if (err) {
        console.error(`Failed test # ${numtests - tests.length}`);
        throw err;
      }
  
      repl.once('close', () => {
        if (opts.clean)
          cleanupTmpFile();
  
        if (opts.checkTotal) {
          assert.deepStrictEqual(lastChunks, expected);
        } else if (expected.length !== i) {
          console.error(tests[numtests - tests.length - 1]);
          throw new Error(`Failed test # ${numtests - tests.length}`);
        }
  
        setImmediate(runTestWrap, true);
      });
  
      if (opts.columns) {
        Object.defineProperty(repl, 'columns', {
          value: opts.columns,
          enumerable: true
        });
      }
      repl.input.run(opts.test);
    });
  }
  
  // run the tests
  runTest();
  