'use strict';

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

var _regenerator;

function _load_regenerator() {
  return _regenerator = _interopRequireDefault(require('babel-runtime/regenerator'));
}

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * 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.
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      */


var _ramda;

function _load_ramda() {
  return _ramda = _interopRequireDefault(require('ramda'));
}

var _bitId;

function _load_bitId() {
  return _bitId = require('../../bit-id');
}

var _;

function _load_() {
  return _ = require('..');
}

var _packageJson;

function _load_packageJson() {
  return _packageJson = _interopRequireWildcard(require('../component/package-json'));
}

var _installPackages;

function _load_installPackages() {
  return _installPackages = require('../../npm-client/install-packages');
}

var _logger;

function _load_logger() {
  return _logger = _interopRequireDefault(require('../../logger/logger'));
}

var _defaultErrorHandler;

function _load_defaultErrorHandler() {
  return _defaultErrorHandler = _interopRequireDefault(require('../../cli/default-error-handler'));
}

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var EjectComponents = function () {
  // for rollback in case of errors
  function EjectComponents(consumer, componentsIds, force) {
    _classCallCheck(this, EjectComponents);

    this.consumer = consumer;
    this.componentsIds = componentsIds;
    this.force = force || false;
    this.componentsToEject = new (_bitId || _load_bitId()).BitIds();
    this.failedComponents = {
      modifiedComponents: new (_bitId || _load_bitId()).BitIds(),
      stagedComponents: new (_bitId || _load_bitId()).BitIds(),
      notExportedComponents: new (_bitId || _load_bitId()).BitIds(),
      selfHostedExportedComponents: new (_bitId || _load_bitId()).BitIds()
    };
  }

  _createClass(EjectComponents, [{
    key: 'eject',
    value: function () {
      var _ref = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee() {
        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                _context.next = 2;
                return this.decideWhichComponentsToEject();

              case 2:
                (_logger || _load_logger()).default.debug(`eject: ${this.componentsToEject.length} to eject`);

                if (!this.componentsToEject.length) {
                  _context.next = 18;
                  break;
                }

                _context.next = 6;
                return (_packageJson || _load_packageJson()).getPackageJsonObject(this.consumer);

              case 6:
                _context.t0 = _context.sent;

                if (_context.t0) {
                  _context.next = 9;
                  break;
                }

                _context.t0 = {};

              case 9:
                this.originalPackageJson = _context.t0;
                _context.next = 12;
                return this.removeComponentsFromPackageJsonAndNodeModules();

              case 12:
                _context.next = 14;
                return this.addComponentsAsPackagesToPackageJson();

              case 14:
                _context.next = 16;
                return this.installPackagesUsingNPMClient();

              case 16:
                _context.next = 18;
                return this.removeComponents();

              case 18:
                (_logger || _load_logger()).default.debug('eject: completed successfully');
                return _context.abrupt('return', {
                  ejectedComponents: this.componentsToEject,
                  failedComponents: this.failedComponents
                });

              case 20:
              case 'end':
                return _context.stop();
            }
          }
        }, _callee, this);
      }));

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

      return eject;
    }()
  }, {
    key: 'decideWhichComponentsToEject',
    value: function () {
      var _ref2 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3() {
        var _this = this;

        var remotes, hubExportedComponents;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                (_logger || _load_logger()).default.debug('eject: getting the components status');

                if (!(_ramda || _load_ramda()).default.isEmpty(this.componentsIds)) {
                  _context3.next = 3;
                  break;
                }

                return _context3.abrupt('return');

              case 3:
                _context3.next = 5;
                return this.consumer.scope.remotes();

              case 5:
                remotes = _context3.sent;
                hubExportedComponents = new (_bitId || _load_bitId()).BitIds();

                this.componentsIds.forEach(function (bitId) {
                  if (!bitId.hasScope()) _this.failedComponents.notExportedComponents.push(bitId);else if (remotes.isHub(bitId.scope)) hubExportedComponents.push(bitId);else _this.failedComponents.selfHostedExportedComponents.push(bitId);
                });

                if (!this.force) {
                  _context3.next = 12;
                  break;
                }

                this.componentsToEject = hubExportedComponents;
                _context3.next = 14;
                break;

              case 12:
                _context3.next = 14;
                return Promise.all(hubExportedComponents.map(function () {
                  var _ref3 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(id) {
                    var componentStatus;
                    return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
                      while (1) {
                        switch (_context2.prev = _context2.next) {
                          case 0:
                            _context2.prev = 0;
                            _context2.next = 3;
                            return _this.consumer.getComponentStatusById(id);

                          case 3:
                            componentStatus = _context2.sent;

                            if (componentStatus.modified) _this.failedComponents.modifiedComponents.push(id);else if (componentStatus.staged) _this.failedComponents.stagedComponents.push(id);else _this.componentsToEject.push(id);
                            _context2.next = 10;
                            break;

                          case 7:
                            _context2.prev = 7;
                            _context2.t0 = _context2['catch'](0);

                            _this.throwEjectError(`eject operation failed getting the status of ${id.toString()}, no action has been done.
            please fix the issue to continue.`, _context2.t0);

                          case 10:
                          case 'end':
                            return _context2.stop();
                        }
                      }
                    }, _callee2, _this, [[0, 7]]);
                  }));

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

              case 14:
              case 'end':
                return _context3.stop();
            }
          }
        }, _callee3, this);
      }));

      function decideWhichComponentsToEject() {
        return _ref2.apply(this, arguments);
      }

      return decideWhichComponentsToEject;
    }()
  }, {
    key: 'removeComponentsFromPackageJsonAndNodeModules',
    value: function () {
      var _ref4 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee4() {
        var action;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                action = 'removing the existing components from package.json and node_modules';
                _context4.prev = 1;

                (_logger || _load_logger()).default.debug(`eject: ${action}`);
                _context4.next = 5;
                return (_packageJson || _load_packageJson()).removeComponentsFromWorkspacesAndDependencies(this.consumer, this.componentsToEject);

              case 5:
                _context4.next = 13;
                break;

              case 7:
                _context4.prev = 7;
                _context4.t0 = _context4['catch'](1);

                (_logger || _load_logger()).default.warn(`eject: failed ${action}, restoring package.json`);
                _context4.next = 12;
                return this.rollBack(action);

              case 12:
                this.throwEjectError(this._buildExceptionMessageWithRollbackData(action), _context4.t0);

              case 13:
              case 'end':
                return _context4.stop();
            }
          }
        }, _callee4, this, [[1, 7]]);
      }));

      function removeComponentsFromPackageJsonAndNodeModules() {
        return _ref4.apply(this, arguments);
      }

      return removeComponentsFromPackageJsonAndNodeModules;
    }()
  }, {
    key: 'addComponentsAsPackagesToPackageJson',
    value: function () {
      var _ref5 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee5() {
        var action;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                action = 'adding the components as packages into package.json';
                _context5.prev = 1;

                (_logger || _load_logger()).default.debug(`eject: ${action}`);
                _context5.next = 5;
                return (_packageJson || _load_packageJson()).addComponentsWithVersionToRoot(this.consumer, this.componentsToEject);

              case 5:
                _context5.next = 14;
                break;

              case 7:
                _context5.prev = 7;
                _context5.t0 = _context5['catch'](1);

                (_logger || _load_logger()).default.error(_context5.t0);
                (_logger || _load_logger()).default.warn(`eject: failed ${action}, restoring package.json`);
                _context5.next = 13;
                return this.rollBack(action);

              case 13:
                this.throwEjectError(this._buildExceptionMessageWithRollbackData(action), _context5.t0);

              case 14:
              case 'end':
                return _context5.stop();
            }
          }
        }, _callee5, this, [[1, 7]]);
      }));

      function addComponentsAsPackagesToPackageJson() {
        return _ref5.apply(this, arguments);
      }

      return addComponentsAsPackagesToPackageJson;
    }()
  }, {
    key: 'installPackagesUsingNPMClient',
    value: function () {
      var _ref6 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee6() {
        var action;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee6$(_context6) {
          while (1) {
            switch (_context6.prev = _context6.next) {
              case 0:
                action = 'installing the components using the NPM client';
                _context6.prev = 1;

                (_logger || _load_logger()).default.debug(`eject: ${action}`);
                _context6.next = 5;
                return (0, (_installPackages || _load_installPackages()).installPackages)(this.consumer, [], true, true);

              case 5:
                _context6.next = 12;
                break;

              case 7:
                _context6.prev = 7;
                _context6.t0 = _context6['catch'](1);
                _context6.next = 11;
                return this.rollBack(action);

              case 11:
                this.throwEjectError(this._buildExceptionMessageWithRollbackData(action), _context6.t0);

              case 12:
              case 'end':
                return _context6.stop();
            }
          }
        }, _callee6, this, [[1, 7]]);
      }));

      function installPackagesUsingNPMClient() {
        return _ref6.apply(this, arguments);
      }

      return installPackagesUsingNPMClient;
    }()
  }, {
    key: 'rollBack',
    value: function () {
      var _ref7 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee7(action) {
        return (_regenerator || _load_regenerator()).default.wrap(function _callee7$(_context7) {
          while (1) {
            switch (_context7.prev = _context7.next) {
              case 0:
                (_logger || _load_logger()).default.warn(`eject: failed ${action}, restoring package.json`);
                _context7.next = 3;
                return (_packageJson || _load_packageJson()).writePackageJsonFromObject(this.consumer, this.originalPackageJson);

              case 3:
              case 'end':
                return _context7.stop();
            }
          }
        }, _callee7, this);
      }));

      function rollBack(_x2) {
        return _ref7.apply(this, arguments);
      }

      return rollBack;
    }()
  }, {
    key: '_buildExceptionMessageWithRollbackData',
    value: function _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.`;
    }
  }, {
    key: 'removeComponents',
    value: function () {
      var _ref8 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee8() {
        return (_regenerator || _load_regenerator()).default.wrap(function _callee8$(_context8) {
          while (1) {
            switch (_context8.prev = _context8.next) {
              case 0:
                _context8.prev = 0;

                (_logger || _load_logger()).default.debug('eject: removing the components files from the filesystem');
                _context8.next = 4;
                return this.removeLocalComponents();

              case 4:
                _context8.next = 9;
                break;

              case 6:
                _context8.prev = 6;
                _context8.t0 = _context8['catch'](0);

                this.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.`, _context8.t0);

              case 9:
              case 'end':
                return _context8.stop();
            }
          }
        }, _callee8, this, [[0, 6]]);
      }));

      function removeComponents() {
        return _ref8.apply(this, arguments);
      }

      return removeComponents;
    }()

    /**
     * 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.
     */

  }, {
    key: 'removeLocalComponents',
    value: function () {
      var _ref9 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee9() {
        return (_regenerator || _load_regenerator()).default.wrap(function _callee9$(_context9) {
          while (1) {
            switch (_context9.prev = _context9.next) {
              case 0:
                _context9.next = 2;
                return this.consumer.removeComponentFromFs(this.componentsToEject, true);

              case 2:
                _context9.next = 4;
                return this.consumer.cleanBitMapAndBitJson(this.componentsToEject, new (_bitId || _load_bitId()).BitIds());

              case 4:
              case 'end':
                return _context9.stop();
            }
          }
        }, _callee9, this);
      }));

      function removeLocalComponents() {
        return _ref9.apply(this, arguments);
      }

      return removeLocalComponents;
    }()
  }, {
    key: 'throwEjectError',
    value: function 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?
      var originalErrorMessage = (0, (_defaultErrorHandler || _load_defaultErrorHandler()).default)(originalError) || originalError.msg || originalError;
      (_logger || _load_logger()).default.error('eject has stopped due to an error', originalErrorMessage);
      throw new Error(`${message}

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

  return EjectComponents;
}();

exports.default = EjectComponents;