import { Component } from 'vue-property-decorator';
import Vue from 'vue';

import LoadingUntilPromiseResolvedOptions from './types/LoadingUntilPromiseResovledOptions';

/**
 * Миксин добавляет методы для автоматического управления флагом loading
 *
 * @example <caption>Добавляемые методы:</caption>
 * withLoading() // Для функций, возвращающих промис
 * loadingUntilPromiseResolved() // Для промисов
 */
@Component
export default class Loadable extends Vue {
  public loading: boolean;

  /**
   * Активирует флаг загрузки до тех пор,
   * пока промис, возвращаемый асинхронной функцией, не разрешится
   *
   * @example
   * async fetchData(params) {
   *   this.value = await this.withLoading(
   *     () => await LostPetitionService.getPaginated(params)
   *   ) as T;
   * }
   */
  async withLoading<T>(asyncFn: () => Promise<T>): Promise<T> {
    this.loading = true;
    return await asyncFn().finally(() => {
      this.loading = false;
    });
  }

  /**
   * Активирует флаг загрузки до тех пор,
   * пока промис не разрешится с изменяемой минимальной задержкой (по-умолчанию, 500мс).
   *
   * Важно: Обработчики на промисе сработают раньше окончания задержки.
   * Чтобы выполнить действие после, необходимо использовать await или then.
   *
   * @example <caption>Базовое использование</caption>
   * const promise = new Promise(...);
   * loadingUntilPromiseResolved(promise)
   *
   * @example <caption>Если необходимо действие после загрузки или задержки</caption>
   * const promise = new Promise(...);
   * loadingUntilPromiseResolved(promise)
   *   .then(promise => promise.then(...).catch(...)); // Аргументом возвращает оригинальный промис
   * // или
   * const promise = new Promise(...);
   * await loadingUntilPromiseResolved(promise); // Ожидаем окончания загрузки
   * promise.then(...).catch(...)
   *
   * @example <caption>Если нужно менять состояние поля отличного от this.loading</caption>
   * const promise = new Promise(...);
   * this.model.loading = true;
   * loadingUntilPromiseResolved(promise) // Ожидаем окончания загрузки
   *  .then(() => this.model.loading = false)
   */
  loadingUntilPromiseResolved<T>(
    promise: Promise<T>,
    options: LoadingUntilPromiseResolvedOptions = {}
  ): Promise<T> {
    const minDelay = options.minDelay ?? 500;
    const start = Date.now();
    this.loading = true;

    return new Promise((resolve) => {
      promise.finally(() => {
        const delta = Date.now() - start;
        const timeout = Math.max(minDelay - delta, 0);

        setTimeout(() => {
          this.loading = false;
          resolve(promise);
        }, timeout);
      });
    });
  }
}
