import { extend } from '../vue';
import { arrayIncludes } from '../utils/array';
import { keys } from '../utils/object';
import { getEventRoot } from '../utils/get-event-root';
// --- Constants ---

var PROP = '$_rootListeners';

// --- Mixin ---

// @vue/component
export var listenOnRootMixin = extend({
  computed: {
    bvEventRoot: function bvEventRoot() {
      return getEventRoot(this);
    }
  },
  created: function created() {
    // Define non-reactive property
    // Object of arrays, keyed by event name,
    // where value is an array of callbacks
    this[PROP] = {};
  },
  beforeDestroy: function beforeDestroy() {
    var _this = this;
    // Unregister all registered listeners
    keys(this[PROP] || {}).forEach(function (event) {
      _this[PROP][event].forEach(function (callback) {
        _this.listenOffRoot(event, callback);
      });
    });
    this[PROP] = null;
  },
  methods: {
    registerRootListener: function registerRootListener(event, callback) {
      if (this[PROP]) {
        this[PROP][event] = this[PROP][event] || [];
        if (!arrayIncludes(this[PROP][event], callback)) {
          this[PROP][event].push(callback);
        }
      }
    },
    unregisterRootListener: function unregisterRootListener(event, callback) {
      if (this[PROP] && this[PROP][event]) {
        this[PROP][event] = this[PROP][event].filter(function (cb) {
          return cb !== callback;
        });
      }
    },
    /**
     * Safely register event listeners on the root Vue node
     * While Vue automatically removes listeners for individual components,
     * when a component registers a listener on `$root` and is destroyed,
     * this orphans a callback because the node is gone, but the `$root`
     * does not clear the callback
     *
     * When registering a `$root` listener, it also registers the listener
     * to be removed in the component's `beforeDestroy()` hook
     *
     * @param {string} event
     * @param {function} callback
     */
    listenOnRoot: function listenOnRoot(event, callback) {
      if (this.bvEventRoot) {
        this.bvEventRoot.$on(event, callback);
        this.registerRootListener(event, callback);
      }
    },
    /**
     * Safely register a `$once()` event listener on the root Vue node
     * While Vue automatically removes listeners for individual components,
     * when a component registers a listener on `$root` and is destroyed,
     * this orphans a callback because the node is gone, but the `$root`
     * does not clear the callback
     *
     * When registering a `$root` listener, it also registers the listener
     * to be removed in the component's `beforeDestroy()` hook
     *
     * @param {string} event
     * @param {function} callback
     */
    listenOnRootOnce: function listenOnRootOnce(event, callback) {
      var _this2 = this;
      if (this.bvEventRoot) {
        var _callback2 = function _callback() {
          _this2.unregisterRootListener(_callback2);
          // eslint-disable-next-line node/no-callback-literal
          callback.apply(void 0, arguments);
        };
        this.bvEventRoot.$once(event, _callback2);
        this.registerRootListener(event, _callback2);
      }
    },
    /**
     * Safely unregister event listeners from the root Vue node
     *
     * @param {string} event
     * @param {function} callback
     */
    listenOffRoot: function listenOffRoot(event, callback) {
      this.unregisterRootListener(event, callback);
      if (this.bvEventRoot) {
        this.bvEventRoot.$off(event, callback);
      }
    },
    /**
     * Convenience method for calling `vm.$emit()` on `$root`
     *
     * @param {string} event
     * @param {*} args
     */
    emitOnRoot: function emitOnRoot(event) {
      if (this.bvEventRoot) {
        var _this$bvEventRoot;
        for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
          args[_key - 1] = arguments[_key];
        }
        (_this$bvEventRoot = this.bvEventRoot).$emit.apply(_this$bvEventRoot, [event].concat(args));
      }
    }
  }
});