"use strict";

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");

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

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

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 _bitId() {
  const data = require("../../bit-id");

  _bitId = function () {
    return data;
  };

  return data;
}

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

  _ = function () {
    return data;
  };

  return data;
}

function packageJsonUtils() {
  const data = _interopRequireWildcard(require("../component/package-json-utils"));

  packageJsonUtils = function () {
    return data;
  };

  return data;
}

function _installPackages() {
  const data = require("../../npm-client/install-packages");

  _installPackages = function () {
    return data;
  };

  return data;
}

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

  _logger = function () {
    return data;
  };

  return data;
}

function _defaultErrorHandler() {
  const data = _interopRequireDefault(require("../../cli/default-error-handler"));

  _defaultErrorHandler = function () {
    return data;
  };

  return data;
}

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

  _scopeRemotes = function () {
    return data;
  };

  return data;
}

function _packageJsonFile() {
  const data = _interopRequireDefault(require("../component/package-json-file"));

  _packageJsonFile = function () {
    return data;
  };

  return data;
}

function _componentVersion() {
  const data = _interopRequireDefault(require("../../scope/component-version"));

  _componentVersion = function () {
    return data;
  };

  return data;
}

function _deleteComponentFiles() {
  const data = _interopRequireDefault(require("./delete-component-files"));

  _deleteComponentFiles = function () {
    return data;
  };

  return data;
}

/**
 * 
 * a classic use case of eject is when a user imports a component using `bit import` to update it,
 * but the user has no intention to have the code as part of the project source code.
 * the eject provides the option to delete the component locally and install it via the NPM client.
 *
 * an implementation note, the entire process is done with rollback in mind.
 * since installing the component via NPM client is an error prone process, we do it first, before
 * removing the component files, so then it's easier to rollback.
 */
class EjectComponents {
  // for rollback in case of errors
  constructor(consumer, componentsIds, force) {
    this.consumer = consumer;
    this.componentsIds = componentsIds;
    this.force = force || false;
    this.componentsToEject = new (_bitId().BitIds)();
    this.failedComponents = {
      modifiedComponents: new (_bitId().BitIds)(),
      stagedComponents: new (_bitId().BitIds)(),
      notExportedComponents: new (_bitId().BitIds)(),
      selfHostedExportedComponents: new (_bitId().BitIds)()
    };
  }

  eject() {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      yield _this.decideWhichComponentsToEject();

      _logger().default.debugAndAddBreadCrumb('eject-components.eject', `${_this.componentsToEject.length} to eject`);

      if (_this.componentsToEject.length) {
        _this.originalPackageJson = yield _packageJsonFile().default.load(_this.consumer.getPath());
        yield _this.removeComponentsFromPackageJsonAndNodeModules();
        yield _this.addComponentsAsPackagesToPackageJson();
        yield _this.installPackagesUsingNPMClient();
        yield _this.removeComponents();
      }

      _logger().default.debug('eject: completed successfully');

      return {
        ejectedComponents: _this.componentsToEject,
        failedComponents: _this.failedComponents
      };
    })();
  }

  decideWhichComponentsToEject() {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debug('eject: getting the components status');

      if (_ramda().default.isEmpty(_this2.componentsIds)) return;
      const remotes = yield (0, _scopeRemotes().getScopeRemotes)(_this2.consumer.scope);
      const hubExportedComponents = new (_bitId().BitIds)();

      _this2.componentsIds.forEach(bitId => {
        if (!bitId.hasScope()) _this2.failedComponents.notExportedComponents.push(bitId);else if (remotes.isHub(bitId.scope)) hubExportedComponents.push(bitId);else _this2.failedComponents.selfHostedExportedComponents.push(bitId);
      });

      if (_this2.force) {
        _this2.componentsToEject = hubExportedComponents;
      } else {
        yield Promise.all(hubExportedComponents.map(
        /*#__PURE__*/
        function () {
          var _ref = (0, _bluebird().coroutine)(function* (id) {
            try {
              const componentStatus = yield _this2.consumer.getComponentStatusById(id);
              if (componentStatus.modified) _this2.failedComponents.modifiedComponents.push(id);else if (componentStatus.staged) _this2.failedComponents.stagedComponents.push(id);else _this2.componentsToEject.push(id);
            } catch (err) {
              _this2.throwEjectError(`eject operation failed getting the status of ${id.toString()}, no action has been done.
            please fix the issue to continue.`, err);
            }
          });

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

  removeComponentsFromPackageJsonAndNodeModules() {
    var _this3 = this;

    return (0, _bluebird().coroutine)(function* () {
      const action = 'removing the existing components from package.json and node_modules';

      try {
        _logger().default.debugAndAddBreadCrumb('eject', action);

        yield packageJsonUtils().removeComponentsFromWorkspacesAndDependencies(_this3.consumer, _this3.componentsToEject);
      } catch (err) {
        _logger().default.warn(`eject: failed ${action}, restoring package.json`);

        yield _this3.rollBack(action);

        _this3.throwEjectError(_this3._buildExceptionMessageWithRollbackData(action), err);
      }
    })();
  }

  addComponentsAsPackagesToPackageJson() {
    var _this4 = this;

    return (0, _bluebird().coroutine)(function* () {
      const action = 'adding the components as packages into package.json';

      try {
        _logger().default.debugAndAddBreadCrumb('eject', action);

        const componentsVersions = yield Promise.all(_this4.componentsToEject.map(
        /*#__PURE__*/
        function () {
          var _ref2 = (0, _bluebird().coroutine)(function* (bitId) {
            const modelComponent = yield _this4.consumer.scope.getModelComponent(bitId); // $FlowFixMe componentsToEject has scope and version

            return new (_componentVersion().default)(modelComponent, bitId.version);
          });

          return function (_x2) {
            return _ref2.apply(this, arguments);
          };
        }()));
        yield packageJsonUtils().addComponentsWithVersionToRoot(_this4.consumer, componentsVersions);
      } catch (err) {
        _logger().default.error(err);

        _logger().default.warn(`eject: failed ${action}, restoring package.json`);

        yield _this4.rollBack(action);

        _this4.throwEjectError(_this4._buildExceptionMessageWithRollbackData(action), err);
      }
    })();
  }

  installPackagesUsingNPMClient() {
    var _this5 = this;

    return (0, _bluebird().coroutine)(function* () {
      const action = 'installing the components using the NPM client';

      try {
        _logger().default.debugAndAddBreadCrumb('eject', action);

        yield (0, _installPackages().installPackages)(_this5.consumer, [], true, true);
      } catch (err) {
        yield _this5.rollBack(action);

        _this5.throwEjectError(_this5._buildExceptionMessageWithRollbackData(action), err);
      }
    })();
  }

  rollBack(action) {
    var _this6 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (_this6.originalPackageJson.fileExist) {
        _logger().default.warn(`eject: failed ${action}, restoring package.json`);

        yield _this6.originalPackageJson.write();
      } else {
        _logger().default.warn(`eject: failed ${action}, no package.json to restore`);
      }
    })();
  }

  _buildExceptionMessageWithRollbackData(action) {
    return `eject failed ${action}.
your package.json (if existed) has been restored, however, some bit generated data may have been deleted, please run "bit link" to restore them.`;
  }

  removeComponents() {
    var _this7 = this;

    return (0, _bluebird().coroutine)(function* () {
      try {
        _logger().default.debug('eject: removing the components files from the filesystem');

        yield _this7.removeLocalComponents();
      } catch (err) {
        _this7.throwEjectError(`eject operation has installed your components successfully using the NPM client.
however, it failed removing the old components from the filesystem.
please use bit remove command to remove them.`, err);
      }
    })();
  }
  /**
   * as part of the 'eject' operation, a component is removed locally. as opposed to the remove
   * command, in this case, no need to remove the objects from the scope, only remove from the
   * filesystem, which means, delete the component files, untrack from .bitmap and clean
   * package.json and bit.json traces.
   */


  removeLocalComponents() {
    var _this8 = this;

    return (0, _bluebird().coroutine)(function* () {
      // @todo: what about the dependencies? if they are getting deleted we have to make sure they
      // not used anywhere else. Because this is part of the eject operation, the user probably
      // gets the dependencies as npm packages so we don't need to worry much about have extra
      // dependencies on the filesystem
      yield (0, _deleteComponentFiles().default)(_this8.consumer, _this8.componentsToEject, true);
      yield _this8.consumer.cleanFromBitMap(_this8.componentsToEject, new (_bitId().BitIds)());
    })();
  }

  throwEjectError(message, originalError) {
    // $FlowFixMe that's right, we don't know whether originalError has 'msg' property, but most have. what other choices do we have?
    const originalErrorMessage = (0, _defaultErrorHandler().default)(originalError) || originalError.msg || originalError;

    _logger().default.error(`eject has stopped due to an error ${originalErrorMessage}`, originalError);

    throw new Error(`${message}

got the following error: ${originalErrorMessage}`);
  }

}

exports.default = EjectComponents;