import { Readable, Stream } from 'stream'
import { AuthInfo, Session } from 'electron'
import { Agent } from 'https'

export default fetch

declare function fetch (
  url: RequestInfo,
  options?: RequestInit
): Promise<Response>

export enum FetchErrorType {
  BodyTimeout = "body-timeout",
  System = "system",
  MaxSize = "max-size",
  Abort = "abort",
  RequestTimeout = "request-timeout",
  Proxy = "proxy",
  NoRedirect = "no-redirect",
  MaxRedirect = "max-redirect",
  InvalidRedirect = "invalid-redirect",
}

export class FetchError extends Error {
  constructor(message: string, type: FetchErrorType, systemError?: { code: string });
  type: string;
  code?: string;
}

export type HeadersInit = Headers | string[][] | { [key: string]: string }

export class Headers {
  constructor (init?: HeadersInit)

  append (name: string, value: string): void

  delete (name: string): void

  get (name: string): string | null

  has (name: string): boolean

  set (name: string, value: string): void

  // WebIDL pair iterator: iterable<ByteString, ByteString>
  entries (): IterableIterator<[string, string]>

  forEach (callback: (value: string, name: string, headers: Headers) => void, thisArg?: any): void

  keys (): IterableIterator<string>

  values (): IterableIterator<string>

  [Symbol.iterator] (): IterableIterator<[string, string]>
}

export type BodyInit = Stream | string | Blob | Buffer | null

export interface Body {
  readonly bodyUsed: boolean

  arrayBuffer (): Promise<ArrayBuffer>

  blob (): Promise<Blob>

  formData (): Promise<FormData>

  json<T = any> (): Promise<T>

  text (): Promise<string>

  buffer (): Promise<Buffer>
}

export class Response implements Body {
  constructor (body: BodyInit, init?: ResponseInit)

  readonly url: string
  readonly status: number
  readonly ok: boolean
  readonly statusText: string
  readonly headers: Headers
  readonly body: Readable | string

  clone (): Response

  // Body impl
  readonly bodyUsed: boolean

  arrayBuffer (): Promise<ArrayBuffer>

  blob (): Promise<Blob>

  formData (): Promise<FormData>

  json<T = any> (): Promise<T>

  text (): Promise<string>

  buffer (): Promise<Buffer>
}

export interface RequestInit {
  // These properties are part of the Fetch Standard
  method?: string
  headers?: HeadersInit
  body?: BodyInit
  signal?: AbortSignal
  // (/!\ only works when running on Node.js) set to `manual` to extract redirect headers, `error` to reject redirect
  redirect?: RequestRedirect

  ////////////////////////////////////////////////////////////////////////////
  // The following properties are electron-fetch extensions

  // (/!\ only works when running on Node.js) maximum redirect count. 0 to not follow redirect
  follow?: number
  // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies)
  timeout?: number
  // maximum response body size in bytes. 0 to disable
  size?: number
  session?: Session
  agent?: Agent,
  useElectronNet?: boolean
  useSessionCookies?: boolean
  // When running on Electron behind an authenticated HTTP proxy, username to use to authenticate
  user?: string
  // When running on Electron behind an authenticated HTTP proxy, password to use to authenticate
  password?: string
  /**
   * When running on Electron behind an authenticated HTTP proxy, handler of `electron.ClientRequest`'s `login` event.
   * Can be used for acquiring proxy credentials in an async manner (e.g. prompting the user).
   */
  onLogin?: (authInfo: AuthInfo) => Promise<{ username: string, password: string } | undefined>
}

export type RequestInfo = Request | string

export class Request implements Body {
  constructor (input: RequestInfo, init?: RequestInit)

  readonly method: string
  readonly url: string
  readonly headers: Headers

  readonly redirect: RequestRedirect
  readonly signal: AbortSignal

  clone (): Request

  ////////////////////////////////////////////////////////////////////////////
  // The following properties are electron-fetch extensions

  // (/!\ only works when running on Node.js) maximum redirect count. 0 to not follow redirect
  follow: number
  // (/!\ only works when running on Node.js)
  counter: number
  // (/!\ only works when running on Electron)
  session?: Session
  // (/!\ only works when running on Electron, throws when set to true on Node.js)
  useElectronNet: boolean
  // (/!\ only works when running on Electron)
  useSessionCookies?: boolean

  ////////////////////////////////////////////////////////////////////////////
  // Body impl
  readonly bodyUsed: boolean

  arrayBuffer (): Promise<ArrayBuffer>

  blob (): Promise<Blob>

  formData (): Promise<FormData>

  json<T = any> (): Promise<T>

  text (): Promise<string>

  buffer (): Promise<Buffer>

  readonly body: Readable
}
