"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true,
});
exports.getContextModuleTemplate = getContextModuleTemplate;
var path = _interopRequireWildcard(require("path"));
var os = _interopRequireWildcard(require("os"));
function _getRequireWildcardCache(nodeInterop) {
  if (typeof WeakMap !== "function") return null;
  var cacheBabelInterop = new WeakMap();
  var cacheNodeInterop = new WeakMap();
  return (_getRequireWildcardCache = function (nodeInterop) {
    return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  })(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
  if (!nodeInterop && obj && obj.__esModule) {
    return obj;
  }
  if (obj === null || (typeof obj !== "object" && typeof obj !== "function")) {
    return { default: obj };
  }
  var cache = _getRequireWildcardCache(nodeInterop);
  if (cache && cache.has(obj)) {
    return cache.get(obj);
  }
  var newObj = {};
  var hasPropertyDescriptor =
    Object.defineProperty && Object.getOwnPropertyDescriptor;
  for (var key in obj) {
    if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
      var desc = hasPropertyDescriptor
        ? Object.getOwnPropertyDescriptor(obj, key)
        : null;
      if (desc && (desc.get || desc.set)) {
        Object.defineProperty(newObj, key, desc);
      } else {
        newObj[key] = obj[key];
      }
    }
  }
  newObj.default = obj;
  if (cache) {
    cache.set(obj, newObj);
  }
  return newObj;
}
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 * @format
 * @oncall react_native
 */

function createFileMap(modulePath, files, processModule) {
  let mapString = "\n";
  files
    .slice()
    // Sort for deterministic output
    .sort()
    .forEach((file) => {
      let filePath = path.relative(modulePath, file);
      if (os.platform() === "win32") {
        filePath = filePath.replace(/\\/g, "/");
      }

      // NOTE(EvanBacon): I'd prefer we prevent the ability for a module to require itself (`require.context('./')`)
      // but Webpack allows this, keeping it here provides better parity between bundlers.

      // Ensure relative file paths start with `./` so they match the
      // patterns (filters) used to include them.
      if (!filePath.startsWith(".")) {
        filePath = `./${filePath}`;
      }
      const key = JSON.stringify(filePath);
      // NOTE(EvanBacon): Webpack uses `require.resolve` in order to load modules on demand,
      // Metro doesn't have this functionality so it will use getters instead. Modules need to
      // be loaded on demand because if we imported directly then users would get errors from importing
      // a file without exports as soon as they create a new file and the context module is updated.

      // NOTE: The values are set to `enumerable` so the `context.keys()` method works as expected.
      mapString += `  ${key}: { enumerable: true, get() { return ${processModule(
        file
      )}; } },\n`;
    });
  return `Object.defineProperties({}, {${mapString}})`;
}
function getEmptyContextModuleTemplate(modulePath) {
  return `
function metroEmptyContext(request) {
  let e = new Error('No modules in context');
  e.code = 'MODULE_NOT_FOUND';
  throw e;
}

// Return the keys that can be resolved.
metroEmptyContext.keys = () => ([]);

// Return the module identifier for a user request.
metroEmptyContext.resolve = function metroContextResolve(request) {
  throw new Error('Unimplemented Metro module context functionality');
}

module.exports = metroEmptyContext;`;
}
function getLoadableContextModuleTemplate(
  modulePath,
  files,
  importSyntax,
  getContextTemplate
) {
  return `// All of the requested modules are loaded behind enumerable getters.
const map = ${createFileMap(
    modulePath,
    files,
    (moduleId) => `${importSyntax}(${JSON.stringify(moduleId)})`
  )};

function metroContext(request) {
  ${getContextTemplate}
}

// Return the keys that can be resolved.
metroContext.keys = function metroContextKeys() {
  return Object.keys(map);
};

// Return the module identifier for a user request.
metroContext.resolve = function metroContextResolve(request) {
  throw new Error('Unimplemented Metro module context functionality');
}

module.exports = metroContext;`;
}

/**
 * Generate a context module as a virtual file string.
 *
 * @prop {ContextMode} mode indicates how the modules should be loaded.
 * @prop {string} modulePath virtual file path for the virtual module. Example: `require.context('./src')` -> `'/path/to/project/src'`.
 * @prop {string[]} files list of absolute file paths that must be exported from the context module. Example: `['/path/to/project/src/index.js']`.
 *
 * @returns a string representing a context module (virtual file contents).
 */
function getContextModuleTemplate(mode, modulePath, files) {
  if (!files.length) {
    return getEmptyContextModuleTemplate(modulePath);
  }
  switch (mode) {
    case "eager":
      return getLoadableContextModuleTemplate(
        modulePath,
        files,
        // NOTE(EvanBacon): It's unclear if we should use `import` or `require` here so sticking
        // with the more stable option (`require`) for now.
        "require",
        [
          "  // Here Promise.resolve().then() is used instead of new Promise() to prevent",
          "  // uncaught exception popping up in devtools",
          "  return Promise.resolve().then(() => map[request]);",
        ].join("\n")
      );
    case "sync":
      return getLoadableContextModuleTemplate(
        modulePath,
        files,
        "require",
        "  return map[request];"
      );
    case "lazy":
    case "lazy-once":
      return getLoadableContextModuleTemplate(
        modulePath,
        files,
        "import",
        "  return map[request];"
      );
    default:
      throw new Error(`Metro context mode "${mode}" is unimplemented`);
  }
}
