"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

exports.__esModule = true;
exports.removeExportQueryParam = exports.StaticQueryMapper = void 0;

var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));

var _isEqual2 = _interopRequireDefault(require("lodash/isEqual"));

var _path = _interopRequireDefault(require("path"));

var _ConcatenatedModule = _interopRequireDefault(require("webpack/lib/optimize/ConcatenatedModule"));

var _jsChunkNames = require("../../js-chunk-names");

var _pageData = require("../../page-data");

var _findSlices = require("../../babel/find-slices");

/**
 * Remove the export query param from a path that can
 * a) contain only the ?export= query param
 * b) but also contain ?__contentFilePath&export=
 */
const removeExportQueryParam = path => {
  if (!(path !== null && path !== void 0 && path.includes(`?`))) {
    return path;
  }

  const [filePath, queryParams] = path === null || path === void 0 ? void 0 : path.split(`?`);
  const params = new URLSearchParams(queryParams);
  params.delete(`export`);
  const paramsString = params.toString();
  return `${filePath}${paramsString ? `?${decodeURIComponent(paramsString)}` : ``}`;
};
/**
 * Checks if a module matches a resourcePath
 */


exports.removeExportQueryParam = removeExportQueryParam;

function doesModuleMatchResourcePath(resourcePath, webpackModule) {
  if (!(webpackModule instanceof _ConcatenatedModule.default)) {
    return removeExportQueryParam(webpackModule.resource) === resourcePath;
  } // ConcatenatedModule is a collection of modules so we have to go deeper to actually get it


  return webpackModule.modules.some(innerModule => removeExportQueryParam(innerModule.resource) === resourcePath);
}
/**
 * A helper to set/get path resolving
 */


function getRealPath(cache, componentPath) {
  if (!cache.has(componentPath)) {
    cache.set(componentPath, _path.default.resolve(componentPath));
  }

  return cache.get(componentPath);
}
/**
 * Grab the actual webpackModule from the resourcePath
 * We return staticQueries and componentPaths cause that's what we care about
 */


function getWebpackModulesByResourcePaths(modules, staticQueries, components, componentsUsingSlices) {
  const realPathCache = new Map();
  const webpackModulesByStaticQueryId = new Map();
  const webpackModulesByComponentId = new Map();
  const webpackModulesUsingSlices = new Set();
  modules.forEach(webpackModule => {
    for (const [id, staticQuery] of staticQueries) {
      const staticQueryComponentPath = getRealPath(realPathCache, staticQuery.componentPath);

      if (!doesModuleMatchResourcePath(staticQueryComponentPath, webpackModule)) {
        continue;
      }

      let set = webpackModulesByStaticQueryId.get(id);

      if (!set) {
        set = new Set();
      }

      set.add(webpackModule);
      webpackModulesByStaticQueryId.set(id, set);
    }

    for (const [id, component] of components) {
      const componentComponentPath = getRealPath(realPathCache, component.componentPath);

      if (!doesModuleMatchResourcePath(componentComponentPath, webpackModule)) {
        continue;
      }

      let set = webpackModulesByComponentId.get(id);

      if (!set) {
        set = new Set();
      }

      set.add(webpackModule);
      webpackModulesByComponentId.set(id, set);
    }

    for (const [filePath, slices] of componentsUsingSlices) {
      const componentComponentPath = getRealPath(realPathCache, filePath);

      if (!doesModuleMatchResourcePath(componentComponentPath, webpackModule)) {
        continue;
      }

      webpackModulesUsingSlices.add({
        module: webpackModule,
        slices: slices
      });
    }
  });
  return {
    webpackModulesByStaticQueryId,
    webpackModulesByComponentId,
    webpackModulesUsingSlices
  };
}
/**
 * Chunks can be async so the group might not represent a pageComponent group
 * We'll need to search for it.
 */


function getChunkGroupsDerivedFromEntrypoint(chunkGroup, entrypoint) {
  // when it's imported by any globals or async-requires we know we have the correct chunkgroups.
  // Async modules won't have hasParent listed
  if (chunkGroup.hasParent(entrypoint)) {
    return [chunkGroup];
  }

  let chunkGroups = [];

  for (const parentChunkGroup of chunkGroup.getParents()) {
    const newChunkGroup = getChunkGroupsDerivedFromEntrypoint(parentChunkGroup, entrypoint);
    chunkGroups = chunkGroups.concat(newChunkGroup);
  }

  return chunkGroups;
}

class StaticQueryMapper {
  constructor(store) {
    this.store = store;
    this.name = `StaticQueryMapper`;
  }

  apply(compiler) {
    const {
      components,
      staticQueryComponents,
      componentsUsingSlices
    } = this.store.getState();
    compiler.hooks.done.tap(this.name, stats => {
      const compilation = stats.compilation; // We only care about the main compilation
      // Chunkgraph should always be available when it's done but you know typescript.

      if (compilation.compiler.parentCompilation || !compilation.chunkGraph) {
        return;
      }

      const staticQueriesByChunkGroup = new Map();
      const pageSliceUsageByChunkGroup = new Map();
      const chunkGroupsWithPageComponents = new Set();
      const chunkGroupsByComponentPath = new Map();
      const {
        webpackModulesByStaticQueryId,
        webpackModulesByComponentId,
        webpackModulesUsingSlices
      } = getWebpackModulesByResourcePaths(compilation.modules, staticQueryComponents, components, componentsUsingSlices);
      const appEntryPoint = compilation.entrypoints.has(`app`) ? compilation.entrypoints.get(`app`) : compilation.entrypoints.get(`commons`); // group hashes by chunkGroup for ease of use

      for (const [staticQueryId, webpackModules] of webpackModulesByStaticQueryId) {
        let chunkGroupsDerivedFromEntrypoints = [];

        for (const webpackModule of webpackModules) {
          for (const chunk of compilation.chunkGraph.getModuleChunksIterable(webpackModule)) {
            for (const chunkGroup of chunk.groupsIterable) {
              if (chunkGroup === appEntryPoint) {
                chunkGroupsDerivedFromEntrypoints.push(chunkGroup);
              } else {
                chunkGroupsDerivedFromEntrypoints = chunkGroupsDerivedFromEntrypoints.concat(getChunkGroupsDerivedFromEntrypoint(chunkGroup, appEntryPoint));
              }
            }
          }
        } // loop over all component chunkGroups or global ones


        chunkGroupsDerivedFromEntrypoints.forEach(chunkGroup => {
          var _staticQueriesByChunk;

          const staticQueryHashes = (_staticQueriesByChunk = staticQueriesByChunkGroup.get(chunkGroup)) !== null && _staticQueriesByChunk !== void 0 ? _staticQueriesByChunk : [];
          staticQueryHashes.push(staticQueryComponents.get(staticQueryId).hash);
          staticQueriesByChunkGroup.set(chunkGroup, staticQueryHashes);
        });
      } // group Slice usage by chunkGroup for ease of use


      for (const {
        slices,
        module: webpackModule
      } of webpackModulesUsingSlices) {
        let chunkGroupsDerivedFromEntrypoints = [];

        for (const chunk of compilation.chunkGraph.getModuleChunksIterable(webpackModule)) {
          for (const chunkGroup of chunk.groupsIterable) {
            if (chunkGroup === appEntryPoint) {
              chunkGroupsDerivedFromEntrypoints.push(chunkGroup);
            } else {
              chunkGroupsDerivedFromEntrypoints = chunkGroupsDerivedFromEntrypoints.concat(getChunkGroupsDerivedFromEntrypoint(chunkGroup, appEntryPoint));
            }
          }
        } // loop over all component chunkGroups or global ones


        chunkGroupsDerivedFromEntrypoints.forEach(chunkGroup => {
          pageSliceUsageByChunkGroup.set(chunkGroup, (0, _findSlices.mergePreviouslyCollectedSlices)(slices, pageSliceUsageByChunkGroup.get(chunkGroup)));
        });
      } // group chunkGroups by componentPaths for ease of use


      for (const [componentPath, webpackModules] of webpackModulesByComponentId) {
        for (const webpackModule of webpackModules) {
          for (const chunk of compilation.chunkGraph.getModuleChunksIterable(webpackModule)) {
            for (const chunkGroup of chunk.groupsIterable) {
              // When it's a direct import from app entrypoint (async-requires) we know we have the correct chunkGroup
              if (chunkGroup.name === (0, _jsChunkNames.generateComponentChunkName)(componentPath)) {
                chunkGroupsWithPageComponents.add(chunkGroup);
                chunkGroupsByComponentPath.set(componentPath, chunkGroup);
              }
            }
          }
        }
      }

      let globalStaticQueries = [];

      for (const [chunkGroup, staticQueryHashes] of staticQueriesByChunkGroup) {
        // When a chunkgroup is not part of a pageComponent we know it's part of a global group.
        if (!chunkGroupsWithPageComponents.has(chunkGroup)) {
          globalStaticQueries = globalStaticQueries.concat(staticQueryHashes);
        }
      }

      let globalSliceUsage = {};

      for (const [chunkGroup, slices] of pageSliceUsageByChunkGroup) {
        var _chunkGroup$name;

        if (!chunkGroupsWithPageComponents.has(chunkGroup) && !((_chunkGroup$name = chunkGroup.name) !== null && _chunkGroup$name !== void 0 && _chunkGroup$name.endsWith(`head`))) {
          globalSliceUsage = (0, _findSlices.mergePreviouslyCollectedSlices)(slices, globalSliceUsage);
        }
      }

      components.forEach(component => {
        const allStaticQueries = new Set(globalStaticQueries); // we only add global slices to pages, not other slices

        let allSlices = component.isSlice ? {} : (0, _cloneDeep2.default)(globalSliceUsage);

        if (chunkGroupsByComponentPath.has(component.componentPath)) {
          const chunkGroup = chunkGroupsByComponentPath.get(component.componentPath);

          if (chunkGroup) {
            const staticQueriesForChunkGroup = staticQueriesByChunkGroup.get(chunkGroup);

            if (staticQueriesForChunkGroup) {
              staticQueriesForChunkGroup.forEach(staticQuery => {
                allStaticQueries.add(staticQuery);
              });
            }

            const slicesForChunkGroup = pageSliceUsageByChunkGroup.get(chunkGroup);

            if (slicesForChunkGroup) {
              allSlices = (0, _findSlices.mergePreviouslyCollectedSlices)(slicesForChunkGroup, allSlices);
            }
          }
        } // modules, chunks, chunkgroups can all have not-deterministic orders so
        // just sort array of static queries we produced to ensure final result is deterministic


        const staticQueryHashes = Array.from(allStaticQueries).sort();
        const slices = Object.keys(allSlices).sort().reduce((obj, key) => {
          obj[key] = allSlices[key];
          return obj;
        }, {});
        const didStaticQueriesChange = !(0, _isEqual2.default)(this.store.getState().staticQueriesByTemplate.get(component.componentPath), staticQueryHashes);
        const didSlicesChange = !(0, _isEqual2.default)(this.store.getState().slicesByTemplate.get(component.componentPath), slices);

        if (didStaticQueriesChange || didSlicesChange) {
          if (component.isSlice) {
            this.store.dispatch({
              type: `ADD_PENDING_SLICE_TEMPLATE_DATA_WRITE`,
              payload: {
                componentPath: component.componentPath,
                sliceNames: component.pages
              }
            });
          } else {
            this.store.dispatch({
              type: `ADD_PENDING_TEMPLATE_DATA_WRITE`,
              payload: {
                componentPath: component.componentPath,
                pages: component.pages
              }
            });
          }
        }

        if (didSlicesChange) {
          this.store.dispatch({
            type: `SET_SLICES_BY_TEMPLATE`,
            payload: {
              componentPath: component.componentPath,
              slices
            }
          });
        }

        if (didStaticQueriesChange) {
          this.store.dispatch({
            type: `SET_STATIC_QUERIES_BY_TEMPLATE`,
            payload: {
              componentPath: component.componentPath,
              staticQueryHashes
            }
          });
        }
      }); // In dev mode we want to write page-data when compilation succeeds

      if (!stats.hasErrors() && compiler.watchMode) {
        (0, _pageData.enqueueFlush)();
      }
    });
  }

}

exports.StaticQueryMapper = StaticQueryMapper;
//# sourceMappingURL=static-query-mapper.js.map