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

  _defineProperty2 = function () {
    return data;
  };

  return data;
}

function _ramda() {
  const data = _interopRequireDefault(require("ramda"));

  _ramda = function () {
    return data;
  };

  return data;
}

function _util() {
  const data = require("util");

  _util = function () {
    return data;
  };

  return data;
}

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

  _constants = function () {
    return data;
  };

  return data;
}

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

  _logger = function () {
    return data;
  };

  return data;
}

function errors() {
  const data = _interopRequireWildcard(require("./exceptions"));

  errors = function () {
    return data;
  };

  return data;
}

/*
  * Setting up block level variable to store class state
  * set's to null by default.
*/
let instance = null;
/**
 * A class which manage all the hooks
 * This is a singelton class which expose getInstance method
 * This class used for register new hooks, actions for existing hooks and trigger hooks
 */

class HooksManager {
  constructor() {
    (0, _defineProperty2().default)(this, "hooks", new Map());

    if (!instance) {
      instance = this;
    }

    return instance;
  }
  /**
   * Initialize the default hooks
   */


  static init() {
    const self = new HooksManager();

    _constants().HOOKS_NAMES.forEach(hookName => self.hooks.set(hookName, []));
  }
  /**
   * Get the instance of the HooksManager
   * @return {HooksManager} instance of the HooksManager
   *
   */


  static getInstance() {
    return instance;
  }
  /**
   * register new hook name
   * @param {string} hookName
   * @param {boolean} throwIfExist - whether to throw an error if the hook name already exists
   * @return {boolean} whether the hook has been registerd
   */


  registerNewHook(hookName, context = {}, throwIfExist = false) {
    if (this.hooks.has(hookName)) {
      const contextMsg = context.extension ? `from ${context.extension}` : '';

      _logger().default.warn(`trying to register an already existing hook ${hookName} ${contextMsg}`);

      if (throwIfExist) {
        throw new (errors().HookAlreadyExists)(hookName);
      }

      return false;
    }

    this.hooks.set(hookName, []);
    return true;
  }
  /**
   * Register action to an existing hook
   * @param {string} hookName - hook to register action to
   * @param {HookAction} hookAction - The action to register to the hook
   * @param {boolean} throwIfNotExist - whether to throw an exception in case the hook doesn't exists
   * @return {boolean} whether the action has been registerd successfully
   */


  registerActionToHook(hookName, hookAction, context = {}, throwIfNotExist = false) {
    if (!this.hooks.has(hookName)) {
      const contextMsg = context.extension ? `from ${context.extension}` : '';

      _logger().default.warn(`trying to register to a non existing hook ${hookName} ${contextMsg}`);

      if (throwIfNotExist) {
        throw new (errors().HookNotExists)(hookName);
      }

      return false;
    }

    this.hooks.get(hookName).push(hookAction);
    return true;
  }
  /**
   * Trigger a hook - run all the actions registerd to this hook
   * The actions will be run in parallel and the errors will be aggregated
   * @param {string} hookName - The hook name to trigger
   * @return {HookFailures} Aggregated errors of the actions failures
   */


  triggerHook(hookName, args = {}, headers = {}, context = {}) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      const resultErrors = [];

      if (!_this.hooks.has(hookName)) {
        _logger().default.warn(`trying to trigger a non existing hook ${hookName}`);

        throw new (errors().HookNotExists)(hookName);
      }

      if (process.env.BIT_LOG) {
        // this is disabled by default due to performance implications
        // prefix your command with "BIT_LOG=*" to log all args and headers
        _logger().default.info(`triggering hook ${hookName} with args:\n ${_stringifyIfNeeded(_stripArgs(args))} \n and headers \n ${_stringifyIfNeeded(_stripHeaders(headers))} \n and context ${_stringifyIfNeeded(context)}`);
      } else {
        _logger().default.info(`triggering hook ${hookName}`);
      }

      const actions = _this.hooks.get(hookName);

      const actionsP = actions.map(action => {
        // Catch errors in order to aggregate them
        // Wrap in a promise in case the action doesn't return a promise
        return Promise.resolve().then(() => {
          _logger().default.info(`running action ${action.name} on hook ${hookName}`);

          return action.run(args, headers, context);
        }).catch(e => {
          _logger().default.error(`running action ${action.name} on hook ${hookName} failed, err:`);

          _logger().default.error(e);

          resultErrors.push({
            [action.name]: e
          });
        });
      });
      yield Promise.all(actionsP);
      return resultErrors;
    })();
  }

}

exports.default = HooksManager;

function _stringifyIfNeeded(val) {
  return typeof val === 'string' ? val : (0, _util().inspect)(val, {
    depth: 5
  });
}
/**
 * Remove some data from the logs (because it's too verbose or because it's sensitive)
 * @param {Object} args
 */


function _stripArgs(args) {
  // Create deep clone
  const res = _ramda().default.clone(args);

  if (res.componentObjects) {
    res.componentObjects = res.componentObjects.length;
  }

  return res;
}
/**
 * Remove some data from the logs (because it's too verbose or because it's sensitive)
 * @param {Object} headers
 */


function _stripHeaders(headers) {
  if (!headers) return; // Create deep clone

  const res = _ramda().default.clone(headers);

  if (res.context && res.context.pubSshKey) {
    const key = res.context.pubSshKey;
    res.context.pubSshKey = `last 70 characters: ${key.substr(key.length - 70)}`;
  }

  return res;
}