"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = createWriteBundlesRequest;
var _RequestTracker = require("../RequestTracker");
var _constants = require("../constants");
var _projectPath = require("../projectPath");
function _nullthrows() {
  const data = _interopRequireDefault(require("nullthrows"));
  _nullthrows = function () {
    return data;
  };
  return data;
}
function _rust() {
  const data = require("@parcel/rust");
  _rust = function () {
    return data;
  };
  return data;
}
var _PackageRequest = require("./PackageRequest");
var _WriteBundleRequest = _interopRequireDefault(require("./WriteBundleRequest"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
 * Packages, optimizes, and writes all bundles to the dist directory.
 */
function createWriteBundlesRequest(input) {
  return {
    type: _RequestTracker.requestTypes.write_bundles_request,
    id: 'write_bundles:' + input.bundleGraph.getBundleGraphHash(),
    run,
    input
  };
}
async function run({
  input,
  api,
  farm,
  options
}) {
  let {
    bundleGraph,
    optionsRef
  } = input;
  let {
    ref,
    dispose
  } = await farm.createSharedReference(bundleGraph);
  api.invalidateOnOptionChange('shouldContentHash');
  let res = new Map();
  let bundleInfoMap = {};
  let writeEarlyPromises = {};
  let hashRefToNameHash = new Map();
  let bundles = bundleGraph.getBundles().filter(bundle => {
    // Do not package and write placeholder bundles to disk. We just
    // need to update the name so other bundles can reference it.
    if (bundle.isPlaceholder) {
      let hash = bundle.id.slice(-8);
      hashRefToNameHash.set(bundle.hashReference, hash);
      let name = (0, _nullthrows().default)(bundle.name, `Expected ${bundle.type} bundle to have a name`).replace(bundle.hashReference, hash);
      res.set(bundle.id, [{
        filePath: (0, _projectPath.joinProjectPath)(bundle.target.distDir, name),
        type: bundle.type,
        // FIXME: this is wrong if the packager changes the type...
        stats: {
          time: 0,
          size: 0
        }
      }]);
      return false;
    }
    return true;
  });

  // Package on the main thread if there is only one bundle to package.
  // This avoids the cost of serializing the bundle graph for single file change builds.
  let useMainThread = bundles.length === 1 || bundles.filter(b => !api.canSkipSubrequest((0, _PackageRequest.getPackageRequestId)(b))).length === 1;
  try {
    await Promise.all(bundles.map(async bundle => {
      let request = (0, _PackageRequest.createPackageRequest)({
        bundle,
        bundleGraph,
        bundleGraphReference: ref,
        optionsRef,
        useMainThread
      });
      let {
        info: infos
      } = await api.runRequest(request, {
        force: !(await (0, _PackageRequest.canSkipPackageRequest)(api, request))
      });
      if (!useMainThread) {
        // Force a refresh of the cache to avoid a race condition
        // between threaded reads and writes that can result in an LMDB cache miss:
        //   1. The main thread has read some value from cache, necessitating a read transaction.
        //   2. Concurrently, Thread A finishes a packaging request.
        //   3. Subsequently, the main thread is tasked with this request, but fails because the read transaction is stale.
        // This only occurs if the reading thread has a transaction that was created before the writing thread committed,
        // and the transaction is still live when the reading thread attempts to get the written value.
        // See https://github.com/parcel-bundler/parcel/issues/9121
        options.cache.refresh();
      }
      bundleInfoMap[bundle.id] = infos;
      if (infos.every(info => info.hashReferences.length === 0)) {
        hashRefToNameHash.set(bundle.hashReference, options.shouldContentHash ? infos.length === 1 ? infos[0].hash.slice(-8) : (0, _rust().hashString)(infos.map(i => i.hash).join(':')).slice(-8) : bundle.id.slice(-8));
        for (let info of infos) {
          let writeBundleRequest = (0, _WriteBundleRequest.default)({
            bundle,
            info,
            hashRefToNameHash,
            bundleGraph
          });
          let promise = api.runRequest(writeBundleRequest);
          // If the promise rejects before we await it (below), we don't want to crash the build.
          promise.catch(() => {});
          writeEarlyPromises[info.cacheKeys.content] = promise;
        }
      }
    }));
    assignComplexNameHashes(hashRefToNameHash, bundles, bundleInfoMap, options);
    await Promise.all(bundles.map(bundle => {
      let promise = Promise.all(bundleInfoMap[bundle.id].map(info => {
        return writeEarlyPromises[info.cacheKeys.content] ?? api.runRequest((0, _WriteBundleRequest.default)({
          bundle,
          info,
          hashRefToNameHash,
          bundleGraph
        }));
      }));
      return promise.then(r => res.set(bundle.id, r.flat()));
    }));
    api.storeResult(res);
    return res;
  } finally {
    await dispose();
  }
}
function assignComplexNameHashes(hashRefToNameHash, bundles, bundleInfoMap, options) {
  for (let bundle of bundles) {
    if (hashRefToNameHash.get(bundle.hashReference) != null) {
      continue;
    }
    hashRefToNameHash.set(bundle.hashReference, options.shouldContentHash ? (0, _rust().hashString)([...getBundlesIncludedInHash(bundle.id, bundleInfoMap)].flatMap(bundleId => bundleInfoMap[bundleId].map(i => i.hash)).join(':')).slice(-8) : bundle.id.slice(-8));
  }
}
function getBundlesIncludedInHash(bundleId, bundleInfoMap, included = new Set()) {
  included.add(bundleId);
  for (let info of bundleInfoMap[bundleId]) {
    for (let hashRef of info.hashReferences) {
      let referencedId = getIdFromHashRef(hashRef);
      if (!included.has(referencedId)) {
        getBundlesIncludedInHash(referencedId, bundleInfoMap, included);
      }
    }
  }
  return included;
}
function getIdFromHashRef(hashRef) {
  return hashRef.slice(_constants.HASH_REF_PREFIX.length);
}