/**
 * 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.
 *
 * @format
 *
 */
"use strict";
/* eslint-disable lint/no-unclear-flowtypes */

const t = require("@babel/types");

const template = require("@babel/template").default;

const traverse = require("@babel/traverse").default;

const WRAP_NAME = "$$_REQUIRE"; // note: babel will prefix this with _
// Check first the `global` variable as the global object. This way serializers
// can create a local variable called global to fake it as a global object
// without having to pollute the window object on web.

const IIFE_PARAM = template(
  "typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this"
);

function wrapModule(
  fileAst,
  importDefaultName,
  importAllName,
  dependencyMapName
) {
  const params = buildParameters(
    importDefaultName,
    importAllName,
    dependencyMapName
  );
  const factory = functionFromProgram(fileAst.program, params);
  const def = t.callExpression(t.identifier("__d"), [factory]);
  const ast = t.file(t.program([t.expressionStatement(def)]));
  const requireName = renameRequires(ast);
  return {
    ast,
    requireName
  };
}

function wrapPolyfill(fileAst) {
  const factory = functionFromProgram(fileAst.program, ["global"]);
  const iife = t.callExpression(factory, [IIFE_PARAM().expression]);
  return t.file(t.program([t.expressionStatement(iife)]));
}

function wrapJson(source) {
  // Unused parameters; remember that's wrapping JSON.
  const moduleFactoryParameters = buildParameters(
    "_aUnused",
    "_bUnused",
    "_cUnused"
  );
  return [
    `__d(function(${moduleFactoryParameters.join(", ")}) {`,
    `  module.exports = ${source};`,
    "});"
  ].join("\n");
}

function functionFromProgram(program, parameters) {
  return t.functionExpression(
    t.identifier(""),
    parameters.map(makeIdentifier),
    t.blockStatement(program.body, program.directives)
  );
}

function makeIdentifier(name) {
  return t.identifier(name);
}

function buildParameters(importDefaultName, importAllName, dependencyMapName) {
  return [
    "global",
    "require",
    importDefaultName,
    importAllName,
    "module",
    "exports",
    dependencyMapName
  ];
}

function renameRequires(ast) {
  let newRequireName = WRAP_NAME;
  traverse(ast, {
    Program(path) {
      const body = path.get("body.0.expression.arguments.0.body");
      newRequireName = body.scope.generateUid(WRAP_NAME);
      body.scope.rename("require", newRequireName);
    }
  });
  return newRequireName;
}

module.exports = {
  WRAP_NAME,
  wrapJson,
  wrapModule,
  wrapPolyfill
};