'use strict';

/**
 * Wrap the method to make sure when the method is invoked, it will only be essentially running after the component is load
 * For example in {@link VegaDatePicker},
 * we want to make sure when {@code @Watch('value')} method is invoked, it should wait for the component is loaded before executing
 *
 * @returns {FunctionDecoratedType} The function decorated
 */
function ComponentLoadRequired() {
  return (componentClass, _decoratedMethodName, decoratedMethod) => {
    const renderedInstanceSet = new Set();
    const { componentDidLoad, disconnectedCallback, connectedCallback } = componentClass;
    /* eslint-disable jsdoc/require-jsdoc */
    componentClass.componentDidLoad = function () {
      renderedInstanceSet.add(this);
      return componentDidLoad && componentDidLoad.call(this);
    };
    componentClass.disconnectedCallback = function () {
      renderedInstanceSet.delete(this);
      this.componentDidLoadedFlag = true;
      return disconnectedCallback && disconnectedCallback.call(this);
    };
    componentClass.connectedCallback = function () {
      this.componentDidLoadedFlag && renderedInstanceSet.add(this);
      return connectedCallback && connectedCallback.call(this);
    };
    const originalMethodBody = decoratedMethod.value;
    /* eslint-disable jsdoc/require-jsdoc */
    decoratedMethod.value = function (...args) {
      if (renderedInstanceSet.has(this)) {
        return originalMethodBody.apply(this, args);
      }
      return new Promise((resolve) => {
        const waitForComponentReady = () => {
          if (renderedInstanceSet.has(this)) {
            return resolve(originalMethodBody.apply(this, args));
          }
          setTimeout(waitForComponentReady, 30);
        };
        waitForComponentReady();
      });
    };
    return decoratedMethod;
  };
}

exports.ComponentLoadRequired = ComponentLoadRequired;
