'use strict';

/**
 * Base class for API errors. Contains indication of HTTP status.
 */

const isNode = typeof process !== 'undefined' && process.release && process.release.name === 'node';

/**
 * Base class for API errors. Contains indication of HTTP status.
 */
export class ApiError extends Error {
  
  /** HTTP status code */
  public status: number;
  /** API request URL */
  public url: string;
  private _code: any;
  private _args: any;

  /**
   * ApiError constructor
   * @param clazz error function
   * @param message error message
   * @param status HTTP status
   * @param url API request URL
   */
  constructor(clazz: Function, message: string, status: number, url?: string) {
    super(url ? message + '. Request URL: ' + url : message);
    this.name = 'ApiError';
    this.status = status;
    this.url = url;

    if (isNode && Error.captureStackTrace) {
      Error.captureStackTrace(this, clazz);
    }
  }

  /**
   * Sets error code, used for i18n
   * @param {string} code error code for i18n
   */
  set code(code: string) {
    this._code = code;
  }

  /**
   * Returns error code used for i18n
   * @return {string} error code
   */
  get code(): string {
    return this._code;
  }

  /**
   * Set message arguments for i18n
   * @param {Array<Object>} args arguments for i18n
   */
  set arguments(args: Array<Object>) {
    this._args = args;
  }

  /**
   * Returns message arguments for i18n
   * @return {Array<Object>} message arguments for i18n
   */
  get arguments(): Array<Object> {
    return this._args;
  }
}

/**
 * Throwing this error results in 404 (Not Found) HTTP response code.
 */
export class NotFoundError extends ApiError {

  /**
   * Represents NotFoundError.
   * @param {string} message error message
   * @param {string} url API request URL
   */
  constructor(message: string, url?: string) {
    super(NotFoundError, message, 404, url);
    this.name = 'NotFoundError';
  }
}

/**
 * Throwing this error results in 403 (Forbidden) HTTP response code.
 */
export class ForbiddenError extends ApiError {

  /**
   * Constructs forbidden error.
   * @param {string} message error message
   * @param {string} url API request URL
   */
  constructor(message: string, url?: string) {
    super(ForbiddenError, message, 403, url);
    this.name = 'ForbiddenError';
  }
}

/**
 * Throwing this error results in 401 (Unauthorized) HTTP response code.
 */
export class UnauthorizedError extends ApiError {

  /**
   * Constructs unauthorized error.
   * @param {string} message error message
   * @param {string} url API request URL
   */
  constructor(message: string, url?: string) {
    super(UnauthorizedError, message, 401, url);
    this.name = 'UnauthorizedError';
  }
}

/**
 * Represents validation error. Throwing this error results in 400 (Bad Request) HTTP response code.
 */
export class ValidationError extends ApiError {
  
  /** Validation error details */
  public details: object;

  /**
   * Constructs validation error.
   * @param {string} message error message
   * @param {Object} details error data
   * @param {string} url API request URL
   */
  constructor(message: string, details?: Object, url?: string) {
    super(ValidationError, message, 400, url);
    this.name = 'ValidationError';
    this.details = details;
  }
}

/**
 * Represents unexpected error. Throwing this error results in 500 (Internal Error) HTTP response code.
 */
export class InternalError extends ApiError {

  /**
   * Constructs unexpected error.
   * @param {string} message error message
   * @param {string} url API request URL
   */
  constructor(message: string, url?: string) {
    super(InternalError, message, 500, url);
    this.name = 'InternalError';
  }
}

/**
 * Represents conflict error. Throwing this error results in 409 (Conflict) HTTP response code.
 */
export class ConflictError extends ApiError {
  /**
   * Constructs conflict error.
   * @param {string} message error message
   * @param {string} url API request URL
   */
  constructor(message: string, url?: string) {
    super(ConflictError, message, 409, url);
    this.name = 'ConflictError';
  }
}

/**
 * metadata for too many requests error
 */
export type TooManyRequestsErrorMetadata = {
  /** throttling period in minutes */
  periodInMinutes?: number,
  /** available requests for periodInMinutes */
  requestsPerPeriodAllowed?: number,
  /** recommended date to retry request */
  recommendedRetryTime: Date | string,
  /** error type */
  type?: string
}

/**
 * Represents too many requests error. Throwing this error results in 429 (Too Many Requests) HTTP response code.
 */
export class TooManyRequestsError extends ApiError {
  
  /** error metadata */
  public metadata: TooManyRequestsErrorMetadata;

  /**
   * Constructs too many requests error.
   * @param {string} message error message
   * @param {TooManyRequestsErrorMetadata} metadata error metadata
   */
  constructor(message: string, metadata: TooManyRequestsErrorMetadata, url?: string) {
    super(TooManyRequestsError, message, 429, url);
    this.name = 'TooManyRequestsError';
    this.metadata = metadata;
  }
}
