/**
 * 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";

const AssetResolutionCache = require("../../node-haste/AssetResolutionCache");

const DependencyGraphHelpers = require("../../node-haste/DependencyGraph/DependencyGraphHelpers");

const FilesByDirNameIndex = require("../../node-haste/FilesByDirNameIndex");

const HasteFS = require("./HasteFS");

const Module = require("./Module");

const ModuleCache = require("./ModuleCache");

const defaults = require("metro-config/src/defaults/defaults");

const parsePlatformFilePath = require("../../node-haste/lib/parsePlatformFilePath");

const path = require("path");

const _require = require("../../node-haste/DependencyGraph/ModuleResolution"),
  ModuleResolver = _require.ModuleResolver;

const _require2 = require("jest-haste-map"),
  ModuleMap = _require2.ModuleMap;

const platforms = new Set(defaults.platforms);
const GENERIC_PLATFORM = "g";
const PACKAGE_JSON = path.sep + "package.json";
const NULL_MODULE = {
  path: "/",

  getPackage() {},

  isHaste() {
    throw new Error("not implemented");
  },

  getName() {
    throw new Error("not implemented");
  }
}; // This function maps the ModuleGraph data structure to jest-haste-map's ModuleMap

const createModuleMap = _ref => {
  let files = _ref.files,
    helpers = _ref.helpers,
    moduleCache = _ref.moduleCache,
    sourceExts = _ref.sourceExts;
  const map = new Map();
  files.forEach(filePath => {
    if (helpers.isNodeModulesDir(filePath)) {
      return;
    }

    let id;
    let module;

    if (filePath.endsWith(PACKAGE_JSON)) {
      module = moduleCache.getPackage(filePath);
      id = module.data.name;
    } else if (sourceExts.indexOf(path.extname(filePath).substr(1)) !== -1) {
      module = moduleCache.getModule(filePath);
      id = module.name;
    }

    if (!(id && module && module.isHaste())) {
      return;
    }

    const mapModule = map.get(id) || Object.create(null);
    const platform =
      parsePlatformFilePath(filePath, platforms).platform || GENERIC_PLATFORM;
    const existingModule = mapModule[platform]; // 0 = Module, 1 = Package in jest-haste-map

    mapModule[platform] = [filePath, module.type === "Package" ? 1 : 0];

    if (existingModule && existingModule[0] !== filePath) {
      throw new Error(
        [
          "@providesModule naming collision:",
          `  Duplicate module name: \`${id}\``,
          `  Paths: \`${filePath}\` collides with \`${existingModule[0]}\``,
          "",
          "This error is caused by a @providesModule declaration " +
            "with the same name across two different files."
        ].join("\n")
      );
    }

    map.set(id, mapModule);
  });
  return map;
};

exports.createResolveFn = function(options) {
  const allowPnp = options.allowPnp,
    assetExts = options.assetExts,
    extraNodeModules = options.extraNodeModules,
    transformedFiles = options.transformedFiles,
    sourceExts = options.sourceExts,
    platform = options.platform;
  const files = Object.keys(transformedFiles);

  function getTransformedFile(path) {
    const result = transformedFiles[path];

    if (!result) {
      throw new Error(`"${path} does not exist`);
    }

    return result;
  }

  const helpers = new DependencyGraphHelpers({
    assetExts,
    providesModuleNodeModules: defaults.providesModuleNodeModules
  });
  const hasteFS = new HasteFS(files);
  const moduleCache = new ModuleCache(
    filePath => hasteFS.closest(filePath, "package.json"),
    getTransformedFile
  );
  const filesByDirNameIndex = new FilesByDirNameIndex(files);
  const assetResolutionCache = new AssetResolutionCache({
    assetExtensions: new Set(assetExts),
    getDirFiles: dirPath => filesByDirNameIndex.getAllFiles(dirPath),
    platforms
  });
  const moduleResolver = new ModuleResolver({
    allowPnp,
    dirExists: filePath => hasteFS.dirExists(filePath),
    doesFileExist: filePath => hasteFS.exists(filePath),
    extraNodeModules,
    isAssetFile: filePath => helpers.isAssetFile(filePath),
    mainFields: options.mainFields,
    moduleCache,
    moduleMap: new ModuleMap({
      duplicates: new Map(),
      map: createModuleMap({
        files,
        helpers,
        moduleCache,
        sourceExts
      }),
      mocks: new Map(),
      rootDir: ""
    }),
    preferNativePlatform: true,
    resolveAsset: (dirPath, assetName, platform) =>
      assetResolutionCache.resolve(dirPath, assetName, platform),
    resolveRequest: options.resolveRequest,
    sourceExts
  });
  return (id, sourcePath) => {
    const from =
      sourcePath != null
        ? new Module(sourcePath, moduleCache, getTransformedFile(sourcePath))
        : NULL_MODULE;
    const allowHaste = !helpers.isNodeModulesDir(from.path);
    return moduleResolver.resolveDependency(from, id, allowHaste, platform)
      .path;
  };
};