/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *  strict-local
 * @format
 */
"use strict";

const addParamsToDefineCall = require("../../lib/addParamsToDefineCall");

const generate = require("../worker/generate");

const mergeSourceMaps = require("../worker/mergeSourceMaps");

const reverseDependencyMapReferences = require("./reverse-dependency-map-references");

const virtualModule = require("../module").virtual;

const _require = require("@babel/core"),
  transformSync = _require.transformSync;

// Transformed modules have the form
//   __d(function(require, module, global, exports, dependencyMap) {
//       /* code */
//   });
//
// This function adds the numeric module ID, and an array with dependencies of
// the dependencies of the module before the closing parenthesis.
function addModuleIdsToModuleWrapper(module, idForPath) {
  const dependencies = module.dependencies,
    file = module.file;
  const code = file.code; // calling `idForPath` on the module itself first gives us a lower module id
  // for the file itself than for its dependencies. That reflects their order
  // in the bundle.

  const fileId = idForPath(file);
  const paramsToAdd = [fileId];

  if (dependencies.length) {
    paramsToAdd.push(dependencies.map(idForPath));
  }

  return addParamsToDefineCall.apply(void 0, [code].concat(paramsToAdd));
}

exports.addModuleIdsToModuleWrapper = addModuleIdsToModuleWrapper;

function inlineModuleIds(module, idForPath) {
  const dependencies = module.dependencies,
    file = module.file;
  const code = file.code,
    map = file.map,
    path = file.path; // calling `idForPath` on the module itself first gives us a lower module id
  // for the file itself than for its dependencies. That reflects their order
  // in the bundle.

  const fileId = idForPath(file);
  const dependencyIds = dependencies.map(idForPath);

  const _transformSync = transformSync(code, {
      ast: true,
      babelrc: false,
      code: false,
      configFile: false,
      plugins: [
        [
          reverseDependencyMapReferences,
          {
            dependencyIds
          }
        ]
      ]
    }),
    ast = _transformSync.ast;

  const _generate = generate(ast, path, "", true),
    generatedCode = _generate.code,
    generatedMap = _generate.map;

  return {
    moduleCode: addParamsToDefineCall(generatedCode, fileId),
    moduleMap: map && generatedMap && mergeSourceMaps(path, map, generatedMap)
  };
}

exports.inlineModuleIds = inlineModuleIds;

// Adds the module ids to a file if the file is a module. If it's not (e.g. a
// script) it just keeps it as-is.
function getModuleCodeAndMap(module, idForPath, options) {
  const file = module.file;

  if (file.type !== "module") {
    return {
      moduleCode: file.code,
      moduleMap: file.map
    };
  }

  if (!options.enableIDInlining) {
    return {
      moduleCode: addModuleIdsToModuleWrapper(module, idForPath),
      moduleMap: file.map
    };
  }

  return inlineModuleIds(module, idForPath);
}

exports.getModuleCodeAndMap = getModuleCodeAndMap; // Concatenates many iterables, by calling them sequentially.

exports.concat = function* concat() {
  for (
    var _len = arguments.length, iterables = new Array(_len), _key = 0;
    _key < _len;
    _key++
  ) {
    iterables[_key] = arguments[_key];
  }

  for (const it of iterables) {
    yield* it;
  }
}; // Creates an idempotent function that returns numeric IDs for objects based
// on their `path` property.

exports.createIdForPathFn = () => {
  const seen = new Map();
  let next = 0;
  return _ref => {
    let path = _ref.path;
    let id = seen.get(path);

    if (id == null) {
      id = next++;
      seen.set(path, id);
    }

    return id;
  };
}; // creates a series of virtual modules with require calls to the passed-in
// modules.

exports.requireCallsTo = function*(modules, idForPath, getRunModuleStatement) {
  for (const module of modules) {
    const id = idForPath(module.file);
    yield virtualModule(
      getRunModuleStatement(id),
      `/<generated>/require-${id}.js`
    );
  }
}; // Divides the modules into two types: the ones that are loaded at startup, and
// the ones loaded deferredly (lazy loaded).

exports.partition = (modules, preloadedModules) => {
  const startup = [];
  const deferred = [];

  for (const module of modules) {
    (preloadedModules.has(module.file.path) ? startup : deferred).push(module);
  }

  return [startup, deferred];
}; // Transforms a new Module object into an old one, so that it can be passed
// around code.

exports.toModuleTransport = (module, idsForPath) => {
  const dependencies = module.dependencies,
    file = module.file;

  const _getModuleCodeAndMap = getModuleCodeAndMap(
      module,
      x => idsForPath(x).moduleId,
      {
        enableIDInlining: true
      }
    ),
    moduleCode = _getModuleCodeAndMap.moduleCode,
    moduleMap = _getModuleCodeAndMap.moduleMap;

  return {
    code: moduleCode,
    dependencies,
    // ID is required but we provide an invalid one for "script"s.
    id: file.type === "module" ? idsForPath(file).localId : -1,
    map: moduleMap,
    name: file.path,
    sourcePath: file.path
  };
};