"use strict";

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

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.applyModifiedVersion = applyModifiedVersion;
exports.default = void 0;

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

  _bluebird = function () {
    return data;
  };

  return data;
}

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));

  _fsExtra = function () {
    return data;
  };

  return data;
}

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

  _pMapSeries = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

function _() {
  const data = require("..");

  _ = function () {
    return data;
  };

  return data;
}

function _component() {
  const data = _interopRequireDefault(require("../component"));

  _component = function () {
    return data;
  };

  return data;
}

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

  _constants = function () {
    return data;
  };

  return data;
}

function _path2() {
  const data = require("../../utils/path");

  _path2 = function () {
    return data;
  };

  return data;
}

function _version() {
  const data = _interopRequireDefault(require("../../scope/models/version"));

  _version = function () {
    return data;
  };

  return data;
}

function _sources() {
  const data = require("../component/sources");

  _sources = function () {
    return data;
  };

  return data;
}

function _mergeVersion() {
  const data = require("./merge-version");

  _mergeVersion = function () {
    return data;
  };

  return data;
}

function _generalError() {
  const data = _interopRequireDefault(require("../../error/general-error"));

  _generalError = function () {
    return data;
  };

  return data;
}

function _manyComponentsWriter() {
  const data = _interopRequireDefault(require("../component-ops/many-components-writer"));

  _manyComponentsWriter = function () {
    return data;
  };

  return data;
}

function _repositories() {
  const data = require("../../scope/repositories");

  _repositories = function () {
    return data;
  };

  return data;
}

var _default =
/*#__PURE__*/
function () {
  var _checkoutVersion = (0, _bluebird().coroutine)(function* (consumer, checkoutProps) {
    const {
      version,
      ids,
      promptMergeOptions
    } = checkoutProps;
    const {
      components
    } = yield consumer.loadComponents(ids);
    const allComponentsStatus = yield getAllComponentsStatus();
    const componentWithConflict = allComponentsStatus.find(component => component.mergeResults && component.mergeResults.hasConflicts);

    if (componentWithConflict) {
      if (!promptMergeOptions && !checkoutProps.mergeStrategy) {
        throw new (_generalError().default)(`automatic merge has failed for component ${componentWithConflict.id.toStringWithoutVersion()}.\nplease use "--manual" to manually merge changes or use "--theirs / --ours" to choose one of the conflicted versions`);
      }

      if (!checkoutProps.mergeStrategy) checkoutProps.mergeStrategy = yield (0, _mergeVersion().getMergeStrategyInteractive)();
    }

    const failedComponents = allComponentsStatus.filter(componentStatus => componentStatus.failureMessage) // $FlowFixMe componentStatus.failureMessage is set
    .map(componentStatus => ({
      id: componentStatus.id,
      failureMessage: componentStatus.failureMessage
    }));
    const succeededComponents = allComponentsStatus.filter(componentStatus => !componentStatus.failureMessage); // do not use Promise.all for applyVersion. otherwise, it'll write all components in parallel,
    // which can be an issue when some components are also dependencies of others

    const componentsResults = yield (0, _pMapSeries().default)(succeededComponents, ({
      id,
      componentFromFS,
      mergeResults
    }) => {
      return applyVersion(consumer, id, componentFromFS, mergeResults, checkoutProps);
    });
    return {
      components: componentsResults,
      version,
      failedComponents
    };

    function getAllComponentsStatus() {
      return _getAllComponentsStatus.apply(this, arguments);
    }

    function _getAllComponentsStatus() {
      _getAllComponentsStatus = (0, _bluebird().coroutine)(function* () {
        const tmp = new (_repositories().Tmp)(consumer.scope);

        try {
          const componentsStatus = yield Promise.all(components.map(component => getComponentStatus(consumer, component, checkoutProps)));
          yield tmp.clear();
          return componentsStatus;
        } catch (err) {
          yield tmp.clear();
          throw err;
        }
      });
      return _getAllComponentsStatus.apply(this, arguments);
    }
  });

  function checkoutVersion(_x, _x2) {
    return _checkoutVersion.apply(this, arguments);
  }

  return checkoutVersion;
}();

exports.default = _default;

function getComponentStatus(_x3, _x4, _x5) {
  return _getComponentStatus.apply(this, arguments);
}
/**
 * 1) when the files are modified with conflicts and the strategy is "ours", leave the FS as is
 * and update only bitmap id version. (not the componentMap object).
 *
 * 2) when the files are modified with conflicts and the strategy is "theirs", write the component
 * according to id.version.
 *
 * 3) when files are modified with no conflict or files are modified with conflicts and the
 * strategy is manual, load the component according to id.version and update component.files.
 * applyModifiedVersion() docs explains what files are updated/added.
 *
 * 4) when --reset flag is used, write the component according to the bitmap version
 *
 * Side note:
 * Deleted file => if files are in used version but not in the modified one, no need to delete it. (similar to git).
 * Added file => if files are not in used version but in the modified one, they'll be under mergeResults.addFiles
 */


function _getComponentStatus() {
  _getComponentStatus = (0, _bluebird().coroutine)(function* (consumer, component, checkoutProps) {
    const {
      version,
      latestVersion,
      reset
    } = checkoutProps;
    const componentModel = yield consumer.scope.getModelComponentIfExist(component.id);
    const componentStatus = {
      id: component.id
    };

    const returnFailure = msg => {
      componentStatus.failureMessage = msg;
      return componentStatus;
    };

    if (!componentModel) {
      return returnFailure(`component ${component.id.toString()} doesn't have any version yet`);
    }

    const getNewVersion = () => {
      if (reset) return component.id.version; // $FlowFixMe if !reset the version is defined

      return latestVersion ? componentModel.latest() : version;
    };

    const newVersion = getNewVersion();

    if (version && !latestVersion && !componentModel.hasVersion(version)) {
      return returnFailure(`component ${component.id.toStringWithoutVersion()} doesn't have version ${version}`);
    }

    const existingBitMapId = consumer.bitMap.getBitId(component.id, {
      ignoreVersion: true
    });
    const currentlyUsedVersion = existingBitMapId.version;

    if (version && currentlyUsedVersion === version) {
      // it won't be relevant for 'reset' as it doesn't have a version
      return returnFailure(`component ${component.id.toStringWithoutVersion()} is already at version ${version}`);
    }

    if (latestVersion && currentlyUsedVersion === newVersion) {
      return returnFailure(`component ${component.id.toStringWithoutVersion()} is already at the latest version, which is ${newVersion}`);
    }

    const baseComponentVersion = yield componentModel.loadVersion(currentlyUsedVersion, consumer.scope.objects);
    const baseComponent = yield consumer.loadComponentFromModel(existingBitMapId);
    const isModified = yield consumer.isComponentModified(baseComponentVersion, component);

    if (!isModified && reset) {
      return returnFailure(`component ${component.id.toStringWithoutVersion()} is not modified`);
    }

    let mergeResults;

    if (isModified && version) {
      const newBitId = component.id.changeVersion(newVersion);
      const currentComponent = yield consumer.loadComponentFromModel(newBitId);
      mergeResults = yield (0, _mergeVersion().threeWayMerge)({
        consumer,
        otherComponent: component,
        otherVersion: currentlyUsedVersion,
        currentComponent,
        currentVersion: newVersion,
        baseComponent
      });
    }

    const versionRef = componentModel.versions[newVersion];
    const componentVersion = yield consumer.scope.getObject(versionRef.hash);
    const newId = component.id.changeVersion(newVersion);
    return {
      componentFromFS: component,
      componentFromModel: componentVersion,
      id: newId,
      mergeResults
    };
  });
  return _getComponentStatus.apply(this, arguments);
}

function applyVersion(_x6, _x7, _x8, _x9, _x10) {
  return _applyVersion.apply(this, arguments);
}
/**
 * relevant only when
 * 1) there is no conflict => add files from mergeResults: addFiles, overrideFiles and modifiedFiles.output.
 * 2) there is conflict and mergeStrategy is manual => add files from mergeResults: addFiles, overrideFiles and modifiedFiles.conflict.
 *
 * this function only updates the files content, it doesn't write the files
 */


function _applyVersion() {
  _applyVersion = (0, _bluebird().coroutine)(function* (consumer, id, componentFromFS, mergeResults, checkoutProps) {
    const {
      mergeStrategy,
      verbose,
      skipNpmInstall,
      ignoreDist
    } = checkoutProps;
    const filesStatus = {};

    if (mergeResults && mergeResults.hasConflicts && mergeStrategy === _mergeVersion().MergeOptions.ours) {
      componentFromFS.files.forEach(file => {
        filesStatus[(0, _path2().pathNormalizeToLinux)(file.relative)] = _mergeVersion().FileStatus.unchanged;
      });
      consumer.bitMap.updateComponentId(id);
      return {
        id,
        filesStatus
      };
    }

    const componentWithDependencies = yield consumer.loadComponentWithDependenciesFromModel(id);
    const componentMap = componentFromFS.componentMap;
    if (!componentMap) throw new (_generalError().default)('applyVersion: componentMap was not found');

    if (componentMap.origin === _constants().COMPONENT_ORIGINS.AUTHORED && !id.scope) {
      componentWithDependencies.dependencies = [];
      componentWithDependencies.devDependencies = [];
      componentWithDependencies.compilerDependencies = [];
      componentWithDependencies.testerDependencies = [];
    }

    const rootDir = componentMap.rootDir;

    const shouldWritePackageJson =
    /*#__PURE__*/
    function () {
      var _ref = (0, _bluebird().coroutine)(function* () {
        if (!rootDir) return false;

        const packageJsonPath = _path().default.join(consumer.getPath(), rootDir, 'package.json');

        return _fsExtra().default.exists(packageJsonPath);
      });

      return function shouldWritePackageJson() {
        return _ref.apply(this, arguments);
      };
    }();

    const shouldInstallNpmPackages = () => {
      if (componentMap.origin === _constants().COMPONENT_ORIGINS.AUTHORED) return false;
      return !skipNpmInstall;
    };

    const writePackageJson = yield shouldWritePackageJson();
    const files = componentWithDependencies.component.files;
    files.forEach(file => {
      filesStatus[(0, _path2().pathNormalizeToLinux)(file.relative)] = _mergeVersion().FileStatus.updated;
    });
    let modifiedStatus = {};

    if (mergeResults) {
      // update files according to the merge results
      modifiedStatus = applyModifiedVersion(files, mergeResults, mergeStrategy);
    }

    const shouldDependenciesSaveAsComponents = yield consumer.shouldDependenciesSavedAsComponents([id]);
    componentWithDependencies.component.dependenciesSavedAsComponents = shouldDependenciesSaveAsComponents[0].saveDependenciesAsComponents;
    const manyComponentsWriter = new (_manyComponentsWriter().default)({
      consumer,
      componentsWithDependencies: [componentWithDependencies],
      installNpmPackages: shouldInstallNpmPackages(),
      override: true,
      writeConfig: Boolean(componentMap.configDir),
      // write bit.json and config files only if it was there before
      configDir: componentMap.configDir,
      verbose,
      writeDists: !ignoreDist,
      writePackageJson
    });
    yield manyComponentsWriter.writeAll();
    return {
      id,
      filesStatus: Object.assign(filesStatus, modifiedStatus)
    };
  });
  return _applyVersion.apply(this, arguments);
}

function applyModifiedVersion(componentFiles, mergeResults, mergeStrategy) {
  const filesStatus = {};
  if (mergeResults.hasConflicts && mergeStrategy !== _mergeVersion().MergeOptions.manual) return filesStatus;
  mergeResults.modifiedFiles.forEach(file => {
    const filePath = _path().default.normalize(file.filePath);

    const foundFile = componentFiles.find(componentFile => componentFile.relative === filePath);
    if (!foundFile) throw new (_generalError().default)(`file ${filePath} not found`);

    if (file.conflict) {
      foundFile.contents = Buffer.from(file.conflict);
      filesStatus[file.filePath] = _mergeVersion().FileStatus.manual;
    } else if (file.output) {
      foundFile.contents = Buffer.from(file.output);
      filesStatus[file.filePath] = _mergeVersion().FileStatus.merged;
    } else {
      throw new (_generalError().default)('file does not have output nor conflict');
    }
  });
  mergeResults.addFiles.forEach(file => {
    componentFiles.push(file.fsFile);
    filesStatus[file.filePath] = _mergeVersion().FileStatus.added;
  });
  mergeResults.overrideFiles.forEach(file => {
    const filePath = _path().default.normalize(file.filePath);

    const foundFile = componentFiles.find(componentFile => componentFile.relative === filePath);
    if (!foundFile) throw new (_generalError().default)(`file ${filePath} not found`);
    foundFile.contents = file.fsFile.contents;
    filesStatus[file.filePath] = _mergeVersion().FileStatus.overridden;
  });
  return filesStatus;
}