"use strict";

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

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.exportManyBareScope = exportManyBareScope;
exports.exportMany = exportMany;

function _bluebird() {
  const data = require("bluebird");

  _bluebird = function () {
    return data;
  };

  return data;
}

function _ramda() {
  const data = _interopRequireDefault(require("ramda"));

  _ramda = function () {
    return data;
  };

  return data;
}

function _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));

  _pMapSeries = function () {
    return data;
  };

  return data;
}

function _enrichContextFromGlobal() {
  const data = _interopRequireDefault(require("../../hooks/utils/enrich-context-from-global"));

  _enrichContextFromGlobal = function () {
    return data;
  };

  return data;
}

function _bitId() {
  const data = require("../../bit-id");

  _bitId = function () {
    return data;
  };

  return data;
}

function _logger() {
  const data = _interopRequireDefault(require("../../logger/logger"));

  _logger = function () {
    return data;
  };

  return data;
}

function _exceptions() {
  const data = require("../exceptions");

  _exceptions = function () {
    return data;
  };

  return data;
}

function _componentObjects() {
  const data = _interopRequireDefault(require("../component-objects"));

  _componentObjects = function () {
    return data;
  };

  return data;
}

function _objects() {
  const data = require("../objects");

  _objects = function () {
    return data;
  };

  return data;
}

function _models() {
  const data = require("../models");

  _models = function () {
    return data;
  };

  return data;
}

function _scopeRemotes() {
  const data = require("../scope-remotes");

  _scopeRemotes = function () {
    return data;
  };

  return data;
}

function _scopeComponentsImporter() {
  const data = _interopRequireDefault(require("./scope-components-importer"));

  _scopeComponentsImporter = function () {
    return data;
  };

  return data;
}

function _constants() {
  const data = require("../../constants");

  _constants = function () {
    return data;
  };

  return data;
}

function _componentIdToPackageName() {
  const data = _interopRequireDefault(require("../../utils/bit/component-id-to-package-name"));

  _componentIdToPackageName = function () {
    return data;
  };

  return data;
}

function _source() {
  const data = _interopRequireDefault(require("../models/source"));

  _source = function () {
    return data;
  };

  return data;
}

/**
 * @TODO there is no real difference between bare scope and a working directory scope - let's adjust terminology to avoid confusions in the future
 * saves a component into the objects directory of the remote scope, then, resolves its
 * dependencies, saves them as well. Finally runs the build process if needed on an isolated
 * environment.
 */
function exportManyBareScope(_x, _x2, _x3) {
  return _exportManyBareScope.apply(this, arguments);
}

function _exportManyBareScope() {
  _exportManyBareScope = (0, _bluebird().coroutine)(function* (scope, componentsObjects, clientIsOld) {
    _logger().default.debugAndAddBreadCrumb('scope.exportManyBareScope', `Going to save ${componentsObjects.length} components`);

    const manyObjects = componentsObjects.map(componentObjects => componentObjects.toObjects(scope.objects));
    const mergedIds = yield mergeObjects(scope, manyObjects);

    _logger().default.debugAndAddBreadCrumb('exportManyBareScope', 'will try to importMany in case there are missing dependencies');

    const scopeComponentsImporter = _scopeComponentsImporter().default.getInstance(scope);

    yield scopeComponentsImporter.importMany(mergedIds, true, false); // resolve dependencies

    _logger().default.debugAndAddBreadCrumb('exportManyBareScope', 'successfully ran importMany');

    yield scope.objects.persist();

    _logger().default.debugAndAddBreadCrumb('exportManyBareScope', 'objects were written successfully to the filesystem'); // @todo: this is a temp workaround, remove once v15 is out


    if (clientIsOld) {
      const manyCompVersions = manyObjects.map(objects => objects.component.toComponentVersion(_constants().LATEST));

      const bitIds = _bitId().BitIds.fromArray(manyCompVersions.map(compVersion => compVersion.id));

      _logger().default.debug('exportManyBareScope: completed. exit.');

      return bitIds;
    }

    _logger().default.debug('exportManyBareScope: completed. exit.');

    return mergedIds;
  });
  return _exportManyBareScope.apply(this, arguments);
}

function exportMany(_x4, _x5, _x6) {
  return _exportMany.apply(this, arguments);
}
/**
 * merge components into the scope.
 *
 * a component might have multiple versions that some where merged and some were not.
 * the BitIds returned here includes the versions that were merged. so it could contain multiple
 * ids of the same component with different versions
 */


function _exportMany() {
  _exportMany = (0, _bluebird().coroutine)(function* (scope, ids, remoteName, context = {}) {
    _logger().default.debugAndAddBreadCrumb('scope.exportMany', 'ids: {ids}', {
      ids: ids.toString()
    });

    const remotes = yield (0, _scopeRemotes().getScopeRemotes)(scope);
    const remote = yield remotes.resolve(remoteName, scope);
    const componentObjects = yield (0, _pMapSeries().default)(ids, id => scope.sources.getObjects(id));
    const componentsAndObjects = [];
    (0, _enrichContextFromGlobal().default)(context);
    const manyObjectsP = componentObjects.map(
    /*#__PURE__*/
    function () {
      var _ref = (0, _bluebird().coroutine)(function* (componentObject) {
        const componentAndObject = componentObject.toObjects(scope.objects);
        componentAndObject.component.clearStateData();
        convertNonScopeToCorrectScope(scope, componentAndObject, remoteName);
        yield changePartialNamesToFullNamesInDists(scope, componentAndObject.component, componentAndObject.objects);
        componentsAndObjects.push(componentAndObject);
        const componentBuffer = yield componentAndObject.component.compress();
        const objectsBuffer = yield Promise.all(componentAndObject.objects.map(obj => obj.compress()));
        return new (_componentObjects().default)(componentBuffer, objectsBuffer);
      });

      return function (_x12) {
        return _ref.apply(this, arguments);
      };
    }());
    const manyObjects = yield Promise.all(manyObjectsP);
    let exportedIds;

    try {
      exportedIds = yield remote.pushMany(manyObjects, context);

      _logger().default.debugAndAddBreadCrumb('exportMany', 'successfully pushed all ids to the bare-scope, going to save them back to local scope');
    } catch (err) {
      _logger().default.warnAndAddBreadCrumb('exportMany', 'failed pushing ids to the bare-scope');

      return Promise.reject(err);
    }

    yield Promise.all(ids.map(id => scope.sources.removeComponentById(id)));
    ids.map(id => scope.createSymlink(id, remoteName));
    componentsAndObjects.map(componentObject => scope.sources.put(componentObject));
    yield scope.objects.persist(); // remove version. exported component might have multiple versions exported

    const idsWithRemoteScope = exportedIds.map(id => _bitId().BitId.parse(id, true).changeVersion(null));
    return _bitId().BitIds.uniqFromArray(idsWithRemoteScope);
  });
  return _exportMany.apply(this, arguments);
}

function mergeObjects(_x7, _x8) {
  return _mergeObjects.apply(this, arguments);
}
/**
 * When exporting components with dependencies to a bare-scope, some of the dependencies may be created locally and as
 * a result their scope-name is null. Once the bare-scope gets the components, it needs to convert these scope names
 * to the bare-scope name.
 * Since the changes it does affect the Version objects, the version REF of a component, needs to be changed as well.
 */


function _mergeObjects() {
  _mergeObjects = (0, _bluebird().coroutine)(function* (scope, manyObjects) {
    const mergeResults = yield Promise.all(manyObjects.map(
    /*#__PURE__*/
    function () {
      var _ref2 = (0, _bluebird().coroutine)(function* (objects) {
        try {
          const result = yield scope.sources.merge(objects, true, false);
          return result;
        } catch (err) {
          if (err instanceof _exceptions().MergeConflict) {
            return err; // don't throw. instead, get all components with merge-conflicts
          }

          throw err;
        }
      });

      return function (_x13) {
        return _ref2.apply(this, arguments);
      };
    }()));
    const componentsWithConflicts = mergeResults.filter(result => result instanceof _exceptions().MergeConflict);

    if (componentsWithConflicts.length) {
      const idsAndVersions = componentsWithConflicts.map(c => ({
        id: c.id,
        versions: c.versions
      })); // sort to have a consistent error message

      const idsAndVersionsSorted = _ramda().default.sortBy(_ramda().default.prop('id'), idsAndVersions);

      throw new (_exceptions().MergeConflictOnRemote)(idsAndVersionsSorted);
    }

    const mergedComponents = mergeResults.filter(({
      mergedVersions
    }) => mergedVersions.length);

    const getMergedIds = ({
      mergedComponent,
      mergedVersions
    }) => mergedVersions.map(version => mergedComponent.toBitId().changeVersion(version));

    return _bitId().BitIds.fromArray(_ramda().default.flatten(mergedComponents.map(getMergedIds)));
  });
  return _mergeObjects.apply(this, arguments);
}

function convertNonScopeToCorrectScope(scope, componentsObjects, remoteScope) {
  const getIdWithUpdatedScope = dependencyId => {
    if (dependencyId.scope) return dependencyId;

    const depId = _models().ModelComponent.fromBitId(dependencyId); // todo: use 'load' for async and switch the foreach with map.


    const dependencyObject = scope.objects.loadSync(depId.hash());

    if (dependencyObject instanceof _models().Symlink) {
      return dependencyId.changeScope(dependencyObject.realScope);
    }

    return dependencyId.changeScope(remoteScope);
  };

  const getBitIdsWithUpdatedScope = bitIds => {
    const updatedIds = bitIds.map(id => getIdWithUpdatedScope(id));
    return _bitId().BitIds.fromArray(updatedIds);
  };

  componentsObjects.objects.forEach(object => {
    if (object instanceof _models().Version) {
      const hashBefore = object.hash().toString();
      object.getAllDependencies().forEach(dependency => {
        dependency.id = getIdWithUpdatedScope(dependency.id);
      });
      object.flattenedDependencies = getBitIdsWithUpdatedScope(object.flattenedDependencies);
      object.flattenedDevDependencies = getBitIdsWithUpdatedScope(object.flattenedDevDependencies);
      object.flattenedCompilerDependencies = getBitIdsWithUpdatedScope(object.flattenedCompilerDependencies);
      object.flattenedTesterDependencies = getBitIdsWithUpdatedScope(object.flattenedTesterDependencies);
      const hashAfter = object.hash().toString();

      if (hashBefore !== hashAfter) {
        _logger().default.debugAndAddBreadCrumb('scope._convertNonScopeToCorrectScope', `switching {id} version hash from ${hashBefore} to ${hashAfter}`, {
          id: componentsObjects.component.id().toString()
        });

        const versions = componentsObjects.component.versions;
        Object.keys(versions).forEach(version => {
          if (versions[version].toString() === hashBefore) {
            versions[version] = _objects().Ref.from(hashAfter);
          }
        });
      }
    }
  });
  componentsObjects.component.scope = remoteScope;
}
/**
 * see https://github.com/teambit/bit/issues/1770 for complete info
 * some compilers require the links to be part of the bundle, change the component name in these
 * files from the id without scope to the id with the scope
 * e.g. `@bit/utils.is-string` becomes `@bit/scope-name.utils.is-string`
 */


function changePartialNamesToFullNamesInDists(_x9, _x10, _x11) {
  return _changePartialNamesToFullNamesInDists.apply(this, arguments);
}

function _changePartialNamesToFullNamesInDists() {
  _changePartialNamesToFullNamesInDists = (0, _bluebird().coroutine)(function* (scope, component, objects) {
    // $FlowFixMe
    const versions = objects.filter(object => object instanceof _models().Version);
    yield Promise.all(versions.map(version => _replaceDistsOfVersionIfNeeded(version)));

    function _replaceDistsOfVersionIfNeeded(_x14) {
      return _replaceDistsOfVersionIfNeeded2.apply(this, arguments);
    }

    function _replaceDistsOfVersionIfNeeded2() {
      _replaceDistsOfVersionIfNeeded2 = (0, _bluebird().coroutine)(function* (version) {
        const dists = version.dists;
        if (!dists) return;
        yield Promise.all(dists.map(
        /*#__PURE__*/
        function () {
          var _ref3 = (0, _bluebird().coroutine)(function* (dist) {
            const newDistObject = yield _createNewDistIfNeeded(version, dist);

            if (newDistObject) {
              dist.file = newDistObject.hash();
              objects.push(newDistObject);
            }

            return null;
          });

          return function (_x17) {
            return _ref3.apply(this, arguments);
          };
        }()));
      });
      return _replaceDistsOfVersionIfNeeded2.apply(this, arguments);
    }

    function _createNewDistIfNeeded(_x15, _x16) {
      return _createNewDistIfNeeded2.apply(this, arguments);
    }

    function _createNewDistIfNeeded2() {
      _createNewDistIfNeeded2 = (0, _bluebird().coroutine)(function* (version, dist) {
        const currentHash = dist.file; // $FlowFixMe

        const distObject = yield scope.objects.load(currentHash);
        const distString = distObject.contents.toString();
        const dependenciesIds = version.getAllDependencies().map(d => d.id);
        const allIds = [...dependenciesIds, component.toBitId()];
        let newDistString = distString;
        allIds.forEach(id => {
          const idWithoutScope = id.changeScope(null);
          const pkgNameWithoutScope = (0, _componentIdToPackageName().default)(idWithoutScope, component.bindingPrefix);
          const pkgNameWithScope = (0, _componentIdToPackageName().default)(id, component.bindingPrefix);
          newDistString = newDistString.replace(new RegExp(pkgNameWithoutScope, 'g'), pkgNameWithScope);
        });

        if (newDistString !== distString) {
          return _source().default.from(Buffer.from(newDistString));
        }

        return null;
      });
      return _createNewDistIfNeeded2.apply(this, arguments);
    }
  });
  return _changePartialNamesToFullNamesInDists.apply(this, arguments);
}