'use strict';

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _bluebird = require('bluebird');

var _bluebird2 = _interopRequireDefault(_bluebird);

var _engine = require('./engine');

var _engine2 = _interopRequireDefault(_engine);

var _proactive = require('./proactive');

var _proactive2 = _interopRequireDefault(_proactive);

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

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new _bluebird2.default(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 _bluebird2.default.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } /**
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  * The Content Renderer is in charge of transforming an {@link ContentManager~Element}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  * into a channel-specific object.
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  * @namespace ContentRenderer
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  * @example
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  * bp.renderers
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  */

module.exports = ({ logger, middlewares, db, contentManager, botfile }) => {
  const processors = {}; // A map of all the platforms that can process outgoing messages
  const renderers = {}; // A map of all the registered renderers

  const registerChannel = ({ platform, processOutgoing }) => {
    if (!_lodash2.default.isString(platform)) {
      throw new Error(`[Renderers] Platform must be a string, got: ${platform}.`);
    }
    if (processors[platform]) {
      throw new Error(`[Renderers] Platform should only be registered once, platform: ${platform}.`);
    }
    if (!_lodash2.default.isFunction(processOutgoing)) {
      throw new Error(`[Renderers] processOutgoing must be a function, platform: ${platform}.`);
    }

    logger.verbose(`[Renderers] Enabled for ${platform}.`);

    processors[platform] = processOutgoing;
  };

  /**
   * @callback Renderer
   * @memberof! ContentRenderer
   * @example
   * bp.renderers.register('#text', data => ({ text: data.englishText }))
   */

  /**
   * Registers a new renderer
   * @param  {String} name Unique name of the renderer (e.g. `#text`).
   * @param  {ContentRenderer.Renderer} rendererFn The rendering function
   * @memberOf! ContentRenderer
   */
  const register = (name, rendererFn) => {
    if (!_lodash2.default.isString(name)) {
      throw new Error(`Renderer name must be a string, received ${name}`);
    }
    if (name.startsWith('#')) {
      name = name.substr(1);
    }

    renderers[name] = rendererFn;
  };

  /**
   * Removes a specific renderer if it exists
   * @param  {String} name Unique name of the renderer (e.g. `#text`)
   * @memberOf! ContentRenderer
   */
  const unregister = name => {
    if (!_lodash2.default.isString(name)) {
      throw new Error(`Renderer name must be a string, received ${name}`);
    }
    if (name.startsWith('#')) {
      name = name.substr(1);
    }
    delete renderers[name];
  };

  /**
   * Returns whether or not a renderer is already registered
   * @param  {String} name Unique name of the renderer (e.g. `#text`)
   * @return {Boolean}
   * @memberOf! ContentRenderer
   */
  const isRegistered = name => {
    if (!_lodash2.default.isString(name)) {
      throw new Error(`Renderer name must be a string, received ${name}`);
    }
    if (name.startsWith('#')) {
      name = name.substr(1);
    }
    return !!renderers[name];
  };

  const invoke = ({ rendererFn, rendererName, context, outputPlatform, incomingEvent = null }) => {
    // TODO throw if incomingEvents null <<<==== MOCK IT

    const options = {
      throwIfNoPlatform: true,
      currentPlatform: outputPlatform
    };

    return (0, _engine2.default)({ rendererFn, rendererName, context, options, processors, incomingEvent });
  };

  const doSendContent = (() => {
    var _ref = _asyncToGenerator(function* (rendererFn, { rendererName, context, outputPlatform, incomingEvent }) {
      const messages = yield invoke({ rendererFn, rendererName, context, outputPlatform, incomingEvent });

      return _bluebird2.default.mapSeries(messages, function (message) {
        if (message.__internal) {
          if (message.type === 'wait') {
            return _bluebird2.default.delay(message.wait);
          }
        } else {
          return middlewares.sendOutgoing(message);
        }
      });
    });

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

  const sendContent = (() => {
    var _ref2 = _asyncToGenerator(function* (incomingEvent, rendererName, additionalData = {}) {
      rendererName = rendererName.startsWith('#') ? rendererName.substr(1) : rendererName;

      // "magic" constants that can be used in the renderers
      const initialData = {
        BOT_URL: botfile.botUrl
      };

      if (rendererName.startsWith('!')) {
        const itemName = rendererName.substr(1);
        const contentItem = yield contentManager.getItem(itemName);

        if (!contentItem) {
          throw new Error(`Could not find content item with ID "${itemName}" in the Content Manager`);
        }

        const { categoryId: itemCategoryId } = contentItem;

        const itemCategory = contentManager.getCategorySchema(itemCategoryId);

        if (!itemCategory) {
          throw new Error(`Could not find category "${itemCategoryId}" in the Content Manager` + ` for item with ID "${itemName}"`);
        }

        const itemRenderer = itemCategory.renderer;
        if (!_lodash2.default.isString(itemRenderer) || !itemRenderer.startsWith('#') || itemRenderer.length <= 1) {
          throw new Error(`Invalid renderer '${itemRenderer}' in category '${itemCategoryId}' of Content Manager.
         A renderer must start with '#'`);
        }

        rendererName = itemRenderer.substr(1);
        Object.assign(initialData, _lodash2.default.isArray(contentItem.data) ? { items: contentItem.data } : contentItem.data);
      }

      const fullContext = Object.assign(initialData, {
        user: incomingEvent.user,
        event: _lodash2.default.pick(incomingEvent, ['raw', 'text', 'type', 'platform', 'user'])
      }, additionalData);

      const renderer = renderers[rendererName];

      if (!renderer) {
        const error = `[Renderer] Renderer not defined (#${rendererName})`;
        logger.error(error);
        throw new Error(error);
      }

      yield doSendContent(renderer, {
        rendererName,
        context: fullContext,
        outputPlatform: incomingEvent.platform,
        incomingEvent
      });

      return {
        renderer: rendererName,
        context: fullContext,
        outputPlatform: incomingEvent.platform
      };
    });

    return function sendContent(_x3, _x4) {
      return _ref2.apply(this, arguments);
    };
  })();

  const processIncoming = (event, next) => {
    event.reply = (rendererName, additionalData = {}) => sendContent(event, rendererName, additionalData);
    next();
  };

  const incomingMiddleware = {
    name: 'rendering.instrumentation',
    type: 'incoming',
    order: 2, // Should really be first
    module: 'botpress',
    description: 'Built-in Botpress middleware that adds a `.reply` to events. Works with renderers.',
    handler: processIncoming
  };

  const proactiveMethods = (0, _proactive2.default)({ sendContent, db });

  return _extends({
    registerChannel,
    registerConnector: registerChannel, // DEPRECATED Use "channel" instead of "connector"
    register,
    unregister,
    isRegistered,
    incomingMiddleware
  }, proactiveMethods);
};
//# sourceMappingURL=index.js.map