'use strict';

const common = require('../common');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const { builtinModules } = require('module');
const publicModules = builtinModules.filter((lib) => !lib.startsWith('_'));

const { isMainThread } = require('worker_threads');

if (!isMainThread) {
  common.skip('process.chdir is not available in Workers');
}

// We have to change the directory to ../fixtures before requiring repl
// in order to make the tests for completion of node_modules work properly
// since repl modifies module.paths.
process.chdir(fixtures.fixturesDir);

const repl = require('repl');
const { startNewREPLServer } = require('../common/repl');

// Tab completion on require on builtin modules works
{
  const { replServer } = startNewREPLServer();

  replServer.complete(
    "require('",
    common.mustCall(function(error, data) {
      assert.strictEqual(error, null);
      publicModules.forEach((lib) => {
        assert(
          data[0].includes(lib) &&
            (lib.startsWith('node:') || data[0].includes(`node:${lib}`)),
          `${lib} not found`
        );
      });
      const newModule = 'foobar';
      assert(!builtinModules.includes(newModule));
      repl.builtinModules.push(newModule);
      replServer.complete(
        "require('",
        common.mustCall((_, [modules]) => {
          assert.strictEqual(data[0].length + 1, modules.length);
          assert(modules.includes(newModule));
        })
      );
    })
  );
}

// Tab completion on require on builtin modules works (with extra spaces and "n" prefix)
{
  const { replServer } = startNewREPLServer();

  replServer.complete(
    "require\t( 'n",
    common.mustCall(function(error, data) {
      assert.strictEqual(error, null);
      assert.strictEqual(data.length, 2);
      assert.strictEqual(data[1], 'n');
      // require(...) completions include `node:`-prefixed modules:
      let lastIndex = -1;

      for (const lib of publicModules.filter((lib) => !lib.startsWith('node:'))) {
        lastIndex = data[0].indexOf(`node:${lib}`);
        assert.notStrictEqual(lastIndex, -1);
      }
      assert.strictEqual(data[0][lastIndex + 1], '');
      // There is only one Node.js module that starts with n:
      assert.strictEqual(data[0][lastIndex + 2], 'net');
      assert.strictEqual(data[0][lastIndex + 3], '');
      // It's possible to pick up non-core modules too
      for (const completion of data[0].slice(lastIndex + 4)) {
        assert.match(completion, /^n/);
      }
    })
  );
}

// Tab completion on require on external modules works
{
  const expected = ['@nodejsscope', '@nodejsscope/'];

  const { replServer } = startNewREPLServer();

  // Require calls should handle all types of quotation marks.
  for (const quotationMark of ["'", '"', '`']) {
    replServer.complete(
      'require(`@nodejs',
      common.mustCall((err, data) => {
        assert.strictEqual(err, null);
        assert.deepStrictEqual(data, [expected, '@nodejs']);
      })
    );

    // Completions should not be greedy in case the quotation ends.
    const input = `require(${quotationMark}@nodejsscope${quotationMark}`;
    replServer.complete(
      input,
      common.mustCall((err, data) => {
        assert.strictEqual(err, null);
        assert.deepStrictEqual(data, [[], undefined]);
      })
    );
  }
}

{
  // Completions should find modules and handle whitespace after the opening bracket.
  const { replServer } = startNewREPLServer();

  replServer.complete(
    'require \t("no_ind',
    common.mustCall((err, data) => {
      assert.strictEqual(err, null);
      assert.deepStrictEqual(data, [['no_index', 'no_index/'], 'no_ind']);
    })
  );
}

// Test tab completion for require() relative to the current directory
{
  const { replServer } = startNewREPLServer();

  const cwd = process.cwd();
  process.chdir(__dirname);

  ["require('.", 'require(".'].forEach((input) => {
    replServer.complete(
      input,
      common.mustCall((err, data) => {
        assert.strictEqual(err, null);
        assert.strictEqual(data.length, 2);
        assert.strictEqual(data[1], '.');
        assert.strictEqual(data[0].length, 2);
        assert.ok(data[0].includes('./'));
        assert.ok(data[0].includes('../'));
      })
    );
  });

  ["require('..", 'require("..'].forEach((input) => {
    replServer.complete(
      input,
      common.mustCall((err, data) => {
        assert.strictEqual(err, null);
        assert.deepStrictEqual(data, [['../'], '..']);
      })
    );
  });

  ['./', './test-'].forEach((path) => {
    [`require('${path}`, `require("${path}`].forEach((input) => {
      replServer.complete(
        input,
        common.mustCall((err, data) => {
          assert.strictEqual(err, null);
          assert.strictEqual(data.length, 2);
          assert.strictEqual(data[1], path);
          assert.ok(data[0].includes('./test-repl-tab-complete'));
        })
      );
    });
  });

  ['../parallel/', '../parallel/test-'].forEach((path) => {
    [`require('${path}`, `require("${path}`].forEach((input) => {
      replServer.complete(
        input,
        common.mustCall((err, data) => {
          assert.strictEqual(err, null);
          assert.strictEqual(data.length, 2);
          assert.strictEqual(data[1], path);
          assert.ok(data[0].includes('../parallel/test-repl-tab-complete'));
        })
      );
    });
  });

  {
    const path = '../fixtures/repl-folder-extensions/f';
    replServer.complete(
      `require('${path}`,
      common.mustSucceed((data) => {
        assert.strictEqual(data.length, 2);
        assert.strictEqual(data[1], path);
        assert.ok(
          data[0].includes('../fixtures/repl-folder-extensions/foo.js')
        );
      })
    );
  }

  process.chdir(cwd);
}
