"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 _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));

  _fsExtra = function () {
    return data;
  };

  return data;
}

function _semver() {
  const data = _interopRequireDefault(require("semver"));

  _semver = function () {
    return data;
  };

  return data;
}

function RA() {
  const data = _interopRequireWildcard(require("ramda-adjunct"));

  RA = function () {
    return data;
  };

  return data;
}

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

  _path = function () {
    return data;
  };

  return data;
}

function _componentMap() {
  const data = _interopRequireDefault(require("../bit-map/component-map"));

  _componentMap = function () {
    return data;
  };

  return data;
}

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

  _logger = function () {
    return data;
  };

  return data;
}

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

  _generalError = function () {
    return data;
  };

  return data;
}

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

  _path2 = function () {
    return data;
  };

  return data;
}

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

  _constants = function () {
    return data;
  };

  return data;
}

function _componentNodeModulesPath() {
  const data = _interopRequireDefault(require("../../utils/bit/component-node-modules-path"));

  _componentNodeModulesPath = function () {
    return data;
  };

  return data;
}

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

  _packageJsonUtils = function () {
    return data;
  };

  return data;
}

function _dataToPersist() {
  const data = _interopRequireDefault(require("../component/sources/data-to-persist"));

  _dataToPersist = function () {
    return data;
  };

  return data;
}

function _removePath() {
  const data = _interopRequireDefault(require("../component/sources/remove-path"));

  _removePath = function () {
    return data;
  };

  return data;
}

function _bitMap() {
  const data = _interopRequireDefault(require("../bit-map/bit-map"));

  _bitMap = function () {
    return data;
  };

  return data;
}

function _configDir() {
  const data = _interopRequireDefault(require("../bit-map/config-dir"));

  _configDir = function () {
    return data;
  };

  return data;
}

function _envExtension() {
  const data = _interopRequireDefault(require("../../extensions/env-extension"));

  _envExtension = function () {
    return data;
  };

  return data;
}

function _componentConfig() {
  const data = _interopRequireDefault(require("../config/component-config"));

  _componentConfig = function () {
    return data;
  };

  return data;
}

function _ejectConf() {
  const data = require("./eject-conf");

  _ejectConf = function () {
    return data;
  };

  return data;
}

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

  _packageJsonFile = function () {
    return data;
  };

  return data;
}

class ComponentWriter {
  constructor({
    component,
    writeToPath,
    writeConfig = false,
    configDir,
    writePackageJson = true,
    override = true,
    isolated = false,
    origin,
    consumer,
    bitMap,
    writeBitDependencies = false,
    deleteBitDirContent,
    existingComponentMap,
    excludeRegistryPrefix = false
  }) {
    this.component = component;
    this.writeToPath = writeToPath;
    this.writeConfig = writeConfig;
    this.configDir = configDir;
    this.writePackageJson = writePackageJson;
    this.override = override;
    this.isolated = isolated;
    this.origin = origin;
    this.consumer = consumer;
    this.bitMap = bitMap;
    this.writeBitDependencies = writeBitDependencies;
    this.deleteBitDirContent = deleteBitDirContent;
    this.existingComponentMap = existingComponentMap;
    this.excludeRegistryPrefix = excludeRegistryPrefix;
  }

  static getInstance(componentWriterProps) {
    return new ComponentWriter(componentWriterProps);
  }
  /**
   * write the component to the filesystem and update .bitmap with the details.
   *
   * bitMap gets updated before writing the files to the filesystem, because as part of writing the
   * package-json file, the componentMap is needed to be stored with the updated version.
   *
   * when a component is not new, write the files according to the paths in .bitmap.
   */


  write() {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      if (!_this.consumer) throw new Error('ComponentWriter.write expect to have a consumer');
      yield _this.populateComponentsFilesToWrite(); // $FlowFixMe consumer is set

      _this.component.dataToPersist.addBasePath(_this.consumer.getPath());

      yield _this.component.dataToPersist.persistAllToFS();
      return _this.component;
    })();
  }

  populateComponentsFilesToWrite() {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (!_this2.component.files || !_this2.component.files.length) {
        throw new (_generalError().default)(`Component ${_this2.component.id.toString()} is invalid as it has no files`);
      }

      _this2.component.dataToPersist = new (_dataToPersist().default)();

      _this2._updateFilesBasePaths();

      _this2.componentMap = _this2.existingComponentMap || _this2.addComponentToBitMap(_this2.writeToPath);
      _this2.component.componentMap = _this2.componentMap;

      _this2._copyFilesIntoDistsWhenDistsOutsideComponentDir();

      _this2._determineWhetherToDeleteComponentDirContent();

      yield _this2._handlePreviouslyNestedCurrentlyImportedCase();

      _this2._determineWhetherToWriteConfig();

      _this2._updateComponentRootPathAccordingToBitMap();

      _this2._updateBitMapIfNeeded();

      yield _this2._updateConsumerConfigIfNeeded();

      _this2._determineWhetherToWritePackageJson();

      yield _this2.populateFilesToWriteToComponentDir();
      return _this2.component;
    })();
  }

  populateFilesToWriteToComponentDir() {
    var _this3 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (_this3.deleteBitDirContent) {
        _this3.component.dataToPersist.removePath(new (_removePath().default)(_this3.writeToPath));
      }

      _this3.component.files.forEach(file => file.override = _this3.override);

      _this3.component.files.map(file => _this3.component.dataToPersist.addFile(file));

      const dists = yield _this3.component.dists.getDistsToWrite(_this3.component, _this3.bitMap, _this3.consumer, false);
      if (dists) _this3.component.dataToPersist.merge(dists);

      if (_this3.writeConfig && _this3.consumer) {
        const resolvedConfigDir = _this3.configDir || _this3.consumer.dirStructure.ejectedEnvsDirStructure;
        const configToWrite = yield _this3.component.getConfigToWrite(_this3.consumer, _this3.bitMap, resolvedConfigDir);

        _this3.component.dataToPersist.merge(configToWrite.dataToPersist);
      } // make sure the project's package.json is not overridden by Bit
      // If a consumer is of isolated env it's ok to override the root package.json (used by the env installation
      // of compilers / testers / extensions)


      if (_this3.writePackageJson && (_this3.isolated || _this3.consumer && _this3.consumer.isolated || _this3.writeToPath !== '.')) {
        const {
          packageJson,
          distPackageJson
        } = (0, _packageJsonUtils().preparePackageJsonToWrite)(_this3.consumer, _this3.component, _this3.writeToPath, _this3.override, _this3.writeBitDependencies, _this3.excludeRegistryPrefix);

        const componentConfig = _componentConfig().default.fromComponent(_this3.component); // @todo: temporarily this is running only when there is no version (or version is "latest")
        // so then package.json always has a valid version. we'll need to figure out when the version
        // needs to be incremented and when it should not.


        if ((!_this3.consumer || _this3.consumer.isolated) && !_this3.component.id.hasVersion()) {
          // this only needs to be done in an isolated
          // or consumerless (dependency in an isolated) environment
          packageJson.addOrUpdateProperty('version', _this3._getNextPatchVersion());
        }

        componentConfig.compiler = _this3.component.compiler ? _this3.component.compiler.toBitJsonObject('.') : {};
        componentConfig.tester = _this3.component.tester ? _this3.component.tester.toBitJsonObject('.') : {};
        packageJson.addOrUpdateProperty('bit', componentConfig.toPlainObject());

        _this3._mergeChangedPackageJsonProps(packageJson);

        yield _this3._populateEnvFilesIfNeeded();

        _this3.component.dataToPersist.addFile(packageJson.toVinylFile());

        if (distPackageJson) _this3.component.dataToPersist.addFile(distPackageJson.toVinylFile());
        _this3.component.packageJsonFile = packageJson;
      }

      if (_this3.component.license && _this3.component.license.contents) {
        _this3.component.license.updatePaths({
          newBase: _this3.writeToPath
        }); // $FlowFixMe this.component.license is set


        _this3.component.license.override = _this3.override; // $FlowFixMe this.component.license is set

        _this3.component.dataToPersist.addFile(_this3.component.license);
      }
    })();
  }

  addComponentToBitMap(rootDir) {
    const filesForBitMap = this.component.files.map(file => {
      return {
        name: file.basename,
        relativePath: (0, _path2().pathNormalizeToLinux)(file.relative),
        test: file.test
      };
    });

    const getConfigDir = () => {
      if (this.configDir) return this.configDir;
      if (this.componentMap) return this.componentMap.configDir;
      return undefined;
    };

    return this.bitMap.addComponent({
      componentId: this.component.id,
      files: filesForBitMap,
      mainFile: (0, _path2().pathNormalizeToLinux)(this.component.mainFile),
      // $FlowFixMe
      rootDir,
      // $FlowFixMe
      configDir: getConfigDir(),
      origin: this.origin,
      originallySharedDir: this.component.originallySharedDir,
      wrapDir: this.component.wrapDir
    });
  }

  _populateEnvFilesIfNeeded() {
    var _this4 = this;

    return (0, _bluebird().coroutine)(function* () {
      const areThereEnvFiles = _this4.component.compiler && !RA().isNilOrEmpty(_this4.component.compiler.files) || _this4.component.tester && !RA().isNilOrEmpty(_this4.component.tester.files);

      if (!areThereEnvFiles) {
        return;
      }

      if (_this4.component.compiler) {
        yield (0, _ejectConf().populateEnvFilesToWrite)({
          configDir: _this4.writeToPath,
          env: _this4.component.compiler,
          consumer: _this4.consumer,
          component: _this4.component,
          deleteOldFiles: false,
          verbose: false
        }); // $FlowFixMe

        _this4.component.dataToPersist.merge(_this4.component.compiler.dataToPersist);
      }

      if (_this4.component.tester) {
        yield (0, _ejectConf().populateEnvFilesToWrite)({
          configDir: _this4.writeToPath,
          env: _this4.component.tester,
          consumer: _this4.consumer,
          component: _this4.component,
          deleteOldFiles: false,
          verbose: false
        }); // $FlowFixMe

        _this4.component.dataToPersist.merge(_this4.component.tester.dataToPersist);
      }

      if (!_this4.writeConfig && !_this4.configDir && _this4.component.componentMap) {
        _this4.configDir = _constants().DEFAULT_EJECTED_ENVS_DIR_PATH;

        _this4.component.componentMap.setConfigDir(_this4.configDir);
      }
    })();
  }

  _mergeChangedPackageJsonProps(packageJson) {
    if (!this.component.packageJsonChangedProps) return;

    const valuesToMerge = this._replaceDistPathTemplateWithCalculatedDistPath(packageJson);

    packageJson.mergePackageJsonObject(valuesToMerge);
  }
  /**
   * see https://github.com/teambit/bit/issues/1808 for more info why it's needed
   */


  _replaceDistPathTemplateWithCalculatedDistPath(packageJson) {
    // $FlowFixMe
    const packageJsonChangedProps = this.component.packageJsonChangedProps;

    const isReplaceNeeded = _ramda().default.values(packageJsonChangedProps).some(val => val.includes(_constants().COMPONENT_DIST_PATH_TEMPLATE));

    if (!isReplaceNeeded) {
      return packageJsonChangedProps;
    }

    if (!this.component.dists || !this.component.dists.distsRootDir) {
      throw new Error(`package.json has a dynamic value ${_constants().COMPONENT_DIST_PATH_TEMPLATE}, however, the dist root is not set`);
    }

    const distRelativeToPackageJson = (0, _path2().getPathRelativeRegardlessCWD)(_path().default.dirname(packageJson.filePath), // $FlowFixMe
    this.component.dists.distsRootDir);
    return Object.keys(packageJsonChangedProps).reduce((acc, key) => {
      const val = packageJsonChangedProps[key].replace(_constants().COMPONENT_DIST_PATH_TEMPLATE, distRelativeToPackageJson);
      acc[key] = val;
      return acc;
    }, {});
  }

  _copyFilesIntoDistsWhenDistsOutsideComponentDir() {
    if (!this.consumer) return; // not relevant when consumer is not available

    if (!this.consumer.shouldDistsBeInsideTheComponent() && this.component.dists.isEmpty()) {
      // since the dists are set to be outside the components dir, the source files must be saved there
      // otherwise, other components in dists won't be able to link to this component
      this.component.copyFilesIntoDists();
    }
  }

  _updateComponentRootPathAccordingToBitMap() {
    this.writeToPath = this.componentMap.getRootDir();
    this.component.writtenPath = this.writeToPath;

    this._updateFilesBasePaths();
  }
  /**
   * when there is componentMap, this component (with this version or other version) is already part of the project.
   * There are several options as to what was the origin before and what is the origin now and according to this,
   * we update/remove/don't-touch the record in bit.map.
   * 1) current origin is AUTHORED - If the version is the same as before, don't update bit.map. Otherwise, update.
   * 2) current origin is IMPORTED - If the version is the same as before, don't update bit.map. Otherwise, update.
   * one exception is where the origin was NESTED before, in this case, remove the current record and add a new one.
   * this case has been already handled before by this._handlePreviouslyNestedCurrentlyImportedCase();
   * 3) current origin is NESTED - the version can't be the same as before (otherwise it would be ignored before and
   * never reach this function, see @write-components.writeToComponentsDir). Therefore, always add to bit.map.
   */


  _updateBitMapIfNeeded() {
    if (this.isolated) return;
    const componentMapExistWithSameVersion = this.bitMap.isExistWithSameVersion(this.component.id);
    const updateBitMap = !componentMapExistWithSameVersion || this.componentMap.originallySharedDir !== this.component.originallySharedDir;

    if (updateBitMap) {
      if (componentMapExistWithSameVersion) {
        // originallySharedDir has been changed. it affects also the relativePath of the files
        // so it's better to just remove the old record and add a new one
        this.bitMap.removeComponent(this.component.id);
      }

      this.component.componentMap = this.addComponentToBitMap(this.componentMap.rootDir);
    }
  }

  _updateConsumerConfigIfNeeded() {
    var _this5 = this;

    return (0, _bluebird().coroutine)(function* () {
      // for authored components there is no bit.json/package.json component specific
      // so if the overrides or envs were changed, it should be written to the consumer-config
      const areEnvsChanged =
      /*#__PURE__*/
      function () {
        var _ref = (0, _bluebird().coroutine)(function* () {
          const context = {
            componentDir: _this5.componentMap.getRootDir()
          };
          const compilerFromConsumer = _this5.consumer ? yield _this5.consumer.getEnv(_constants().COMPILER_ENV_TYPE, context) : null;
          const testerFromConsumer = _this5.consumer ? yield _this5.consumer.getEnv(_constants().TESTER_ENV_TYPE, context) : null;
          const compilerFromComponent = _this5.component.compiler ? _this5.component.compiler.toModelObject() : null;
          const testerFromComponent = _this5.component.tester ? _this5.component.tester.toModelObject() : null;
          return _envExtension().default.areEnvsDifferent(compilerFromConsumer ? compilerFromConsumer.toModelObject() : null, compilerFromComponent) || _envExtension().default.areEnvsDifferent(testerFromConsumer ? testerFromConsumer.toModelObject() : null, testerFromComponent);
        });

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

      if (_this5.componentMap.origin === _constants().COMPONENT_ORIGINS.AUTHORED && _this5.consumer) {
        _this5.consumer.config.overrides.updateOverridesIfChanged(_this5.component, (yield areEnvsChanged()));
      }
    })();
  }

  _determineWhetherToWriteConfig() {
    if (this.componentMap.origin === _constants().COMPONENT_ORIGINS.AUTHORED) {
      this.writeConfig = false;
    }
  }
  /**
   * don't write the package.json for an authored component, because its dependencies are managed
   * by the root package.json
   */


  _determineWhetherToWritePackageJson() {
    this.writePackageJson = this.writePackageJson && this.origin !== _constants().COMPONENT_ORIGINS.AUTHORED;
  }
  /**
   * when a user imports a component that was a dependency before, write the component directly
   * into the components directory for an easy access/change. Then, remove the current record from
   * bit.map and add an updated one.
   */


  _handlePreviouslyNestedCurrentlyImportedCase() {
    var _this6 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (!_this6.consumer) return;

      if (_this6.origin === _constants().COMPONENT_ORIGINS.IMPORTED && _this6.componentMap.origin === _constants().COMPONENT_ORIGINS.NESTED) {
        yield _this6._cleanOldNestedComponent();
        _this6.componentMap = _this6.addComponentToBitMap(_this6.writeToPath);
      }
    })();
  }
  /**
   * For IMPORTED component we have to delete the content of the directory before importing.
   * Otherwise, when the author adds new files outside of the previous originallySharedDir and this user imports them
   * the environment will contain both copies, the old one with the old originallySharedDir and the new one.
   * If a user made changes to the imported component, it will show a warning and stop the process.
   */


  _determineWhetherToDeleteComponentDirContent() {
    if (typeof this.deleteBitDirContent === 'undefined') {
      this.deleteBitDirContent = this.origin === _constants().COMPONENT_ORIGINS.IMPORTED;
    }
  }

  _updateFilesBasePaths() {
    const newBase = this.writeToPath || '.';
    this.component.files.forEach(file => file.updatePaths({
      newBase
    }));

    if (!this.component.dists.isEmpty()) {
      this.component.dists.get().forEach(dist => dist.updatePaths({
        newBase
      }));
    }
  }

  _cleanOldNestedComponent() {
    var _this7 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (!_this7.consumer) throw new Error('ComponentWriter._cleanOldNestedComponent expect to have a consumer'); // $FlowFixMe this function gets called when it was previously NESTED, so the rootDir is set

      const oldLocation = _path().default.join(_this7.consumer.getPath(), _this7.componentMap.rootDir);

      _logger().default.debugAndAddBreadCrumb('component-writer._cleanOldNestedComponent', 'deleting the old directory of a component at {oldLocation}', {
        oldLocation
      });

      yield _fsExtra().default.remove(oldLocation);
      yield _this7._removeNodeModulesLinksFromDependents();

      _this7.bitMap.removeComponent(_this7.component.id);
    })();
  }

  _removeNodeModulesLinksFromDependents() {
    var _this8 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (!_this8.consumer) {
        throw new Error('ComponentWriter._removeNodeModulesLinksFromDependents expect to have a consumer');
      }

      const directDependentIds = yield _this8.consumer.getAuthoredAndImportedDependentsIdsOf([_this8.component]);
      yield Promise.all(directDependentIds.map(dependentId => {
        const dependentComponentMap = _this8.consumer ? _this8.consumer.bitMap.getComponent(dependentId) : null;
        const relativeLinkPath = _this8.consumer ? (0, _componentNodeModulesPath().default)(_this8.consumer.config.bindingPrefix, _this8.component.id) : null;
        const nodeModulesLinkAbs = _this8.consumer && dependentComponentMap && relativeLinkPath ? _this8.consumer.toAbsolutePath(_path().default.join(dependentComponentMap.getRootDir(), relativeLinkPath)) : null;

        if (nodeModulesLinkAbs) {
          _logger().default.debug(`deleting an obsolete link to node_modules at ${nodeModulesLinkAbs}`);
        }

        return nodeModulesLinkAbs ? _fsExtra().default.remove(nodeModulesLinkAbs) : Promise.resolve();
      }));
    })();
  }

  _getConfigDir() {
    if (this.configDir) return this.configDir;
    if (this.consumer) return this.consumer.dirStructure.ejectedEnvsDirStructure;
    return new (_configDir().default)(_constants().DEFAULT_EJECTED_ENVS_DIR_PATH);
  }

  _getNextPatchVersion() {
    return _semver().default.inc(this.component.version, 'prerelease') || '0.0.1-0';
  }

}

exports.default = ComponentWriter;