"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.AssetGraphBuilder = void 0;
exports.default = createAssetGraphRequest;
function _logger() {
  const data = _interopRequireDefault(require("@parcel/logger"));
  _logger = function () {
    return data;
  };
  return data;
}
function _assert() {
  const data = _interopRequireDefault(require("assert"));
  _assert = function () {
    return data;
  };
  return data;
}
function _nullthrows() {
  const data = _interopRequireDefault(require("nullthrows"));
  _nullthrows = function () {
    return data;
  };
  return data;
}
function _utils() {
  const data = require("@parcel/utils");
  _utils = function () {
    return data;
  };
  return data;
}
function _rust() {
  const data = require("@parcel/rust");
  _rust = function () {
    return data;
  };
  return data;
}
function _diagnostic() {
  const data = _interopRequireDefault(require("@parcel/diagnostic"));
  _diagnostic = function () {
    return data;
  };
  return data;
}
var _types = require("../types");
var _AssetGraph = _interopRequireDefault(require("../AssetGraph"));
var _constants = require("../constants");
var _EntryRequest = _interopRequireDefault(require("./EntryRequest"));
var _TargetRequest = _interopRequireDefault(require("./TargetRequest"));
var _AssetRequest = _interopRequireDefault(require("./AssetRequest"));
var _PathRequest = _interopRequireDefault(require("./PathRequest"));
var _projectPath = require("../projectPath");
var _dumpGraphToGraphViz = _interopRequireDefault(require("../dumpGraphToGraphViz"));
var _SymbolPropagation = require("../SymbolPropagation");
var _RequestTracker = require("../RequestTracker");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function createAssetGraphRequest(requestInput) {
  return {
    type: _RequestTracker.requestTypes.asset_graph_request,
    id: requestInput.name,
    run: async input => {
      let prevResult = await input.api.getPreviousResult();
      let builder = new AssetGraphBuilder(input, prevResult);
      let assetGraphRequest = await await builder.build();

      // early break for incremental bundling if production or flag is off;
      if (!input.options.shouldBundleIncrementally || input.options.mode === 'production') {
        assetGraphRequest.assetGraph.safeToIncrementallyBundle = false;
      }
      return assetGraphRequest;
    },
    input: requestInput
  };
}
const typesWithRequests = new Set(['entry_specifier', 'entry_file', 'dependency', 'asset_group']);
class AssetGraphBuilder {
  assetRequests = [];
  constructor({
    input,
    api,
    options
  }, prevResult) {
    let {
      entries,
      assetGroups,
      optionsRef,
      name,
      requestedAssetIds,
      shouldBuildLazily,
      lazyIncludes,
      lazyExcludes
    } = input;
    let assetGraph = (prevResult === null || prevResult === void 0 ? void 0 : prevResult.assetGraph) ?? new _AssetGraph.default();
    assetGraph.safeToIncrementallyBundle = true;
    assetGraph.setRootConnections({
      entries,
      assetGroups
    });
    assetGraph.undeferredDependencies.clear();
    this.assetGroupsWithRemovedParents = (prevResult === null || prevResult === void 0 ? void 0 : prevResult.assetGroupsWithRemovedParents) ?? new Set();
    this.previousSymbolPropagationErrors = (prevResult === null || prevResult === void 0 ? void 0 : prevResult.previousSymbolPropagationErrors) ?? new Map();
    this.changedAssets = (prevResult === null || prevResult === void 0 ? void 0 : prevResult.changedAssets) ?? new Map();
    this.changedAssetsPropagation = new Set();
    this.prevChangedAssetsPropagation = prevResult === null || prevResult === void 0 ? void 0 : prevResult.changedAssetsPropagation;
    this.assetGraph = assetGraph;
    this.optionsRef = optionsRef;
    this.options = options;
    this.api = api;
    this.name = name;
    this.requestedAssetIds = requestedAssetIds ?? new Set();
    this.shouldBuildLazily = shouldBuildLazily ?? false;
    this.lazyIncludes = lazyIncludes ?? [];
    this.lazyExcludes = lazyExcludes ?? [];
    this.cacheKey = (0, _rust().hashString)(`${_constants.PARCEL_VERSION}${name}${JSON.stringify(entries) ?? ''}${options.mode}${options.shouldBuildLazily ? 'lazy' : 'eager'}`) + '-AssetGraph';
    this.isSingleChangeRebuild = api.getInvalidSubRequests().filter(req => req.requestType === 'asset_request').length === 1;
    this.queue = new (_utils().PromiseQueue)();
    assetGraph.onNodeRemoved = nodeId => {
      this.assetGroupsWithRemovedParents.delete(nodeId);

      // This needs to mark all connected nodes that doesn't become orphaned
      // due to replaceNodesConnectedTo to make sure that the symbols of
      // nodes from which at least one parent was removed are updated.
      let node = (0, _nullthrows().default)(assetGraph.getNode(nodeId));
      if (assetGraph.isOrphanedNode(nodeId) && node.type === 'dependency') {
        let children = assetGraph.getNodeIdsConnectedFrom(nodeId);
        for (let child of children) {
          let childNode = (0, _nullthrows().default)(assetGraph.getNode(child));
          (0, _assert().default)(childNode.type === 'asset_group' || childNode.type === 'asset');
          childNode.usedSymbolsDownDirty = true;
          this.assetGroupsWithRemovedParents.add(child);
        }
      }
    };
  }
  async build() {
    let errors = [];
    let rootNodeId = (0, _nullthrows().default)(this.assetGraph.rootNodeId, 'A root node is required to traverse');
    let visitedAssetGroups = new Set();
    let visited = new Set([rootNodeId]);
    const visit = nodeId => {
      if (errors.length > 0) {
        return;
      }
      if (this.shouldSkipRequest(nodeId)) {
        visitChildren(nodeId);
      } else {
        // ? do we need to visit children inside of the promise that is queued?
        this.queueCorrespondingRequest(nodeId, errors).then(() => visitChildren(nodeId));
      }
    };
    const visitChildren = nodeId => {
      for (let childNodeId of this.assetGraph.getNodeIdsConnectedFrom(nodeId)) {
        let child = (0, _nullthrows().default)(this.assetGraph.getNode(childNodeId));
        if ((!visited.has(childNodeId) || child.hasDeferred) && this.shouldVisitChild(nodeId, childNodeId)) {
          if (child.type === 'asset_group') {
            visitedAssetGroups.add(childNodeId);
          }
          visited.add(childNodeId);
          visit(childNodeId);
        }
      }
    };
    visit(rootNodeId);
    await this.queue.run();
    _logger().default.verbose({
      origin: '@parcel/core',
      message: 'Asset graph walked',
      meta: {
        visitedAssetGroupsCount: visitedAssetGroups.size
      }
    });
    if (this.prevChangedAssetsPropagation) {
      // Add any previously seen Assets that have not been propagated yet to
      // 'this.changedAssetsPropagation', but only if they still remain in the graph
      // as they could have been removed since the last build
      for (let assetId of this.prevChangedAssetsPropagation) {
        if (this.assetGraph.hasContentKey(assetId)) {
          this.changedAssetsPropagation.add(assetId);
        }
      }
    }
    if (errors.length) {
      this.api.storeResult({
        assetGraph: this.assetGraph,
        changedAssets: this.changedAssets,
        changedAssetsPropagation: this.changedAssetsPropagation,
        assetGroupsWithRemovedParents: this.assetGroupsWithRemovedParents,
        previousSymbolPropagationErrors: undefined,
        assetRequests: []
      }, this.cacheKey);

      // TODO: eventually support multiple errors since requests could reject in parallel
      throw errors[0];
    }
    if (this.assetGraph.nodes.length > 1) {
      await (0, _dumpGraphToGraphViz.default)(this.assetGraph, 'AssetGraph_' + this.name + '_before_prop');
      try {
        let errors = (0, _SymbolPropagation.propagateSymbols)({
          options: this.options,
          assetGraph: this.assetGraph,
          changedAssetsPropagation: this.changedAssetsPropagation,
          assetGroupsWithRemovedParents: this.assetGroupsWithRemovedParents,
          previousErrors: this.previousSymbolPropagationErrors
        });
        this.changedAssetsPropagation.clear();
        if (errors.size > 0) {
          this.api.storeResult({
            assetGraph: this.assetGraph,
            changedAssets: this.changedAssets,
            changedAssetsPropagation: this.changedAssetsPropagation,
            assetGroupsWithRemovedParents: this.assetGroupsWithRemovedParents,
            previousSymbolPropagationErrors: errors,
            assetRequests: []
          }, this.cacheKey);

          // Just throw the first error. Since errors can bubble (e.g. reexporting a reexported symbol also fails),
          // determining which failing export is the root cause is nontrivial (because of circular dependencies).
          throw new (_diagnostic().default)({
            diagnostic: [...errors.values()][0]
          });
        }
      } catch (e) {
        await (0, _dumpGraphToGraphViz.default)(this.assetGraph, 'AssetGraph_' + this.name + '_failed');
        throw e;
      }
    }
    await (0, _dumpGraphToGraphViz.default)(this.assetGraph, 'AssetGraph_' + this.name);
    this.api.storeResult({
      assetGraph: this.assetGraph,
      changedAssets: new Map(),
      changedAssetsPropagation: this.changedAssetsPropagation,
      assetGroupsWithRemovedParents: undefined,
      previousSymbolPropagationErrors: undefined,
      assetRequests: []
    }, this.cacheKey);
    return {
      assetGraph: this.assetGraph,
      changedAssets: this.changedAssets,
      changedAssetsPropagation: this.changedAssetsPropagation,
      assetGroupsWithRemovedParents: undefined,
      previousSymbolPropagationErrors: undefined,
      assetRequests: this.assetRequests
    };
  }
  shouldVisitChild(nodeId, childNodeId) {
    if (this.shouldBuildLazily) {
      let node = (0, _nullthrows().default)(this.assetGraph.getNode(nodeId));
      let childNode = (0, _nullthrows().default)(this.assetGraph.getNode(childNodeId));
      if (node.type === 'asset' && childNode.type === 'dependency') {
        // This logic will set `node.requested` to `true` if the node is in the list of requested asset ids
        // (i.e. this is an entry of a (probably) placeholder bundle that wasn't previously requested)
        //
        // Otherwise, if this node either is explicitly not requested, or has had it's requested attribute deleted,
        // it will determine whether this node is an "async child" - that is, is it a (probably)
        // dynamic import(). If so, it will explicitly have it's `node.requested` set to `false`
        //
        // If it's not requested, but it's not an async child then it's `node.requested` is deleted (undefined)

        // by default with lazy compilation all nodes are lazy
        let isNodeLazy = true;

        // For conditional lazy building - if this node matches the `lazyInclude` globs that means we want
        // only those nodes to be treated as lazy - that means if this node does _NOT_ match that glob, then we
        // also consider it not lazy (so it gets marked as requested).
        const relativePath = (0, _projectPath.fromProjectPathRelative)(node.value.filePath);
        if (this.lazyIncludes.length > 0) {
          isNodeLazy = this.lazyIncludes.some(lazyIncludeRegex => relativePath.match(lazyIncludeRegex));
        }
        // Excludes override includes, so a node is _not_ lazy if it is included in the exclude list.
        if (this.lazyExcludes.length > 0 && isNodeLazy) {
          isNodeLazy = !this.lazyExcludes.some(lazyExcludeRegex => relativePath.match(lazyExcludeRegex));
        }
        if (this.requestedAssetIds.has(node.value.id) || !isNodeLazy) {
          node.requested = true;
        } else if (!node.requested) {
          let isAsyncChild = this.assetGraph.getIncomingDependencies(node.value).every(dep => dep.isEntry || dep.priority !== _types.Priority.sync);
          if (isAsyncChild) {
            node.requested = !isNodeLazy;
          } else {
            delete node.requested;
          }
        }
        let previouslyDeferred = childNode.deferred;
        childNode.deferred = node.requested === false;

        // The child dependency node we're now evaluating should not be deferred if it's parent
        // is explicitly not requested (requested = false, but not requested = undefined)
        //
        // if we weren't previously deferred but we are now, then this dependency node's parents should also
        // be marked as deferred
        //
        // if we were previously deferred but we not longer are, then then all parents should no longer be
        // deferred either
        if (!previouslyDeferred && childNode.deferred) {
          this.assetGraph.markParentsWithHasDeferred(childNodeId);
        } else if (previouslyDeferred && !childNode.deferred) {
          // Mark Asset and Dependency as dirty for symbol propagation as it was
          // previously deferred and it's used symbols may have changed
          this.changedAssetsPropagation.add(node.id);
          node.usedSymbolsDownDirty = true;
          this.changedAssetsPropagation.add(childNode.id);
          childNode.usedSymbolsDownDirty = true;
          this.assetGraph.unmarkParentsWithHasDeferred(childNodeId);
        }

        // We `shouldVisitChild` if the childNode is not deferred
        return !childNode.deferred;
      }
    }
    return this.assetGraph.shouldVisitChild(nodeId, childNodeId);
  }
  shouldSkipRequest(nodeId) {
    let node = (0, _nullthrows().default)(this.assetGraph.getNode(nodeId));
    return node.complete === true || !typesWithRequests.has(node.type) || node.correspondingRequest != null && this.api.canSkipSubrequest(node.correspondingRequest);
  }
  queueCorrespondingRequest(nodeId, errors) {
    let promise;
    let node = (0, _nullthrows().default)(this.assetGraph.getNode(nodeId));
    switch (node.type) {
      case 'entry_specifier':
        promise = this.runEntryRequest(node.value);
        break;
      case 'entry_file':
        promise = this.runTargetRequest(node.value);
        break;
      case 'dependency':
        promise = this.runPathRequest(node.value);
        break;
      case 'asset_group':
        promise = this.runAssetRequest(node.value);
        break;
      default:
        throw new Error(`Can not queue corresponding request of node with type ${node.type}`);
    }
    return this.queue.add(() => promise.then(null, error => errors.push(error)));
  }
  async runEntryRequest(input) {
    let prevEntries = this.assetGraph.safeToIncrementallyBundle ? this.assetGraph.getEntryAssets().map(asset => asset.id).sort() : [];
    let request = (0, _EntryRequest.default)(input);
    let result = await this.api.runRequest(request, {
      force: true
    });
    this.assetGraph.resolveEntry(request.input, result.entries, request.id);
    if (this.assetGraph.safeToIncrementallyBundle) {
      let currentEntries = this.assetGraph.getEntryAssets().map(asset => asset.id).sort();
      let didEntriesChange = prevEntries.length !== currentEntries.length || prevEntries.every((entryId, index) => entryId === currentEntries[index]);
      if (didEntriesChange) {
        this.assetGraph.safeToIncrementallyBundle = false;
      }
    }
  }
  async runTargetRequest(input) {
    let request = (0, _TargetRequest.default)(input);
    let targets = await this.api.runRequest(request, {
      force: true
    });
    this.assetGraph.resolveTargets(request.input, targets, request.id);
  }
  async runPathRequest(input) {
    let request = (0, _PathRequest.default)({
      dependency: input,
      name: this.name
    });
    let result = await this.api.runRequest(request, {
      force: true
    });
    this.assetGraph.resolveDependency(input, result, request.id);
  }
  async runAssetRequest(input) {
    this.assetRequests.push(input);
    let request = (0, _AssetRequest.default)({
      ...input,
      name: this.name,
      optionsRef: this.optionsRef,
      isSingleChangeRebuild: this.isSingleChangeRebuild
    });
    let assets = await this.api.runRequest(request, {
      force: true
    });
    if (assets != null) {
      for (let asset of assets) {
        if (this.assetGraph.safeToIncrementallyBundle) {
          let otherAsset = this.assetGraph.getNodeByContentKey(asset.id);
          if (otherAsset != null) {
            (0, _assert().default)(otherAsset.type === 'asset');
            if (!this._areDependenciesEqualForAssets(asset, otherAsset.value)) {
              this.assetGraph.safeToIncrementallyBundle = false;
            }
          } else {
            // adding a new entry or dependency
            this.assetGraph.safeToIncrementallyBundle = false;
          }
        }
        this.changedAssets.set(asset.id, asset);
        this.changedAssetsPropagation.add(asset.id);
      }
      this.assetGraph.resolveAssetGroup(input, assets, request.id);
    } else {
      this.assetGraph.safeToIncrementallyBundle = false;
    }
    this.isSingleChangeRebuild = false;
  }

  /**
   * Used for incremental bundling of modified assets
   */
  _areDependenciesEqualForAssets(asset, otherAsset) {
    let assetDependencies = Array.from(asset === null || asset === void 0 ? void 0 : asset.dependencies.keys()).sort();
    let otherAssetDependencies = Array.from(otherAsset === null || otherAsset === void 0 ? void 0 : otherAsset.dependencies.keys()).sort();
    if (assetDependencies.length !== otherAssetDependencies.length) {
      return false;
    }
    return assetDependencies.every((key, index) => {
      var _asset$dependencies$g, _otherAsset$dependenc;
      if (key !== otherAssetDependencies[index]) {
        return false;
      }
      return (0, _utils().setEqual)(new Set(asset === null || asset === void 0 || (_asset$dependencies$g = asset.dependencies.get(key)) === null || _asset$dependencies$g === void 0 || (_asset$dependencies$g = _asset$dependencies$g.symbols) === null || _asset$dependencies$g === void 0 ? void 0 : _asset$dependencies$g.keys()), new Set(otherAsset === null || otherAsset === void 0 || (_otherAsset$dependenc = otherAsset.dependencies.get(key)) === null || _otherAsset$dependenc === void 0 || (_otherAsset$dependenc = _otherAsset$dependenc.symbols) === null || _otherAsset$dependenc === void 0 ? void 0 : _otherAsset$dependenc.keys()));
    });
  }
}
exports.AssetGraphBuilder = AssetGraphBuilder;