"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 _objectSpread2() {
  const data = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));

  _objectSpread2 = function () {
    return data;
  };

  return data;
}

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

  _bluebird = function () {
    return data;
  };

  return data;
}

function _defineProperty2() {
  const data = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

  _defineProperty2 = function () {
    return data;
  };

  return data;
}

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

  _path = 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 _ajv() {
  const data = _interopRequireDefault(require("ajv"));

  _ajv = function () {
    return data;
  };

  return data;
}

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

  _semver = function () {
    return data;
  };

  return data;
}

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

  _logger = function () {
    return data;
  };

  return data;
}

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

  _scope = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

function _extensionNameNotValid() {
  const data = _interopRequireDefault(require("./exceptions/extension-name-not-valid"));

  _extensionNameNotValid = function () {
    return data;
  };

  return data;
}

function _extensionGetDynamicConfigError() {
  const data = _interopRequireDefault(require("./exceptions/extension-get-dynamic-config-error"));

  _extensionGetDynamicConfigError = function () {
    return data;
  };

  return data;
}

function _analytics() {
  const data = require("../analytics/analytics");

  _analytics = function () {
    return data;
  };

  return data;
}

function _extensionLoadError() {
  const data = _interopRequireDefault(require("./exceptions/extension-load-error"));

  _extensionLoadError = function () {
    return data;
  };

  return data;
}

function _environment() {
  const data = _interopRequireDefault(require("../environment"));

  _environment = function () {
    return data;
  };

  return data;
}

function _extensionSchemaError() {
  const data = _interopRequireDefault(require("./exceptions/extension-schema-error"));

  _extensionSchemaError = function () {
    return data;
  };

  return data;
}

const ajv = new (_ajv().default)();
const CORE_EXTENSIONS_PATH = './core-extensions';

// export type BaseExtensionProps = {
//   ...InstanceSpecificProps,
//   ...StaticProps
// };
// type staticProps = $Diff<BaseExtensionProps, instanceSpecificProps>
class BaseExtension {
  // Store the required plugin
  // Store the required plugin
  constructor(extensionProps) {
    (0, _defineProperty2().default)(this, "api", _getConcreteBaseAPI({
      name: this.name
    }));
    this.name = extensionProps.name;
    this.rawConfig = extensionProps.rawConfig;
    this.schema = extensionProps.schema;
    this.options = extensionProps.options;
    this.dynamicConfig = extensionProps.dynamicConfig || extensionProps.rawConfig;
    this.context = extensionProps.context;
    this.script = extensionProps.script;
    this.disabled = extensionProps.disabled;
    this.filePath = extensionProps.filePath;
    this.rootDir = extensionProps.rootDir || '';
    this.loaded = extensionProps.loaded;
    this.api = extensionProps.api;
  }

  get writeConfigFilesOnAction() {
    if (!this.initOptions) {
      return false;
    }

    return this.initOptions.writeConfigFilesOnAction;
  }

  get initOptions() {
    return this._initOptions;
  }

  set initOptions(opts) {
    const defaultInitOpts = {
      writeConfigFilesOnAction: false
    };

    if (!opts) {
      this._initOptions = defaultInitOpts;
      return;
    }

    const res = {};

    if (opts.write) {
      res.writeConfigFilesOnAction = true;
    }

    this._initOptions = res;
  }
  /**
   * Run the extension's init function
   */


  init(throws = false) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      _analytics().Analytics.addBreadCrumb('base-extension', 'initialize extension');

      try {
        let initOptions = {};

        if (_this.script && _this.script.init && typeof _this.script.init === 'function') {
          initOptions = _this.script.init({
            rawConfig: _this.rawConfig,
            dynamicConfig: _this.dynamicConfig,
            api: _this.api
          });
        } // wrap in promise, in case a script has async init


        _this.initOptions = yield Promise.resolve(initOptions);
        _this.initialized = true; // Make sure to not kill the process if an extension didn't load correctly
      } catch (err) {
        _logger().default.error(`initialized extension ${_this.name} failed`);

        _logger().default.error(err);

        if (throws) {
          throw new (_extensionLoadError().default)(err, _this.name);
        }

        _this.initialized = false;
        return false;
      }

      return true;
    })();
  }

  extendAPI(baseApi, api) {
    this.api = _ramda().default.merge(baseApi, api);
  }

  toString() {
    return JSON.stringify(this, null, 2);
  }

  toBitJsonObject() {
    return {
      [this.name]: {
        rawConfig: this.rawConfig,
        options: this.options
      }
    };
  }

  toModelObject() {
    return {
      name: this.name,
      config: this.dynamicConfig
    };
  }

  toObject() {
    return this.toModelObject();
  }
  /**
   * Reload the extension, this mainly contain the process of going to the extension file requiring it and get the dynamic config
   * It mostly used for env extension when sometime on the first load the env didn't installed yet (only during build / test) phase
   */


  reload(scopePath, {
    throws
  }) {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      _analytics().Analytics.addBreadCrumb('base-extension', 'reload extension');

      if (!_this2.filePath && !_this2.options.core) {
        const {
          resolvedPath,
          componentPath
        } = _getExtensionPath(_this2.name, scopePath, _this2.options.core);

        _this2.filePath = resolvedPath;
        _this2.rootDir = componentPath;
      }

      _this2.name = _addVersionToNameFromPathIfMissing(_this2.name, _this2.rootDir, _this2.options);
      const baseProps = yield BaseExtension.loadFromFile({
        name: _this2.name,
        filePath: _this2.filePath,
        rootDir: _this2.rootDir,
        rawConfig: _this2.rawConfig,
        options: _this2.options,
        throws
      });

      if (baseProps.loaded) {
        _this2.loaded = baseProps.loaded;
        _this2.script = baseProps.script;
        _this2.dynamicConfig = baseProps.dynamicConfig;
        yield _this2.init();
      }
    })();
  }

  setExtensionPathInScope(scopePath) {
    const {
      resolvedPath,
      componentPath
    } = _getExtensionPath(this.name, scopePath, this.options.core);

    this.filePath = resolvedPath;
    this.rootDir = componentPath;
  }

  static transformStringToModelObject(name) {
    return {
      name,
      config: {}
    };
  }
  /**
   * Load extension by name
   * The extension will be from scope by default or from file
   * if there is file(path) in the options
   * The file path is relative to the bit.json of the project or absolute
   * @param {string} name - name of the extension
   * @param {Object} rawConfig - raw config for the extension
   * @param {Object} options - extension options such as - disabled, file, core
   * @param {string} consumerPath - path to the consumer folder (to load the file relatively)
   * @param {string} scopePath - scope which stores the extension code
   */


  static load({
    name,
    rawConfig = {},
    // $FlowFixMe
    options = {},
    consumerPath,
    scopePath,
    throws = false,
    context
  }) {
    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debug(`base-extension loading ${name}`);

      const concreteBaseAPI = _getConcreteBaseAPI({
        name
      });

      if (options.file) {
        let absPath = options.file;
        const file = options.file || '';

        if (!_path().default.isAbsolute(options.file) && consumerPath) {
          absPath = _path().default.resolve(consumerPath, file);
        }

        const staticExtensionProps = yield BaseExtension.loadFromFile({
          name,
          filePath: absPath,
          rawConfig,
          options,
          throws
        });
        const extensionProps = (0, _objectSpread2().default)({
          api: concreteBaseAPI,
          context
        }, staticExtensionProps);
        extensionProps.api = concreteBaseAPI;
        return extensionProps;
      }

      let staticExtensionProps = {
        name,
        rawConfig,
        dynamicConfig: rawConfig,
        options,
        disabled: false,
        loaded: false,
        filePath: ''
      }; // Require extension from scope

      if (scopePath) {
        const {
          resolvedPath,
          componentPath
        } = _getExtensionPath(name, scopePath, options.core);

        const nameWithVersion = _addVersionToNameFromPathIfMissing(name, componentPath, options);

        staticExtensionProps = yield BaseExtension.loadFromFile({
          name: nameWithVersion,
          filePath: resolvedPath,
          rootDir: componentPath,
          rawConfig,
          options,
          throws
        });
      }

      const extensionProps = (0, _objectSpread2().default)({
        api: concreteBaseAPI,
        context
      }, staticExtensionProps);
      return extensionProps;
    })();
  }

  static loadFromModelObject(modelObject) {
    let staticExtensionProps;

    if (typeof modelObject === 'string') {
      staticExtensionProps = {
        name: modelObject,
        rawConfig: {},
        dynamicConfig: {},
        options: {},
        disabled: false,
        loaded: false,
        filePath: ''
      };
    } else {
      staticExtensionProps = {
        name: modelObject.name,
        rawConfig: modelObject.config,
        dynamicConfig: modelObject.config,
        options: {},
        disabled: false,
        loaded: false,
        filePath: ''
      };
    }

    const concreteBaseAPI = _getConcreteBaseAPI({
      name: staticExtensionProps.name
    });

    const extensionProps = (0, _objectSpread2().default)({
      api: concreteBaseAPI
    }, staticExtensionProps);
    return extensionProps;
  }

  static loadFromFile({
    name,
    filePath,
    rootDir,
    rawConfig = {},
    // $FlowFixMe
    options = {},
    throws = false
  }) {
    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debug(`base-extension, loading extension ${name} from ${filePath}`);

      const extensionProps = {
        name,
        rawConfig,
        dynamicConfig: rawConfig,
        options,
        disabled: false,
        loaded: false,
        filePath: '',
        rootDir: ''
      }; // Skip disabled extensions

      if (options.disabled) {
        extensionProps.disabled = true;

        _logger().default.info(`skip extension ${extensionProps.name} because it is disabled`);

        extensionProps.loaded = false;
        return extensionProps;
      }

      extensionProps.filePath = filePath;
      extensionProps.rootDir = rootDir;
      const isFileExist = yield _fsExtra().default.exists(filePath);

      if (!isFileExist) {
        // Do not throw an error if the file not exist since we will install it later
        // unless you specify the options.file which means you want a specific file which won't be installed automatically later
        if (throws && options.file) {
          const err = new Error(`the file ${filePath} not found`);
          throw new (_extensionLoadError().default)(err, extensionProps.name);
        }

        extensionProps.loaded = false;
        return extensionProps;
      }

      if (rootDir && !_environment().default.isEnvironmentInstalled(rootDir)) {
        extensionProps.loaded = false;
        return extensionProps;
      }

      try {
        // $FlowFixMe
        const script = require(filePath); // eslint-disable-line


        extensionProps.script = script.default ? script.default : script;

        if (extensionProps.script.getSchema && typeof extensionProps.script.getSchema === 'function') {
          // the function may or may not be a promise
          extensionProps.schema = yield Promise.resolve(extensionProps.script.getSchema());
          const valid = ajv.validate(extensionProps.schema, rawConfig);

          if (!valid) {
            throw new (_extensionSchemaError().default)(name, ajv.errorsText());
          }
        } // Make sure to not kill the process if an extension didn't load correctly

      } catch (err) {
        if (err.code === 'MODULE_NOT_FOUND') {
          const msg = `loading extension ${extensionProps.name} failed, the file ${extensionProps.filePath} not found`;

          _logger().default.warn(msg); // console.error(msg); // eslint-disable-line no-console

        }

        _logger().default.error(`loading extension ${extensionProps.name} failed`);

        _logger().default.error(err);

        extensionProps.loaded = false;

        if (throws) {
          let printStack = true;

          if (err instanceof _extensionSchemaError().default) {
            printStack = false;
          }

          throw new (_extensionLoadError().default)(err, extensionProps.name, printStack);
        }

        return extensionProps;
      }

      extensionProps.loaded = true;
      return extensionProps;
    })();
  }

  static loadDynamicConfig(extensionProps) {
    _logger().default.debug('base-extension - loadDynamicConfig');

    const getDynamicConfig = _ramda().default.path(['script', 'getDynamicConfig'], extensionProps);

    if (getDynamicConfig && typeof getDynamicConfig === 'function') {
      try {
        const dynamicConfig = getDynamicConfig({
          rawConfig: extensionProps.rawConfig
        });
        return dynamicConfig;
      } catch (err) {
        throw new (_extensionGetDynamicConfigError().default)(err, extensionProps.name);
      }
    }

    return undefined;
  }

}

exports.default = BaseExtension;

const _getExtensionPath = (name, scopePath, isCore = false) => {
  if (isCore) {
    return _getCoreExtensionPath(name);
  }

  if (!scopePath) {
    throw new Error('base-extension._getExtensionPath expects to get scopePath');
  }

  return _getRegularExtensionPath(name, scopePath);
};

const _getCoreExtensionPath = name => {
  const componentPath = _path().default.join(__dirname, CORE_EXTENSIONS_PATH, name);

  return {
    resolvedPath: componentPath,
    componentPath
  };
};

const _getRegularExtensionPath = (name, scopePath) => {
  let bitId;

  try {
    bitId = _bitId().BitId.parse(name, true); // todo: make sure it always has a scope
  } catch (err) {
    throw new (_extensionNameNotValid().default)(name);
  }

  if (!bitId || !bitId.scope) throw new (_extensionNameNotValid().default)(name);

  const internalComponentsPath = _scope().Scope.getComponentsRelativePath();

  const internalComponentPath = _scope().Scope.getComponentRelativePath(bitId, scopePath);

  const componentPath = _path().default.join(scopePath, internalComponentsPath, internalComponentPath);

  try {
    // This might throw an error in case of imported component when the env
    // isn't installed yet
    // It will be handled in higher functions
    const resolved = require.resolve(componentPath);

    return {
      resolvedPath: typeof resolved === 'string' ? resolved : componentPath,
      componentPath
    };
  } catch (e) {
    return {
      resolvedPath: componentPath,
      componentPath
    };
  }
};

const _getExtensionVersionFromComponentPath = componentPath => {
  const parsed = _path().default.parse(componentPath);

  const version = parsed.base;

  if (!_semver().default.valid(version)) {
    return undefined;
  }

  return version;
};

const _addVersionToNameFromPathIfMissing = (name, componentPath, options) => {
  if (options && options.core) return name; // if it's a core extension, it's not a bit-id.

  let bitId;

  try {
    bitId = _bitId().BitId.parse(name, true); // @todo: make sure it always has a scope name
  } catch (err) {
    throw new (_extensionNameNotValid().default)(name);
  }

  if (bitId.getVersion().latest) {
    const version = _getExtensionVersionFromComponentPath(componentPath);

    return bitId.changeVersion(version).toString();
  }

  return name;
};

const baseApi = {
  /**
   * API to get logger
   */
  getLogger: name => () => (0, _logger().createExtensionLogger)(name)
};
/**
 * Function which get actual params and return a concrete base api
 */

const _getConcreteBaseAPI = ({
  name
}) => {
  const concreteBaseAPI = _ramda().default.clone(baseApi);

  concreteBaseAPI.getLogger = baseApi.getLogger(name);
  return concreteBaseAPI;
};