module.exports = class URLSearchParams {
  static _urls = new WeakMap()

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = new Map()

    if (url) URLSearchParams._urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else if (init) {
      for (const [name, value] of typeof init[Symbol.iterator] === 'function'
        ? init
        : Object.entries(init)) {
        this.append(name, value)
      }
    }
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    return this._params.length
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = URLSearchParams._urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    if (input[0] === '?') input = input.substring(1)

    this._params = new Map()

    for (const sequence of input.split('&')) {
      if (sequence.length === 0) continue

      let i = sequence.indexOf('=')
      if (i === -1) i = sequence.length

      const name = decodeURIComponent(sequence.substring(0, i))
      const value = decodeURIComponent(sequence.substring(i + 1, sequence.length))

      let list = this._params.get(name)

      if (list === undefined) {
        list = []
        this._params.set(name, list)
      }

      list.push(value)
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let output = ''

    for (let [name, values] of this._params) {
      name = encodeURIComponent(name)

      for (const value of values) {
        if (output) output += '&'

        output += name + '=' + encodeURIComponent(value)
      }
    }

    return output
  }
}
