"use strict";

exports.__esModule = true;
exports.default = void 0;

var _ssrWindow = require("ssr-window");

var _template = _interopRequireDefault(require("template7"));

var _dom = _interopRequireDefault(require("../../shared/dom7"));

var _utils = require("../../shared/utils");

var _vdom = _interopRequireDefault(require("./vdom"));

var _patch = _interopRequireDefault(require("./patch"));

var _componentMixins = _interopRequireDefault(require("./component-mixins"));

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

/* eslint no-underscore-dangle: "off" */
var Component = /*#__PURE__*/function () {
  function Component(app, options, extendContext, children) {
    if (options === void 0) {
      options = {};
    }

    if (extendContext === void 0) {
      extendContext = {};
    }

    var window = (0, _ssrWindow.getWindow)();
    var document = (0, _ssrWindow.getDocument)();
    var componentId = (0, _utils.id)();
    var self = this;
    (0, _utils.merge)(self, {
      $props: {}
    }, extendContext, {
      $: _dom.default,
      $$: _dom.default,
      $dom7: _dom.default,
      $app: app,
      $f7: app,
      $options: (0, _utils.extend)({
        id: componentId
      }, options),
      $id: options.isClassComponent ? self.constructor.id : options.id || componentId,
      $mixins: options.isClassComponent ? self.constructor.mixins : options.mixins,
      $children: children || [],
      $isRootComponent: !!options.root
    });
    var $options = self.$options;

    if (self.$mixins && self.$mixins.length) {
      for (var i = self.$mixins.length - 1; i >= 0; i -= 1) {
        var mixin = self.$mixins[i];

        if (typeof mixin === 'string') {
          if (_componentMixins.default[mixin]) self.$mixins[i] = _componentMixins.default[mixin];else self.$mixins.splice(i, 1);
        }
      }
    }

    Object.defineProperty(self, '$slots', {
      enumerable: true,
      configurable: true,
      get: function get() {
        var slots = {};
        self.$children.forEach(function (childVNode) {
          var childSlotName = 'default';

          if (childVNode.data) {
            childSlotName = childVNode.data.attrs && childVNode.data.attrs.slot || 'default';
          }

          if (!slots[childSlotName]) slots[childSlotName] = [];
          slots[childSlotName].push(childVNode);
        });
        return slots;
      }
    }); // Root data and methods

    Object.defineProperty(self, '$root', {
      enumerable: true,
      configurable: true,
      get: function get() {
        if (self.$isRootComponent) {
          return self;
        }

        if (app.rootComponent) {
          if (!self.$onRootUpdated) {
            self.$onRootUpdated = function () {
              return self.$update();
            };

            app.on('rootComponentUpdated', self.$onRootUpdated);
          }

          return app.rootComponent;
        }

        var root = (0, _utils.merge)({}, app.data, app.methods);

        if (window && window.Proxy) {
          root = new window.Proxy(root, {
            set: function set(target, name, val) {
              app.data[name] = val;
            },
            deleteProperty: function deleteProperty(target, name) {
              delete app.data[name];
              delete app.methods[name];
            },
            has: function has(target, name) {
              return name in app.data || name in app.methods;
            }
          });
        }

        return root;
      },
      set: function set() {}
    }); // Bind render

    if ($options.render) $options.render = $options.render.bind(self); // Bind methods

    var methods = {};

    if (self.$mixins && self.$mixins.length) {
      self.$mixins.forEach(function (mixin) {
        if (mixin.methods) Object.assign(methods, mixin.methods);
      });
    }

    if ($options.methods) {
      Object.assign(methods, $options.methods);
    }

    Object.keys(methods).forEach(function (methodName) {
      self[methodName] = methods[methodName].bind(self);
    }); // Bind Events

    if ($options.on) {
      Object.keys($options.on).forEach(function (eventName) {
        $options.on[eventName] = $options.on[eventName].bind(self);
      });
    }

    if ($options.once) {
      Object.keys($options.once).forEach(function (eventName) {
        $options.once[eventName] = $options.once[eventName].bind(self);
      });
    }

    self.$style = $options.isClassComponent ? self.constructor.style : $options.style;
    self.$styleScoped = $options.isClassComponent ? self.constructor.styleScoped : $options.styleScoped;
    self.__updateQueue = [];
    return new Promise(function (resolve, reject) {
      self.$hook('data', true).then(function (datas) {
        var data = {};
        datas.forEach(function (dt) {
          Object.assign(data, dt || {});
        });
        (0, _utils.extend)(self, data);
        self.$hook('beforeCreate');
        var html = self.$render();

        if (self.$options.el) {
          html = html.trim();
          self.$vnode = (0, _vdom.default)(html, self, true);

          if (self.$style) {
            self.$styleEl = document.createElement('style');
            self.$styleEl.innerHTML = self.$style;
          }

          self.el = self.$options.el;
          (0, _patch.default)(self.el, self.$vnode);
          self.el = self.$vnode.elm;
          self.$el = (0, _dom.default)(self.el);
          self.$attachEvents();
          self.el.f7Component = self;
          self.$hook('created');
          self.$mount();
          resolve(self);
          return;
        } // Make Dom


        if (html && typeof html === 'string') {
          html = html.trim();
          self.$vnode = (0, _vdom.default)(html, self, true);
          self.el = document.createElement(self.$vnode.sel || 'div');
          (0, _patch.default)(self.el, self.$vnode);
          self.$el = (0, _dom.default)(self.el);
        } else if (html) {
          self.el = html;
          self.$el = (0, _dom.default)(self.el);
        }

        if (self.$style) {
          self.$styleEl = document.createElement('style');
          self.$styleEl.innerHTML = self.$style;
        }

        self.$attachEvents();

        if (self.el) {
          self.el.f7Component = self;
        }

        self.$hook('created');
        resolve(self);
      }).catch(function (err) {
        reject(err);
      });
    });
  }

  var _proto = Component.prototype;

  _proto.$attachEvents = function $attachEvents() {
    var self = this;
    var $options = self.$options,
        $el = self.$el;

    if (self.$mixins && self.$mixins.length) {
      self.$detachEventsHandlers = {};
      self.$mixins.forEach(function (mixin) {
        if (mixin.on) {
          Object.keys(mixin.on).forEach(function (eventName) {
            var handler = mixin.on[eventName].bind(self);
            if (!self.$detachEventsHandlers[eventName]) self.$detachEventsHandlers[eventName] = [];
            self.$detachEventsHandlers[eventName].push(handler);
            $el.on((0, _utils.eventNameToColonCase)(eventName), handler);
          });
        }

        if (mixin.once) {
          Object.keys(mixin.once).forEach(function (eventName) {
            var handler = mixin.once[eventName].bind(self);
            if (!self.$detachEventsHandlers[eventName]) self.$detachEventsHandlers[eventName] = [];
            self.$detachEventsHandlers[eventName].push(handler);
            $el.once((0, _utils.eventNameToColonCase)(eventName), handler);
          });
        }
      });
    }

    if ($options.on) {
      Object.keys($options.on).forEach(function (eventName) {
        $el.on((0, _utils.eventNameToColonCase)(eventName), $options.on[eventName]);
      });
    }

    if ($options.once) {
      Object.keys($options.once).forEach(function (eventName) {
        $el.once((0, _utils.eventNameToColonCase)(eventName), $options.once[eventName]);
      });
    }
  };

  _proto.$detachEvents = function $detachEvents() {
    var self = this;
    var $options = self.$options,
        $el = self.$el;

    if ($options.on) {
      Object.keys($options.on).forEach(function (eventName) {
        $el.off((0, _utils.eventNameToColonCase)(eventName), $options.on[eventName]);
      });
    }

    if ($options.once) {
      Object.keys($options.once).forEach(function (eventName) {
        $el.off((0, _utils.eventNameToColonCase)(eventName), $options.once[eventName]);
      });
    }

    if (!self.$detachEventsHandlers) return;
    Object.keys(self.$detachEventsHandlers).forEach(function (eventName) {
      var handlers = self.$detachEventsHandlers[eventName];
      handlers.forEach(function (handler) {
        $el.off((0, _utils.eventNameToColonCase)(eventName), handler);
      });
      self.$detachEventsHandlers[eventName] = [];
      delete self.$detachEventsHandlers[eventName];
    });
    self.$detachEventsHandlers = null;
    delete self.$detachEventsHandlers;
  };

  _proto.$render = function $render() {
    var self = this;
    var $options = self.$options;
    var html = '';

    if ($options.render) {
      html = $options.render();
    } else if (self.render) {
      html = self.render.call(self);
    } else if ($options.template) {
      if (typeof $options.template === 'string') {
        html = _template.default.compile($options.template)(self);
      } else {
        // Supposed to be function
        html = $options.template(self);
      }
    }

    return html;
  };

  _proto.$startUpdateQueue = function $startUpdateQueue() {
    var self = this;
    var window = (0, _ssrWindow.getWindow)();
    if (self.__requestAnimationFrameId) return;

    function update() {
      var html = self.$render(); // Make Dom

      if (html && typeof html === 'string') {
        html = html.trim();
        var newVNode = (0, _vdom.default)(html, self, false);
        self.$vnode = (0, _patch.default)(self.$vnode, newVNode);
      }
    }

    self.__requestAnimationFrameId = window.requestAnimationFrame(function () {
      if (self.__updateIsPending) update();
      var resolvers = [].concat(self.__updateQueue);
      self.__updateQueue = [];
      self.__updateIsPending = false;
      window.cancelAnimationFrame(self.__requestAnimationFrameId);
      delete self.__requestAnimationFrameId;
      delete self.__updateIsPending;
      resolvers.forEach(function (resolver) {
        return resolver();
      });
      resolvers = [];
    });
  };

  _proto.$tick = function $tick(callback) {
    var self = this;
    return new Promise(function (resolve) {
      function resolver() {
        resolve();
        if (callback) callback();
      }

      self.__updateQueue.push(resolver);

      self.$startUpdateQueue();
    });
  };

  _proto.$update = function $update(callback) {
    var self = this;
    return new Promise(function (resolve) {
      function resolver() {
        resolve();
        if (callback) callback();

        if (self.$isRootComponent) {
          self.$f7.emit('rootComponentUpdated');
        }
      }

      self.__updateIsPending = true;

      self.__updateQueue.push(resolver);

      self.$startUpdateQueue();
    });
  };

  _proto.$setState = function $setState(mergeState, callback) {
    if (mergeState === void 0) {
      mergeState = {};
    }

    var self = this;
    (0, _utils.merge)(self, mergeState);
    return self.$update(callback);
  };

  _proto.$f7ready = function $f7ready(callback) {
    var _this = this;

    if (this.$f7.initialized) {
      callback(this.$f7);
      return;
    }

    this.$f7.once('init', function () {
      callback(_this.$f7);
    });
  };

  _proto.$mount = function $mount(mountMethod) {
    var self = this;
    self.$hook('beforeMount');
    if (self.$styleEl) (0, _dom.default)('head').append(self.$styleEl);
    if (mountMethod) mountMethod(self.el);
    self.$hook('mounted');
  };

  _proto.$destroy = function $destroy() {
    var self = this;
    var window = (0, _ssrWindow.getWindow)();
    self.$hook('beforeDestroy');
    if (self.$styleEl) (0, _dom.default)(self.$styleEl).remove();

    if (self.$onRootUpdated) {
      self.$f7.off('rootComponentUpdated', self.$onRootUpdated);
      delete self.$onRootUpdated;
    }

    self.$detachEvents();
    self.$hook('destroyed'); // Delete component instance

    if (self.el && self.el.f7Component) {
      self.el.f7Component = null;
      delete self.el.f7Component;
    } // Patch with empty node


    if (self.$vnode) {
      self.$vnode = (0, _patch.default)(self.$vnode, {
        sel: self.$vnode.sel,
        data: {}
      });
    } // Clear update queue


    window.cancelAnimationFrame(self.__requestAnimationFrameId); // Delete all props

    (0, _utils.deleteProps)(self);
  };

  _proto.$hook = function $hook(name, async) {
    var self = this;

    if (async) {
      var promises = [];

      if (self.$mixins && self.$mixins.length) {
        self.$mixins.forEach(function (mixin) {
          if (mixin[name]) promises.push(mixin[name].call(self));
        });
      }

      if (self[name] && typeof self[name] === 'function') {
        promises.push(self[name].call(self));
      }

      if (self.$options[name]) {
        promises.push(self.$options[name].call(self));
      }

      return Promise.all(promises);
    }

    if (self.$mixins && self.$mixins.length) {
      self.$mixins.forEach(function (mixin) {
        if (mixin[name] && typeof mixin[name] === 'function') {
          mixin[name].call(self);
        }
      });
    }

    if (self.$options[name]) return self.$options[name].call(self);
    if (self[name]) return self[name].call(self);
    return undefined;
  };

  return Component;
}();

var _default = Component;
exports.default = _default;