export interface IResolve<T> {
  (value?: T | PromiseLike<T>): void;
}

export interface IReject {
  (reason?: any): void;
}

export type TDeferredStatus = 'pending' | 'fulfilled' | 'rejected';

export class Deferred<T> {
  public promise: Promise<T>;
  public resolve: IResolve<T>;
  public reject: IReject;
  public status: TDeferredStatus;
  public claimed = false;
  public claim() {
    if (this.claimed) {
      throw new Error('Deferred already claimed');
    }
    this.claimed = true;
  }

  public startedAt: number;
  public stoppedAt: number;
  public get duration(): number {
    if (this.stoppedAt) {
      return this.stoppedAt - this.startedAt;
    } else {
      return Date.now() - this.startedAt;
    }
  }

  constructor() {
    this.promise = new Promise<T>((resolve, reject) => {
      this.resolve = (valueArg: T | PromiseLike<T>) => {
        this.status = 'fulfilled';
        this.stoppedAt = Date.now();
        resolve(valueArg);
      };
      this.reject = (reason: any) => {
        this.status = 'rejected';
        this.stoppedAt = Date.now();
        reject(reason);
      };
      this.startedAt = Date.now();
      this.status = 'pending';
    });
  }
}

export const defer = <T>() => {
  return new Deferred<T>();
};