import { isAsyncIterable } from '../utils/iterable-utils';
import { COMPONENT_SYMBOL, PROP_TYPES_SYMBOL, ASYNC_ORIGINAL_SYMBOL, ASYNC_RESOLVED_SYMBOL, ASYNC_DEFAULTS_SYMBOL } from './constants';
const EMPTY_PROPS = Object.freeze({});
export default class ComponentState {
  constructor(component) {
    this.component = void 0;
    this.onAsyncPropUpdated = void 0;
    this.asyncProps = void 0;
    this.oldProps = void 0;
    this.oldAsyncProps = void 0;
    this.component = component;
    this.asyncProps = {};

    this.onAsyncPropUpdated = () => {};

    this.oldProps = null;
    this.oldAsyncProps = null;
  }

  finalize() {
    for (const propName in this.asyncProps) {
      const asyncProp = this.asyncProps[propName];

      if (asyncProp && asyncProp.type && asyncProp.type.release) {
        asyncProp.type.release(asyncProp.resolvedValue, asyncProp.type, this.component);
      }
    }

    this.asyncProps = {};
    this.component = null;
    this.resetOldProps();
  }

  getOldProps() {
    return this.oldAsyncProps || this.oldProps || EMPTY_PROPS;
  }

  resetOldProps() {
    this.oldAsyncProps = null;
    this.oldProps = this.component ? this.component.props : null;
  }

  hasAsyncProp(propName) {
    return propName in this.asyncProps;
  }

  getAsyncProp(propName) {
    const asyncProp = this.asyncProps[propName];
    return asyncProp && asyncProp.resolvedValue;
  }

  isAsyncPropLoading(propName) {
    if (propName) {
      const asyncProp = this.asyncProps[propName];
      return Boolean(asyncProp && asyncProp.pendingLoadCount > 0 && asyncProp.pendingLoadCount !== asyncProp.resolvedLoadCount);
    }

    for (const key in this.asyncProps) {
      if (this.isAsyncPropLoading(key)) {
        return true;
      }
    }

    return false;
  }

  reloadAsyncProp(propName, value) {
    this._watchPromise(propName, Promise.resolve(value));
  }

  setAsyncProps(props) {
    this.component = props[COMPONENT_SYMBOL] || this.component;
    const resolvedValues = props[ASYNC_RESOLVED_SYMBOL] || {};
    const originalValues = props[ASYNC_ORIGINAL_SYMBOL] || props;
    const defaultValues = props[ASYNC_DEFAULTS_SYMBOL] || {};

    for (const propName in resolvedValues) {
      const value = resolvedValues[propName];

      this._createAsyncPropData(propName, defaultValues[propName]);

      this._updateAsyncProp(propName, value);

      resolvedValues[propName] = this.getAsyncProp(propName);
    }

    for (const propName in originalValues) {
      const value = originalValues[propName];

      this._createAsyncPropData(propName, defaultValues[propName]);

      this._updateAsyncProp(propName, value);
    }
  }

  _fetch(propName, url) {
    return null;
  }

  _onResolve(propName, value) {}

  _onError(propName, error) {}

  _updateAsyncProp(propName, value) {
    if (!this._didAsyncInputValueChange(propName, value)) {
      return;
    }

    if (typeof value === 'string') {
      value = this._fetch(propName, value);
    }

    if (value instanceof Promise) {
      this._watchPromise(propName, value);

      return;
    }

    if (isAsyncIterable(value)) {
      this._resolveAsyncIterable(propName, value);

      return;
    }

    this._setPropValue(propName, value);
  }

  _freezeAsyncOldProps() {
    if (!this.oldAsyncProps && this.oldProps) {
      this.oldAsyncProps = Object.create(this.oldProps);

      for (const propName in this.asyncProps) {
        Object.defineProperty(this.oldAsyncProps, propName, {
          enumerable: true,
          value: this.oldProps[propName]
        });
      }
    }
  }

  _didAsyncInputValueChange(propName, value) {
    const asyncProp = this.asyncProps[propName];

    if (value === asyncProp.resolvedValue || value === asyncProp.lastValue) {
      return false;
    }

    asyncProp.lastValue = value;
    return true;
  }

  _setPropValue(propName, value) {
    this._freezeAsyncOldProps();

    const asyncProp = this.asyncProps[propName];

    if (asyncProp) {
      value = this._postProcessValue(asyncProp, value);
      asyncProp.resolvedValue = value;
      asyncProp.pendingLoadCount++;
      asyncProp.resolvedLoadCount = asyncProp.pendingLoadCount;
    }
  }

  _setAsyncPropValue(propName, value, loadCount) {
    const asyncProp = this.asyncProps[propName];

    if (asyncProp && loadCount >= asyncProp.resolvedLoadCount && value !== undefined) {
      this._freezeAsyncOldProps();

      asyncProp.resolvedValue = value;
      asyncProp.resolvedLoadCount = loadCount;
      this.onAsyncPropUpdated(propName, value);
    }
  }

  _watchPromise(propName, promise) {
    const asyncProp = this.asyncProps[propName];

    if (asyncProp) {
      asyncProp.pendingLoadCount++;
      const loadCount = asyncProp.pendingLoadCount;
      promise.then(data => {
        if (!this.component) {
          return;
        }

        data = this._postProcessValue(asyncProp, data);

        this._setAsyncPropValue(propName, data, loadCount);

        this._onResolve(propName, data);
      }).catch(error => {
        this._onError(propName, error);
      });
    }
  }

  async _resolveAsyncIterable(propName, iterable) {
    if (propName !== 'data') {
      this._setPropValue(propName, iterable);

      return;
    }

    const asyncProp = this.asyncProps[propName];

    if (!asyncProp) {
      return;
    }

    asyncProp.pendingLoadCount++;
    const loadCount = asyncProp.pendingLoadCount;
    let data = [];
    let count = 0;

    for await (const chunk of iterable) {
      if (!this.component) {
        return;
      }

      const {
        dataTransform
      } = this.component.props;

      if (dataTransform) {
        data = dataTransform(chunk, data);
      } else {
        data = data.concat(chunk);
      }

      Object.defineProperty(data, '__diff', {
        enumerable: false,
        value: [{
          startRow: count,
          endRow: data.length
        }]
      });
      count = data.length;

      this._setAsyncPropValue(propName, data, loadCount);
    }

    this._onResolve(propName, data);
  }

  _postProcessValue(asyncProp, value) {
    const propType = asyncProp.type;

    if (propType && this.component) {
      if (propType.release) {
        propType.release(asyncProp.resolvedValue, propType, this.component);
      }

      if (propType.transform) {
        return propType.transform(value, propType, this.component);
      }
    }

    return value;
  }

  _createAsyncPropData(propName, defaultValue) {
    const asyncProp = this.asyncProps[propName];

    if (!asyncProp) {
      const propTypes = this.component && this.component.props[PROP_TYPES_SYMBOL];
      this.asyncProps[propName] = {
        type: propTypes && propTypes[propName],
        lastValue: null,
        resolvedValue: defaultValue,
        pendingLoadCount: 0,
        resolvedLoadCount: 0
      };
    }
  }

}
//# sourceMappingURL=component-state.js.map