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

"use strict";

const Module = require("./Module");
const Package = require("./Package");
class ModuleCache {
  // Cache for "closest package.json" queries by module path.

  // The inverse of _packagePathByModulePath.

  constructor(options) {
    this._getClosestPackage = options.getClosestPackage;
    this._moduleCache = Object.create(null);
    this._packageCache = Object.create(null);
    this._packagePathByModulePath = Object.create(null);
    this._modulePathsByPackagePath = Object.create(null);
  }
  getModule(filePath) {
    if (!this._moduleCache[filePath]) {
      this._moduleCache[filePath] = new Module(filePath, this);
    }
    return this._moduleCache[filePath];
  }
  getPackage(filePath) {
    if (!this._packageCache[filePath]) {
      this._packageCache[filePath] = new Package({
        file: filePath,
      });
    }
    return this._packageCache[filePath];
  }
  getPackageForModule(module) {
    return this.getPackageOf(module.path);
  }
  getPackageOf(modulePath) {
    var _this$_modulePathsByP;
    let packagePath = this._packagePathByModulePath[modulePath];
    if (packagePath && this._packageCache[packagePath]) {
      return this._packageCache[packagePath];
    }
    packagePath = this._getClosestPackage(modulePath);
    if (!packagePath) {
      return null;
    }
    this._packagePathByModulePath[modulePath] = packagePath;
    const modulePaths =
      (_this$_modulePathsByP = this._modulePathsByPackagePath[packagePath]) !==
        null && _this$_modulePathsByP !== void 0
        ? _this$_modulePathsByP
        : new Set();
    modulePaths.add(modulePath);
    this._modulePathsByPackagePath[packagePath] = modulePaths;
    return this.getPackage(packagePath);
  }
  invalidate(filePath) {
    if (this._moduleCache[filePath]) {
      this._moduleCache[filePath].invalidate();
      delete this._moduleCache[filePath];
    }
    if (this._packageCache[filePath]) {
      this._packageCache[filePath].invalidate();
      delete this._packageCache[filePath];
    }
    if (this._packagePathByModulePath[filePath]) {
      // filePath is a module inside a package.
      const packagePath = this._packagePathByModulePath[filePath];
      delete this._packagePathByModulePath[filePath];
      // This change doesn't invalidate any cached "closest package.json"
      // queries for the package's other modules. Clean up only this module.
      const modulePaths = this._modulePathsByPackagePath[packagePath];
      if (modulePaths) {
        modulePaths.delete(filePath);
        if (modulePaths.size === 0) {
          delete this._modulePathsByPackagePath[packagePath];
        }
      }
    }
    if (this._modulePathsByPackagePath[filePath]) {
      // filePath is a package. This change invalidates all cached "closest
      // package.json" queries for modules inside this package.
      const modulePaths = this._modulePathsByPackagePath[filePath];
      for (const modulePath of modulePaths) {
        delete this._packagePathByModulePath[modulePath];
      }
      modulePaths.clear();
      delete this._modulePathsByPackagePath[filePath];
    }
  }
}
module.exports = ModuleCache;
