import { Body, BodyInit, cloneBody, extractContentType } from './body'
import { NextURL } from '../next-url'
import { validateURL } from '../utils'

const INTERNALS = Symbol('internal response')
const REDIRECTS = new Set([301, 302, 303, 307, 308])

class BaseResponse extends Body implements Response {
  [INTERNALS]: {
    headers: Headers
    status: number
    statusText: string
    type: 'default' | 'error'
    url?: NextURL
  }

  constructor(body?: BodyInit | null, init?: ResponseInit) {
    super(body)

    this[INTERNALS] = {
      headers: new Headers(init?.headers),
      status: init?.status || 200,
      statusText: init?.statusText || '',
      type: 'default',
      url: init?.url ? new NextURL(init.url) : undefined,
    }

    if (this[INTERNALS].status < 200 || this[INTERNALS].status > 599) {
      throw new RangeError(
        `Responses may only be constructed with status codes in the range 200 to 599, inclusive.`
      )
    }

    if (body !== null && !this[INTERNALS].headers.has('Content-Type')) {
      const contentType = extractContentType(this)
      if (contentType) {
        this[INTERNALS].headers.append('Content-Type', contentType)
      }
    }
  }

  static redirect(url: string, status = 307) {
    if (!REDIRECTS.has(status)) {
      throw new RangeError(
        'Failed to execute "redirect" on "response": Invalid status code'
      )
    }

    return new Response(validateURL(url), {
      headers: { Location: url },
      status,
    })
  }

  static error() {
    const response = new BaseResponse(null, { status: 0, statusText: '' })
    response[INTERNALS].type = 'error'
    return response
  }

  get url() {
    return this[INTERNALS].url?.toString() || ''
  }

  get ok() {
    return this[INTERNALS].status >= 200 && this[INTERNALS].status < 300
  }

  get status() {
    return this[INTERNALS].status
  }

  get statusText() {
    return this[INTERNALS].statusText
  }

  get headers() {
    return this[INTERNALS].headers
  }

  get redirected() {
    return (
      this[INTERNALS].status > 299 &&
      this[INTERNALS].status < 400 &&
      this[INTERNALS].headers.has('Location')
    )
  }

  get type() {
    return this[INTERNALS].type
  }

  clone() {
    return new BaseResponse(cloneBody(this), {
      headers: this.headers,
      status: this.status,
      statusText: this.statusText,
      url: this.url,
    })
  }

  get [Symbol.toStringTag]() {
    return 'Response'
  }
}

export interface ResponseInit {
  headers?: HeadersInit
  status?: number
  statusText?: string
  url?: string
}

export { BaseResponse as Response }
