"use strict";

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

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.mergeVersion = mergeVersion;
exports.getMergeStrategyInteractive = getMergeStrategyInteractive;
exports.getMergeStrategy = getMergeStrategy;
exports.FileStatus = exports.MergeOptions = exports.mergeOptionsCli = 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 _chalk() {
  const data = _interopRequireDefault(require("chalk"));

  _chalk = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

function _prompts() {
  const data = require("../../../prompts");

  _prompts = function () {
    return data;
  };

  return data;
}

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

  _path2 = function () {
    return data;
  };

  return data;
}

function _twoWayMerge() {
  const data = _interopRequireDefault(require("./two-way-merge"));

  _twoWayMerge = function () {
    return data;
  };

  return data;
}

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

  _generalError = function () {
    return data;
  };

  return data;
}

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

  _componentWriter = function () {
    return data;
  };

  return data;
}

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

  _repositories = function () {
    return data;
  };

  return data;
}

const mergeOptionsCli = {
  o: 'ours',
  t: 'theirs',
  m: 'manual'
};
exports.mergeOptionsCli = mergeOptionsCli;
const MergeOptions = {
  ours: 'ours',
  theirs: 'theirs',
  manual: 'manual'
};
exports.MergeOptions = MergeOptions;
const FileStatus = {
  merged: _chalk().default.green('auto-merged'),
  manual: _chalk().default.red('CONFLICT'),
  updated: _chalk().default.green('updated'),
  added: _chalk().default.green('added'),
  overridden: _chalk().default.yellow('overridden'),
  unchanged: _chalk().default.green('unchanged')
};
exports.FileStatus = FileStatus;

function mergeVersion(_x, _x2, _x3, _x4) {
  return _mergeVersion.apply(this, arguments);
}

function _mergeVersion() {
  _mergeVersion = (0, _bluebird().coroutine)(function* (consumer, version, ids, mergeStrategy) {
    const {
      components
    } = yield consumer.loadComponents(_bitId().BitIds.fromArray(ids));
    const allComponentsStatus = yield getAllComponentsStatus();
    const componentWithConflict = allComponentsStatus.find(component => component.mergeResults.hasConflicts);

    if (componentWithConflict && !mergeStrategy) {
      mergeStrategy = yield getMergeStrategyInteractive();
    }

    const mergedComponentsP = allComponentsStatus.map(({
      id,
      componentFromFS,
      mergeResults
    }) => {
      return applyVersion(consumer, id, componentFromFS, mergeResults, mergeStrategy);
    });
    const mergedComponents = yield Promise.all(mergedComponentsP);
    return {
      components: mergedComponents,
      version
    };

    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, version)));
          yield tmp.clear();
          return componentsStatus;
        } catch (err) {
          yield tmp.clear();
          throw err;
        }
      });
      return _getAllComponentsStatus.apply(this, arguments);
    }
  });
  return _mergeVersion.apply(this, arguments);
}

function getComponentStatus(_x5, _x6, _x7) {
  return _getComponentStatus.apply(this, arguments);
}
/**
 * it doesn't matter whether the component is modified. the idea is to merge the
 * specified version with the current version.
 *
 * 1) when there are conflicts and the strategy is "ours", don't do any change to the component.
 *
 * 2) when there are conflicts and the strategy is "theirs", add all files from the specified
 * version and write the component.
 *
 * 3) when there is no conflict or there are conflicts and the strategy is manual, update
 * component.files.
 *
 * it's going to be 2-way merge:
 * current-file: is the current file.
 * base-file: empty.
 * other-file: the specified version.
 */


function _getComponentStatus() {
  _getComponentStatus = (0, _bluebird().coroutine)(function* (consumer, component, version) {
    const componentModel = yield consumer.scope.sources.get(component.id);

    if (!componentModel) {
      throw new (_generalError().default)(`component ${component.id.toString()} doesn't have any version yet`);
    }

    if (!componentModel.hasVersion(version)) {
      throw new (_generalError().default)(`component ${component.id.toStringWithoutVersion()} doesn't have version ${version}`);
    }

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

    if (currentlyUsedVersion === version) {
      throw new (_generalError().default)(`component ${component.id.toStringWithoutVersion()} is already at version ${version}`);
    }

    const otherComponent = yield consumer.loadComponentFromModel(component.id.changeVersion(version));
    const mergeResults = yield (0, _twoWayMerge().default)({
      consumer,
      otherComponent,
      otherVersion: version,
      currentComponent: component,
      // $FlowFixMe
      currentVersion: currentlyUsedVersion
    });
    return {
      componentFromFS: component,
      id: component.id,
      mergeResults
    };
  });
  return _getComponentStatus.apply(this, arguments);
}

function applyVersion(_x8, _x9, _x10, _x11, _x12) {
  return _applyVersion.apply(this, arguments);
}

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

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

    const component = componentFromFS.componentFromModel;
    if (!component) throw new (_generalError().default)('failed finding the component in the model');
    const componentMap = componentFromFS.componentMap;
    if (!componentMap) throw new (_generalError().default)('applyVersion: componentMap was not found');
    const files = componentFromFS.files;
    component.files = files;
    files.forEach(file => {
      filesStatus[(0, _path2().pathNormalizeToLinux)(file.relative)] = FileStatus.unchanged;
    }); // update files according to the merge results

    const modifiedStatus = applyModifiedVersion(consumer, files, mergeResults, mergeStrategy);

    const componentWriter = _componentWriter().default.getInstance({
      component,
      writeToPath: component.files[0].base,
      // find the current path from the files. (we use the first one but it's the same for all)
      writeConfig: false,
      // never override the existing bit.json
      writePackageJson: false,
      deleteBitDirContent: false,
      origin: componentMap.origin,
      consumer,
      bitMap: consumer.bitMap,
      existingComponentMap: componentMap
    });

    yield componentWriter.write();
    consumer.bitMap.removeComponent(component.id);
    componentWriter.origin = componentMap.origin; // $FlowFixMe todo: fix this. does configDir should be a string or ConfigDir?

    componentWriter.configDir = componentMap.configDir;
    componentWriter.addComponentToBitMap(componentMap.rootDir);
    return {
      id,
      filesStatus: Object.assign(filesStatus, modifiedStatus)
    };
  });
  return _applyVersion.apply(this, arguments);
}

function applyModifiedVersion(consumer, componentFiles, mergeResults, mergeStrategy) {
  const 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 (mergeResults.hasConflicts && mergeStrategy === MergeOptions.theirs) {
      // write the version of otherFile
      const otherFile = file.otherFile;
      foundFile.contents = otherFile.contents;
      filesStatus[file.filePath] = FileStatus.updated;
    } else if (file.conflict) {
      foundFile.contents = Buffer.from(file.conflict);
      filesStatus[file.filePath] = FileStatus.manual;
    } else if (file.output) {
      foundFile.contents = Buffer.from(file.output);
      filesStatus[file.filePath] = FileStatus.merged;
    } else {
      throw new (_generalError().default)('file does not have output nor conflict');
    }
  });
  mergeResults.addFiles.forEach(file => {
    const otherFile = file.otherFile;
    componentFiles.push(otherFile);
    filesStatus[file.filePath] = FileStatus.added;
  });
  return filesStatus;
}

function getMergeStrategyInteractive() {
  return _getMergeStrategyInteractive.apply(this, arguments);
}

function _getMergeStrategyInteractive() {
  _getMergeStrategyInteractive = (0, _bluebird().coroutine)(function* () {
    try {
      const result = yield (0, _prompts().resolveConflictPrompt)();
      return mergeOptionsCli[result.mergeStrategy];
    } catch (err) {
      // probably user clicked ^C
      throw new (_generalError().default)('the action has been canceled');
    }
  });
  return _getMergeStrategyInteractive.apply(this, arguments);
}

function getMergeStrategy(ours, theirs, manual) {
  if (ours && theirs || ours && manual || theirs && manual) {
    throw new (_generalError().default)('please choose only one of the following: ours, theirs or manual');
  }

  if (ours) return MergeOptions.ours;
  if (theirs) return MergeOptions.theirs;
  if (manual) return MergeOptions.manual;
  return null;
}