"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
exports.nodeFromAsset = nodeFromAsset;
exports.nodeFromAssetGroup = nodeFromAssetGroup;
exports.nodeFromDep = nodeFromDep;
exports.nodeFromEntryFile = nodeFromEntryFile;
exports.nodeFromEntrySpecifier = nodeFromEntrySpecifier;
function _assert() {
  const data = _interopRequireDefault(require("assert"));
  _assert = function () {
    return data;
  };
  return data;
}
function _rust() {
  const data = require("@parcel/rust");
  _rust = function () {
    return data;
  };
  return data;
}
function _utils() {
  const data = require("@parcel/utils");
  _utils = function () {
    return data;
  };
  return data;
}
function _nullthrows() {
  const data = _interopRequireDefault(require("nullthrows"));
  _nullthrows = function () {
    return data;
  };
  return data;
}
function _graph() {
  const data = require("@parcel/graph");
  _graph = function () {
    return data;
  };
  return data;
}
var _Dependency = require("./Dependency");
var _projectPath = require("./projectPath");
var _Environment = require("./public/Environment");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function nodeFromDep(dep) {
  return {
    id: dep.id,
    type: 'dependency',
    value: dep,
    deferred: false,
    excluded: false,
    usedSymbolsDown: new Set(),
    usedSymbolsUp: new Map(),
    usedSymbolsDownDirty: true,
    usedSymbolsUpDirtyDown: true,
    usedSymbolsUpDirtyUp: true
  };
}
function nodeFromAssetGroup(assetGroup) {
  return {
    id: (0, _rust().hashString)((0, _projectPath.fromProjectPathRelative)(assetGroup.filePath) + assetGroup.env.id + String(assetGroup.isSource) + String(assetGroup.sideEffects) + (assetGroup.code ?? '') + ':' + (assetGroup.pipeline ?? '') + ':' + (assetGroup.query ?? '')),
    type: 'asset_group',
    value: assetGroup,
    usedSymbolsDownDirty: true
  };
}
function nodeFromAsset(asset) {
  return {
    id: asset.id,
    type: 'asset',
    value: asset,
    usedSymbols: new Set(),
    usedSymbolsDownDirty: true,
    usedSymbolsUpDirty: true
  };
}
function nodeFromEntrySpecifier(entry) {
  return {
    id: 'entry_specifier:' + (0, _projectPath.fromProjectPathRelative)(entry),
    type: 'entry_specifier',
    value: entry
  };
}
function nodeFromEntryFile(entry) {
  return {
    id: 'entry_file:' + (0, _utils().hashObject)(entry),
    type: 'entry_file',
    value: entry
  };
}
class AssetGraph extends _graph().ContentGraph {
  safeToIncrementallyBundle = true;
  constructor(opts) {
    if (opts) {
      let {
        hash,
        ...rest
      } = opts;
      super(rest);
      this.hash = hash;
    } else {
      super();
      this.setRootNodeId(this.addNode({
        id: '@@root',
        type: 'root',
        value: null
      }));
    }
    this.undeferredDependencies = new Set();
    this.envCache = new Map();
  }

  // $FlowFixMe[prop-missing]
  static deserialize(opts) {
    return new AssetGraph(opts);
  }

  // $FlowFixMe[prop-missing]
  serialize() {
    return {
      ...super.serialize(),
      hash: this.hash
    };
  }

  // Deduplicates Environments by making them referentially equal
  normalizeEnvironment(input) {
    let {
      id,
      context
    } = input.env;
    let idAndContext = `${id}-${context}`;
    let env = this.envCache.get(idAndContext);
    if (env) {
      input.env = env;
    } else {
      this.envCache.set(idAndContext, input.env);
    }
  }
  setRootConnections({
    entries,
    assetGroups
  }) {
    let nodes = [];
    if (entries) {
      for (let entry of entries) {
        let node = nodeFromEntrySpecifier(entry);
        nodes.push(node);
      }
    } else if (assetGroups) {
      nodes.push(...assetGroups.map(assetGroup => nodeFromAssetGroup(assetGroup)));
    }
    this.replaceNodeIdsConnectedTo((0, _nullthrows().default)(this.rootNodeId), nodes.map(node => this.addNode(node)));
  }
  addNode(node) {
    this.hash = null;
    let existing = this.getNodeByContentKey(node.id);
    if (existing != null) {
      (0, _assert().default)(existing.type === node.type);
      // $FlowFixMe[incompatible-type] Checked above
      // $FlowFixMe[prop-missing]
      existing.value = node.value;
      let existingId = this.getNodeIdByContentKey(node.id);
      this.updateNode(existingId, existing);
      return existingId;
    }
    return super.addNodeByContentKey(node.id, node);
  }
  removeNode(nodeId) {
    this.hash = null;
    this.onNodeRemoved && this.onNodeRemoved(nodeId);
    return super.removeNode(nodeId);
  }
  resolveEntry(entry, resolved, correspondingRequest) {
    let entrySpecifierNodeId = this.getNodeIdByContentKey(nodeFromEntrySpecifier(entry).id);
    let entrySpecifierNode = (0, _nullthrows().default)(this.getNode(entrySpecifierNodeId));
    (0, _assert().default)(entrySpecifierNode.type === 'entry_specifier');
    entrySpecifierNode.correspondingRequest = correspondingRequest;
    this.replaceNodeIdsConnectedTo(entrySpecifierNodeId, resolved.map(file => this.addNode(nodeFromEntryFile(file))));
  }
  resolveTargets(entry, targets, correspondingRequest) {
    let depNodes = targets.map(target => {
      // In library mode, all of the entry's symbols are "used"
      // In non-browser environments, exports may also be used (e.g. serverless request handlers).
      let includeAllSymbols = target.env.isLibrary || !_Environment.BROWSER_ENVS.has(target.env.context);
      let node = nodeFromDep(
      // The passed project path is ignored in this case, because there is no `loc`
      (0, _Dependency.createDependency)('', {
        specifier: (0, _projectPath.fromProjectPathRelative)(entry.filePath),
        specifierType: 'esm',
        // ???
        pipeline: target.pipeline,
        target: target,
        env: target.env,
        isEntry: true,
        needsStableName: true,
        symbols: includeAllSymbols ? new Map([['*', {
          local: '*',
          isWeak: true,
          loc: null
        }]]) : undefined
      }));
      if (includeAllSymbols) {
        node.usedSymbolsDown.add('*');
        node.usedSymbolsUp.set('*', undefined);
      }
      return node;
    });
    let entryNodeId = this.getNodeIdByContentKey(nodeFromEntryFile(entry).id);
    let entryNode = (0, _nullthrows().default)(this.getNode(entryNodeId));
    (0, _assert().default)(entryNode.type === 'entry_file');
    entryNode.correspondingRequest = correspondingRequest;
    this.replaceNodeIdsConnectedTo(entryNodeId, depNodes.map(node => this.addNode(node)));
  }
  resolveDependency(dependency, assetGroup, correspondingRequest) {
    let depNodeId = this.getNodeIdByContentKey(dependency.id);
    let depNode = (0, _nullthrows().default)(this.getNode(depNodeId));
    (0, _assert().default)(depNode.type === 'dependency');
    depNode.correspondingRequest = correspondingRequest;
    if (!assetGroup) {
      return;
    }
    let assetGroupNode = nodeFromAssetGroup(assetGroup);
    let existing = this.getNodeByContentKey(assetGroupNode.id);
    if (existing != null) {
      (0, _assert().default)(existing.type === 'asset_group');
      assetGroupNode.value.canDefer = assetGroupNode.value.canDefer && existing.value.canDefer;
    }
    let assetGroupNodeId = this.addNode(assetGroupNode);
    this.replaceNodeIdsConnectedTo(this.getNodeIdByContentKey(dependency.id), [assetGroupNodeId]);
    this.replaceNodeIdsConnectedTo(depNodeId, [assetGroupNodeId]);
  }
  shouldVisitChild(nodeId, childNodeId) {
    let node = (0, _nullthrows().default)(this.getNode(nodeId));
    let childNode = (0, _nullthrows().default)(this.getNode(childNodeId));
    if (node.type !== 'dependency' || childNode.type !== 'asset_group' || childNode.deferred === false) {
      return true;
    }
    // Node types are proved above
    let dependencyNode = node;
    let assetGroupNode = childNode;
    let {
      sideEffects,
      canDefer = true
    } = assetGroupNode.value;
    let dependency = dependencyNode.value;
    let dependencyPreviouslyDeferred = dependencyNode.hasDeferred;
    let assetGroupPreviouslyDeferred = assetGroupNode.deferred;
    let defer = this.shouldDeferDependency(dependency, sideEffects, canDefer);
    dependencyNode.hasDeferred = defer;
    assetGroupNode.deferred = defer;
    if (!dependencyPreviouslyDeferred && defer) {
      this.markParentsWithHasDeferred(nodeId);
    } else if (assetGroupPreviouslyDeferred && !defer) {
      this.unmarkParentsWithHasDeferred(childNodeId);
    }
    return !defer;
  }

  // Dependency: mark parent Asset <- AssetGroup with hasDeferred true
  markParentsWithHasDeferred(nodeId) {
    this.traverseAncestors(nodeId, (traversedNodeId, _, actions) => {
      let traversedNode = (0, _nullthrows().default)(this.getNode(traversedNodeId));
      if (traversedNode.type === 'asset') {
        traversedNode.hasDeferred = true;
      } else if (traversedNode.type === 'asset_group') {
        traversedNode.hasDeferred = true;
        actions.skipChildren();
      } else if (nodeId !== traversedNodeId) {
        actions.skipChildren();
      }
    });
  }

  // AssetGroup: update hasDeferred of all parent Dependency <- Asset <- AssetGroup
  unmarkParentsWithHasDeferred(nodeId) {
    this.traverseAncestors(nodeId, (traversedNodeId, ctx, actions) => {
      let traversedNode = (0, _nullthrows().default)(this.getNode(traversedNodeId));
      if (traversedNode.type === 'asset') {
        let hasDeferred = this.getNodeIdsConnectedFrom(traversedNodeId).some(childNodeId => {
          let childNode = (0, _nullthrows().default)(this.getNode(childNodeId));
          return childNode.hasDeferred == null ? false : childNode.hasDeferred;
        });
        if (!hasDeferred) {
          delete traversedNode.hasDeferred;
        }
        return {
          hasDeferred
        };
      } else if (traversedNode.type === 'asset_group' && nodeId !== traversedNodeId) {
        if (!(ctx !== null && ctx !== void 0 && ctx.hasDeferred)) {
          this.safeToIncrementallyBundle = false;
          delete traversedNode.hasDeferred;
        }
        actions.skipChildren();
      } else if (traversedNode.type === 'dependency') {
        this.safeToIncrementallyBundle = false;
        traversedNode.hasDeferred = false;
      } else if (nodeId !== traversedNodeId) {
        actions.skipChildren();
      }
    });
  }

  // Defer transforming this dependency if it is marked as weak, there are no side effects,
  // no re-exported symbols are used by ancestor dependencies and the re-exporting asset isn't
  // using a wildcard and isn't an entry (in library mode).
  // This helps with performance building large libraries like `lodash-es`, which re-exports
  // a huge number of functions since we can avoid even transforming the files that aren't used.
  shouldDeferDependency(dependency, sideEffects, canDefer) {
    let dependencySymbols = dependency.symbols;

    // Doing this separately keeps Flow happy further down
    if (!dependencySymbols) {
      return false;
    }
    let isDeferrable = [...dependencySymbols].every(([, {
      isWeak
    }]) => isWeak) && sideEffects === false && canDefer && !dependencySymbols.has('*');
    if (!isDeferrable) {
      return false;
    }
    let depNodeId = this.getNodeIdByContentKey(dependency.id);
    let depNode = this.getNode(depNodeId);
    (0, _assert().default)(depNode);
    let assets = this.getNodeIdsConnectedTo(depNodeId);
    let symbols = new Map([...dependencySymbols].map(([key, val]) => [val.local, key]));
    (0, _assert().default)(assets.length === 1);
    let firstAsset = (0, _nullthrows().default)(this.getNode(assets[0]));
    (0, _assert().default)(firstAsset.type === 'asset');
    let resolvedAsset = firstAsset.value;

    // This doesn't change from here, so checking it now saves
    // us some calls to `getIncomingDependency`
    if (!resolvedAsset.symbols) {
      return true;
    }
    let deps = this.getIncomingDependencies(resolvedAsset);
    return deps.every(d => {
      // If this dependency has already been through this process, and we
      // know it's not deferrable, then there's no need to re-check
      if (this.undeferredDependencies.has(d)) {
        return false;
      }
      let depIsDeferrable = d.symbols && !(d.env.isLibrary && d.isEntry) && !d.symbols.has('*') && ![...d.symbols.keys()].some(symbol => {
        var _resolvedAsset$symbol;
        let assetSymbol = (_resolvedAsset$symbol = resolvedAsset.symbols) === null || _resolvedAsset$symbol === void 0 || (_resolvedAsset$symbol = _resolvedAsset$symbol.get(symbol)) === null || _resolvedAsset$symbol === void 0 ? void 0 : _resolvedAsset$symbol.local;
        return assetSymbol != null && symbols.has(assetSymbol);
      });
      if (!depIsDeferrable) {
        // Mark this dep as not deferrable so it doesn't have to be re-checked
        this.undeferredDependencies.add(d);
        return false;
      }
    });
  }
  resolveAssetGroup(assetGroup, assets, correspondingRequest) {
    this.normalizeEnvironment(assetGroup);
    let assetGroupNode = nodeFromAssetGroup(assetGroup);
    assetGroupNode = this.getNodeByContentKey(assetGroupNode.id);
    if (!assetGroupNode) {
      return;
    }
    (0, _assert().default)(assetGroupNode.type === 'asset_group');
    assetGroupNode.correspondingRequest = correspondingRequest;
    let assetsByKey = new Map();
    for (let asset of assets) {
      if (asset.uniqueKey != null) {
        assetsByKey.set(asset.uniqueKey, asset);
      }
    }
    let dependentAssetKeys = new Set();
    for (let asset of assets) {
      for (let dep of asset.dependencies.values()) {
        if (assetsByKey.has(dep.specifier)) {
          dependentAssetKeys.add(dep.specifier);
        }
      }
    }
    let assetObjects = [];
    let assetNodeIds = [];
    for (let asset of assets) {
      this.normalizeEnvironment(asset);
      let isDirect = !dependentAssetKeys.has(asset.uniqueKey);
      let dependentAssets = [];
      for (let dep of asset.dependencies.values()) {
        let dependentAsset = assetsByKey.get(dep.specifier);
        if (dependentAsset) {
          dependentAssets.push(dependentAsset);
          if (dependentAsset.id === asset.id) {
            // Don't orphan circular dependencies.
            isDirect = true;
          }
        }
      }
      let id = this.addNode(nodeFromAsset(asset));
      assetObjects.push({
        assetNodeId: id,
        dependentAssets
      });
      if (isDirect) {
        assetNodeIds.push(id);
      }
    }
    this.replaceNodeIdsConnectedTo(this.getNodeIdByContentKey(assetGroupNode.id), assetNodeIds);
    for (let {
      assetNodeId,
      dependentAssets
    } of assetObjects) {
      // replaceNodesConnectedTo has merged the value into the existing node, retrieve
      // the actual current node.
      let assetNode = (0, _nullthrows().default)(this.getNode(assetNodeId));
      (0, _assert().default)(assetNode.type === 'asset');
      this.resolveAsset(assetNode, dependentAssets);
    }
  }
  resolveAsset(assetNode, dependentAssets) {
    let depNodeIds = [];
    let depNodesWithAssets = [];
    for (let dep of assetNode.value.dependencies.values()) {
      this.normalizeEnvironment(dep);
      let depNode = nodeFromDep(dep);
      let existing = this.getNodeByContentKey(depNode.id);
      if ((existing === null || existing === void 0 ? void 0 : existing.type) === 'dependency' && existing.value.resolverMeta != null) {
        depNode.value.meta = {
          ...depNode.value.meta,
          ...existing.value.resolverMeta
        };
        depNode.value.resolverMeta = existing.value.resolverMeta;
      }
      if ((existing === null || existing === void 0 ? void 0 : existing.type) === 'dependency' && existing.value.resolverPriority != null) {
        depNode.value.priority = existing.value.resolverPriority;
        depNode.value.resolverPriority = existing.value.resolverPriority;
      }
      let dependentAsset = dependentAssets.find(a => a.uniqueKey === dep.specifier);
      if (dependentAsset) {
        depNode.complete = true;
        depNodesWithAssets.push([depNode, nodeFromAsset(dependentAsset)]);
      }
      depNode.value.sourceAssetType = assetNode.value.type;
      depNodeIds.push(this.addNode(depNode));
    }
    assetNode.usedSymbolsUpDirty = true;
    assetNode.usedSymbolsDownDirty = true;
    this.replaceNodeIdsConnectedTo(this.getNodeIdByContentKey(assetNode.id), depNodeIds);
    for (let [depNode, dependentAssetNode] of depNodesWithAssets) {
      let depAssetNodeId = this.addNode(dependentAssetNode);
      this.replaceNodeIdsConnectedTo(this.getNodeIdByContentKey(depNode.id), [depAssetNodeId]);
    }
  }
  getIncomingDependencies(asset) {
    let nodeId = this.getNodeIdByContentKey(asset.id);
    let assetGroupIds = this.getNodeIdsConnectedTo(nodeId);
    let dependencies = [];
    for (let i = 0; i < assetGroupIds.length; i++) {
      let assetGroupId = assetGroupIds[i];

      // Sometimes assets are connected directly to dependencies
      // rather than through an asset group. This happens due to
      // inline dependencies on assets via uniqueKey. See resolveAsset.
      let node = this.getNode(assetGroupId);
      if ((node === null || node === void 0 ? void 0 : node.type) === 'dependency') {
        dependencies.push(node.value);
        continue;
      }
      let assetIds = this.getNodeIdsConnectedTo(assetGroupId);
      for (let j = 0; j < assetIds.length; j++) {
        let node = this.getNode(assetIds[j]);
        if (!node || node.type !== 'dependency') {
          continue;
        }
        dependencies.push(node.value);
      }
    }
    return dependencies;
  }
  traverseAssets(visit, startNodeId) {
    return this.filteredTraverse(nodeId => {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      return node.type === 'asset' ? node.value : null;
    }, visit, startNodeId);
  }
  getEntryAssetGroupNodes() {
    let entryNodes = [];
    this.traverse((nodeId, _, actions) => {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      if (node.type === 'asset_group') {
        entryNodes.push(node);
        actions.skipChildren();
      }
    });
    return entryNodes;
  }
  getEntryAssets() {
    let entries = [];
    this.traverseAssets((asset, ctx, traversal) => {
      entries.push(asset);
      traversal.skipChildren();
    });
    return entries;
  }
  getHash() {
    if (this.hash != null) {
      return this.hash;
    }
    let hash = new (_rust().Hash)();
    // TODO: sort??
    this.traverse(nodeId => {
      let node = (0, _nullthrows().default)(this.getNode(nodeId));
      if (node.type === 'asset') {
        hash.writeString((0, _nullthrows().default)(node.value.outputHash));
      } else if (node.type === 'dependency' && node.value.target) {
        hash.writeString(JSON.stringify(node.value.target));
      }
    });
    this.hash = hash.finish();
    return this.hash;
  }
}
exports.default = AssetGraph;