import EmberObject from 'ember-object';
import { later } from 'ember-runloop';
import RSVP from 'rsvp';

/*

  This object handles various asynchronous timing concerns for search queries.

  `isWaiting` - This flag serves as a loading indicator with a minimum amount
  of wait time determined by the `delayResult` option. At a miminum `isWaiting`
  will wait `delayResults` time before becoming false. If the request takes longer
  than `delayResults` time, it will wait until the request finishes.

  `isLoading` - This flag determines the true loading status of the search request.

  `didError` - This flag becomes true when a search request rejects.

 */

export default EmberObject.extend({
  init() {
    this._super(...arguments);
    this.isLoading = false;
    this.isWaiting = false;
    this.currentPromise = null;
  },

  /**
   * To mimic cancelable task behavior we use a `start` method for this task.
   * The task is "canceled" whenever `start` is called again or the `cancel`
   * method is called.
   *
   * When this task is canceled the rest of the callbacks in the promise chain
   * are no-ops and the success callback will not be called.
   *
   * @param {object} arguments - An object of arguments
   * @param {function} arguments.request - A callback representing the network
   * request to make. This function must return a promise.
   * @param {function} arguments.success - A callback that will be called with
   * results if the request is successfull and this task is not canceled.
   * @param {function} arugments.complete - A callback that is always called
   * when the task is complete (even if it is canceled).
   * @param {number} arugments.debounce - Amount of delay to use for debouncing
   * requests.
   * @param {number} arugments.delayResults. - The minimum amount of time to
   * wait before toggling isWaiting to false.
   */
  start({ request, success = () => {}, complete = () => {}, debounce, delayResults }) {
    this.set('isLoading', true);
    this.set('isWaiting', typeof delayResults === 'number');
    this.set('didError', false);

    this.cancel();

    let currentPromise = this.currentPromise = resolveAfter(debounce).then(() => {
      if (currentPromise._isCanceled) { return; }

      let requestPromise = request().then((results) => {
        if (currentPromise._isCanceled) { return; }
        success(results);
      }, () => {
        if (currentPromise._isCanceled) { return; }
        this.set('didError', true);
      }).finally(() => {
        if (currentPromise._isCanceled) { return; }
        this.set('isLoading', false);
      });

      return RSVP.all([requestPromise, resolveAfter(delayResults)]);
    }).finally(() => {
      if (!currentPromise._isCanceled) {
        this.set('isWaiting', false);
      }
      complete();
    });

    currentPromise._isCanceled = false;
  },

  cancel() {
    if (this.currentPromise) {
      this.currentPromise._isCanceled = true;
    }
  }
});

export function resolveAfter(time = 0) {
  return new RSVP.Promise(resolve => later(resolve, time));
}
