"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

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

var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

var _asyncIterator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncIterator"));

var _util = require("./util");

var _queryLdflex = _interopRequireDefault(require("@solid/query-ldflex"));

const evaluatorQueue = (0, _util.createTaskQueue)();
/**
 * Evaluates a map of LDflex expressions into a singular value or a list.
 * Expressions can be changed and/or re-evaluated.
 */

class ExpressionEvaluator {
  constructor() {
    (0, _defineProperty2.default)(this, "pending", {});
    (0, _defineProperty2.default)(this, "cancel", false);
  }

  /** Stops all pending and future evaluations */
  destroy() {
    this.pending = {};
    this.cancel = true;
    evaluatorQueue.clear(this);
  }
  /** Evaluates the given singular value and list expressions. */


  async evaluate(values, lists, updateCallback) {
    // Create evaluators for each expression, and mark them as pending
    const reset = {
      error: undefined,
      pending: true
    };
    const evaluators = evaluatorQueue.schedule([...Object.entries(values).map(([key, expr]) => {
      reset[key] = undefined;
      return () => this.evaluateAsValue(key, expr, updateCallback);
    }), ...Object.entries(lists).map(([key, expr]) => {
      reset[key] = [];
      return () => this.evaluateAsList(key, expr, updateCallback);
    })], this);
    updateCallback(reset); // Wait until all evaluators are done (or one of them errors)

    try {
      await Promise.all(evaluators);
    } catch (error) {
      updateCallback({
        error
      });
    } // Update the pending flag if all evaluators wrote their value or errored,
    // and if no new evaluators are pending


    const statuses = await Promise.all(evaluators.map(e => e.catch(error => {
      console.warn('@solid/react-components', 'Expression evaluation failed.', error);
      return true;
    }))); // Stop if results are no longer needed

    if (this.cancel) return; // Reset the pending flag if all are done and no others are pending

    if (!statuses.some(done => !done) && Object.keys(this.pending).length === 0) updateCallback({
      pending: false
    });
  }
  /** Evaluates the property expression as a singular value. */


  async evaluateAsValue(key, expr, updateCallback) {
    // Obtain and await the promise
    const promise = this.resolveExpression(key, expr, 'then');
    this.pending[key] = promise;

    try {
      const value = await promise; // Stop if another evaluator took over in the meantime (component update)

      if (this.pending[key] !== promise) return false;
      updateCallback({
        [key]: value
      });
    } // Ensure the evaluator is removed, even in case of errors
    finally {
      if (this.pending[key] === promise) delete this.pending[key];
    }

    return true;
  }
  /** Evaluates the property expression as a list. */


  async evaluateAsList(key, expr, updateCallback) {
    // Create the iterable
    const iterable = this.resolveExpression(key, expr, Symbol.asyncIterator);
    if (!iterable) return true;
    this.pending[key] = iterable; // Read the iterable

    const items = [];

    const update = () => !this.cancel && updateCallback({
      [key]: [...items]
    });

    const itemQueue = (0, _util.createTaskQueue)({
      timeBetween: 100,
      drop: true
    });

    try {
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;

      var _iteratorError;

      try {
        for (var _iterator = (0, _asyncIterator2.default)(iterable), _step, _value; _step = await _iterator.next(), _iteratorNormalCompletion = _step.done, _value = await _step.value, !_iteratorNormalCompletion; _iteratorNormalCompletion = true) {
          const item = _value;
          // Stop if another evaluator took over in the meantime (component update)
          if (this.pending[key] !== iterable) return false;
          items.push(item);
          itemQueue.schedule(update);
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return != null) {
            await _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }
    } // Ensure pending updates are applied, and the evaluator is removed
    finally {
      const needsUpdate = itemQueue.clear();

      if (this.pending[key] === iterable) {
        if (needsUpdate) update();
        delete this.pending[key];
      }
    }

    return true;
  }
  /** Resolves the property into an LDflex path. */


  resolveExpression(key, expr, expectedProperty) {
    // If the property is an LDflex string expression, resolve it
    if (!expr) return '';
    const resolved = typeof expr === 'string' ? _queryLdflex.default.resolve(expr) : expr; // Ensure that the resolved value is an LDflex path

    if (!resolved || typeof resolved[expectedProperty] !== 'function') throw new Error(`${key} should be an LDflex path or string but is ${expr}`);
    return resolved;
  }

}

exports.default = ExpressionEvaluator;