/* Riot WIP, @license MIT */
import { autobindMethods, defineProperties, isObject, defineProperty, IS_PURE_SYMBOL, PARENT_KEY_SYMBOL, ATTRIBUTES_KEY_SYMBOL, PROPS_KEY, evaluateAttributeExpressions, STATE_KEY, TEMPLATE_KEY_SYMBOL, ROOT_KEY, SLOTS_KEY, ON_BEFORE_MOUNT_KEY, ON_MOUNTED_KEY, SHOULD_UPDATE_KEY, ON_BEFORE_UPDATE_KEY, IS_COMPONENT_UPDATING, ON_UPDATED_KEY, ON_BEFORE_UNMOUNT_KEY, ON_UNMOUNTED_KEY, isFunction } from '@riotjs/util';
import { addCssHook } from './add-css-hook.js';
import { bindDOMNodeToComponentInstance } from './bind-dom-node-to-component-instance.js';
import { computeComponentState } from './compute-component-state.js';
import { computeInitialProps } from './compute-initial-props.js';
import { createAttributeBindings } from './create-attribute-bindings.js';
import { runPlugins } from './run-plugins.js';

/**
 * Component creation factory function that will enhance the user provided API
 * @param   {Object} component - a component implementation previously defined
 * @param   {Array} options.slots - component slots generated via riot compiler
 * @param   {Array} options.attributes - attribute expressions generated via riot compiler
 * @returns {Riot.Component} a riot component instance
 */

function manageComponentLifecycle(component, _ref) {
  let {
    slots,
    attributes,
    props
  } = _ref;
  return autobindMethods(runPlugins(defineProperties(isObject(component) ? Object.create(component) : component, {
    mount(element, state, parentScope) {
      if (state === void 0) {
        state = {};
      }

      // any element mounted passing through this function can't be a pure component
      defineProperty(element, IS_PURE_SYMBOL, false);
      this[PARENT_KEY_SYMBOL] = parentScope;
      this[ATTRIBUTES_KEY_SYMBOL] = createAttributeBindings(element, attributes).mount(parentScope);
      defineProperty(this, PROPS_KEY, Object.freeze(Object.assign({}, computeInitialProps(element, props), evaluateAttributeExpressions(this[ATTRIBUTES_KEY_SYMBOL].expressions))));
      this[STATE_KEY] = computeComponentState(this[STATE_KEY], state);
      this[TEMPLATE_KEY_SYMBOL] = this.template.createDOM(element).clone(); // link this object to the DOM node

      bindDOMNodeToComponentInstance(element, this); // add eventually the 'is' attribute

      component.name && addCssHook(element, component.name); // define the root element

      defineProperty(this, ROOT_KEY, element); // define the slots array

      defineProperty(this, SLOTS_KEY, slots); // before mount lifecycle event

      this[ON_BEFORE_MOUNT_KEY](this[PROPS_KEY], this[STATE_KEY]); // mount the template

      this[TEMPLATE_KEY_SYMBOL].mount(element, this, parentScope);
      this[ON_MOUNTED_KEY](this[PROPS_KEY], this[STATE_KEY]);
      return this;
    },

    update(state, parentScope) {
      if (state === void 0) {
        state = {};
      }

      if (parentScope) {
        this[PARENT_KEY_SYMBOL] = parentScope;
        this[ATTRIBUTES_KEY_SYMBOL].update(parentScope);
      }

      const newProps = evaluateAttributeExpressions(this[ATTRIBUTES_KEY_SYMBOL].expressions);
      if (this[SHOULD_UPDATE_KEY](newProps, this[PROPS_KEY]) === false) return;
      defineProperty(this, PROPS_KEY, Object.freeze(Object.assign({}, this[PROPS_KEY], newProps)));
      this[STATE_KEY] = computeComponentState(this[STATE_KEY], state);
      this[ON_BEFORE_UPDATE_KEY](this[PROPS_KEY], this[STATE_KEY]); // avoiding recursive updates
      // see also https://github.com/riot/riot/issues/2895

      if (!this[IS_COMPONENT_UPDATING]) {
        this[IS_COMPONENT_UPDATING] = true;
        this[TEMPLATE_KEY_SYMBOL].update(this, this[PARENT_KEY_SYMBOL]);
      }

      this[ON_UPDATED_KEY](this[PROPS_KEY], this[STATE_KEY]);
      this[IS_COMPONENT_UPDATING] = false;
      return this;
    },

    unmount(preserveRoot) {
      this[ON_BEFORE_UNMOUNT_KEY](this[PROPS_KEY], this[STATE_KEY]);
      this[ATTRIBUTES_KEY_SYMBOL].unmount(); // if the preserveRoot is null the template html will be left untouched
      // in that case the DOM cleanup will happen differently from a parent node

      this[TEMPLATE_KEY_SYMBOL].unmount(this, this[PARENT_KEY_SYMBOL], preserveRoot === null ? null : !preserveRoot);
      this[ON_UNMOUNTED_KEY](this[PROPS_KEY], this[STATE_KEY]);
      return this;
    }

  })), Object.keys(component).filter(prop => isFunction(component[prop])));
}

export { manageComponentLifecycle };
