/**
 * source by `component-classes`
 * https://github.com/component/classes.git
 */
import indexOf from 'lodash-es/indexOf'

/**
 * Whitespace regexp.
 */
const re = /\s+/

export class ClassList {
  el: Element
  list: DOMTokenList

  constructor(el: Element) {
    if (!el || !el.nodeType) {
      throw new Error('A DOM element reference is required')
    }
    this.el = el
    this.list = el.classList
  }

  array() {
    const className = this.el.getAttribute('class') || ''
    const str = className.replace(/^\s+|\s+$/g, '')
    const arr = str.split(re)
    if (arr[0] === '') arr.shift()
    return arr
  }

  /**
   * Add class `name` if not already present.
   *
   * @param {string} name
   * @return {ClassList}
   * @api public
   */
  add(name: string): ClassList {
    // classList
    if (this.list) {
      this.list.add(name)
      return this
    }

    // fallback
    const arr = this.array()
    const i = indexOf(arr, name)
    if (!~i) arr.push(name)
    this.el.className = arr.join(' ')
    return this
  }
  /**
   * Remove class `name` when present, or
   * pass a regular expression to remove
   * any which match.
   *
   * @param {string | RegExp} name
   * @return {ClassList}
   * @api public
   */
  remove(name: string | RegExp): ClassList {
    if (toString.call(name) === '[object RegExp]') {
      return this._removeMatching(name as RegExp)
    }

    // classList
    if (this.list) {
      this.list.remove(name as string)
      return this
    }

    // fallback
    const arr = this.array()
    const i = indexOf(arr, name)
    if (~i) arr.splice(i, 1)
    this.el.className = arr.join(' ')
    return this
  }
  /**
   * Remove all classes matching `re`.
   *
   * @param {RegExp} re
   * @return {ClassList}
   * @api private
   */
  _removeMatching(re: RegExp): ClassList {
    const arr = this.array()
    for (let i = 0; i < arr.length; i++) {
      if (re.test(arr[i])) {
        this.remove(arr[i])
      }
    }
    return this
  }

  /**
   * Toggle class `name`, can force state via `force`.
   *
   * For browsers that support classList, but do not support `force` yet,
   * the mistake will be detected and corrected.
   *
   * @param {string} name
   * @param {boolean} force
   * @return {ClassList}
   * @api public
   */
  toggle(name: string, force: boolean): ClassList {
    // classList
    if (this.list) {
      if (typeof force !== 'undefined') {
        if (force !== this.list.toggle(name, force)) {
          this.list.toggle(name) // toggle again to correct
        }
      } else {
        this.list.toggle(name)
      }
      return this
    }

    // fallback
    if (typeof force !== 'undefined') {
      if (!force) {
        this.remove(name)
      } else {
        this.add(name)
      }
    } else {
      if (this.has(name)) {
        this.remove(name)
      } else {
        this.add(name)
      }
    }

    return this
  }
  /**
   * Check if class `name` is present.
   *
   * @param {string} name
   * @api public
   */
  has(name: string) {
    return this.list ? this.list.contains(name) : !!~indexOf(this.array(), name)
  }
  /**
   * Check if class `name` is present.
   *
   * @param {string} name
   * @api public
   */
  contains(name: string) {
    return this.has(name)
  }
}

/**
 * Wrap `el` in a `ClassList`.
 *
 * @param {Element} el
 * @return {ClassList}
 * @api public
 */
export default function (el: Element): ClassList {
  return new ClassList(el)
}
