'use strict';

const common = require('../common');

// Can't process.chdir() in worker.
const { isMainThread } = require('worker_threads');

if (!isMainThread) {
  common.skip('This test only works on a main thread');
}

const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const url = require('url');
const fs = require('fs');
const {
  compileFunction,
  Script,
  createContext,
  constants: { USE_MAIN_CONTEXT_DEFAULT_LOADER },
} = require('vm');
const assert = require('assert');

common.expectWarning('ExperimentalWarning',
                     'vm.USE_MAIN_CONTEXT_DEFAULT_LOADER is an experimental feature and might change at any time');

assert(
  !process.execArgv.includes('--experimental-vm-modules'),
  'This test must be run without --experimental-vm-modules');

assert.strictEqual(typeof USE_MAIN_CONTEXT_DEFAULT_LOADER, 'symbol');

async function testNotFoundErrors(options) {
  // Import user modules.
  const script = new Script('import("./message.mjs")', options);
  // Use try-catch for better async stack traces in the logs.
  await assert.rejects(script.runInThisContext(), { code: 'ERR_MODULE_NOT_FOUND' });

  const imported = compileFunction('return import("./message.mjs")', [], options)();
  // Use try-catch for better async stack traces in the logs.
  await assert.rejects(imported, { code: 'ERR_MODULE_NOT_FOUND' });
}

async function testLoader(options) {
  {
    // Import built-in modules
    const script = new Script('import("fs")', options);
    let result = await script.runInThisContext();
    assert.strictEqual(result.constants.F_OK, fs.constants.F_OK);

    const imported = compileFunction('return import("fs")', [], options)();
    result = await imported;
    assert.strictEqual(result.constants.F_OK, fs.constants.F_OK);
  }

  const moduleUrl = fixtures.fileURL('es-modules', 'message.mjs');
  fs.copyFileSync(moduleUrl, tmpdir.resolve('message.mjs'));

  {
    const namespace = await import(moduleUrl);
    const script = new Script('import("./message.mjs")', options);
    const result = await script.runInThisContext();
    assert.deepStrictEqual(result, namespace);
  }

  {
    const namespace = await import(moduleUrl);
    const imported = compileFunction('return import("./message.mjs")', [], options)();
    const result = await imported;
    assert.deepStrictEqual(result, namespace);
  }
}

async function main() {
  {
    // Importing with absolute path as filename.
    tmpdir.refresh();
    const filename = tmpdir.resolve('index.js');
    const options = {
      filename,
      importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER
    };
    await testNotFoundErrors(options);
    await testLoader(options);
  }

  {
    // Importing with file:// URL as filename.
    tmpdir.refresh();
    // We use a search parameter to bypass caching.
    const filename = url.pathToFileURL(tmpdir.resolve('index.js')).href + '?t=1';
    const options = {
      filename,
      importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER
    };
    await testNotFoundErrors(options);
    await testLoader(options);
  }

  {
    // For undefined or non-path/URL filenames, import() should resolve to the cwd.
    tmpdir.refresh();
    process.chdir(tmpdir.path);
    const undefinedOptions = {
      importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER
    };
    const nonPathOptions = {
      filename: 'non-path',
      importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER
    };
    // Run the error tests first to avoid caching.
    await testNotFoundErrors(undefinedOptions);
    await testNotFoundErrors(nonPathOptions);

    // createContext() with null referrer also resolves to cwd.
    {
      const options = {
        importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER,
      };
      const ctx = createContext({}, options);
      const s = new Script('Promise.resolve("import(\'./message.mjs\')").then(eval)', {
        importModuleDynamically: common.mustNotCall(),
      });
      await assert.rejects(s.runInContext(ctx), { code: 'ERR_MODULE_NOT_FOUND' });
    }

    await testLoader(undefinedOptions);
    await testLoader(nonPathOptions);

    {
      const options = {
        importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER,
      };
      const ctx = createContext({}, options);
      const moduleUrl = fixtures.fileURL('es-modules', 'message.mjs');
      const namespace = await import(moduleUrl.href);
      const script = new Script('Promise.resolve("import(\'./message.mjs\')").then(eval)', {
        importModuleDynamically: common.mustNotCall(),
      });
      const result = await script.runInContext(ctx);
      assert.deepStrictEqual(result, namespace);
    }
  }
}

main().catch(common.mustNotCall());
