export class AbortPromiseError extends Error {
  public name = 'AbortError';
  public event?: Event;
}
export class AbortPromise implements PromiseLike<AbortPromiseError> {
  public controller = new AbortController();
  public signal = this.controller.signal;
  public reason!: AbortPromiseError;

  public get aborted() {
    return this.signal.aborted;
  }

  public abort(reason: string) {
    if (this.signal.aborted) {
      return false;
    }

    this.reason = new AbortPromiseError(reason);
    this.controller.abort();

    return true;
  }

  public onAbort(callback: any) {
    if (this.signal.aborted) {
      callback(this.reason);

      return;
    }
    this.signal.addEventListener(
      'abort',
      (event: Event) => {
        this.reason.event = event;
        callback(this.reason);
      },
      { once: true }
    );
  }

  public then<T = AbortPromiseError>(
    resolve?: (value: AbortPromiseError) => PromiseLike<T> | T
  ): Promise<T> {
    if (resolve) {
      this.onAbort(resolve);
    }

    // eslint-disable-next-line promise/param-names
    return new Promise((r) => this.onAbort(r));
  }
}

export function delay(delay: number) {
  // eslint-disable-next-line promise/param-names
  return new Promise((r) => setTimeout(r, delay));
}

export async function repeat<T extends any, P extends any>(
  this: any,
  // eslint-disable-next-line default-param-last
  count = 5,
  func: (...args: T[]) => Promise<P>,
  ...params: T[]
): Promise<P> {
  // вообще надо ждать пуша, но к чёрту
  try {
    return await func.call(this, ...params);
  } catch (e) {
    if (count - 1 < 0) {
      throw e;
    }
    await delay(500);

    return repeat(count - 1, func, ...params);
  }
}

// потому что сгенерированные псевдопромисы клиента при поытке then делают запрос дважды =\
export function serviceToPromise(
  abort: AbortPromise | null | undefined,
  ...params: any[]
) {
  let api: any;

  if (abort instanceof AbortPromise || !abort) {
    api = params.shift();
  } else {
    api = abort;
    abort = null;
  }
  const action: string = params.shift();

  return new Promise((resolve, reject) => {
    const request = api[action](
      ...params,
      (error: Error, data: any, response: any) =>
        error ? reject(error) : resolve({ data, response })
    );

    if (abort) {
      abort.onAbort((e: Error) => {
        reject(e);
        request.abort(e);
      });
    }
  });
}
