/* global MouseEvent */

import { default as sweetAlert } from '../sweetalert2.js'
import { swalPrefix, swalClasses } from './classes.js'
import { sweetContainer } from './default.js'

// Remember state in cases where opening and handling a modal will fiddle with it.
export const states = {
  previousWindowKeyDown: null,
  previousActiveElement: null,
  previousBodyPadding: null
}

/*
 * Add modal + overlay to DOM
 */
export const init = () => {
  if (typeof document === 'undefined') {
    console.error('SweetAlert2 requires document to initialize')
    return
  } else if (document.getElementsByClassName(swalClasses.container).length) {
    return
  }

  document.body.appendChild(sweetContainer)

  const modal = getModal()
  const input = getChildByClass(modal, swalClasses.input)
  const file = getChildByClass(modal, swalClasses.file)
  const range = modal.querySelector(`.${swalClasses.range} input`)
  const rangeOutput = modal.querySelector(`.${swalClasses.range} output`)
  const select = getChildByClass(modal, swalClasses.select)
  const checkbox = modal.querySelector(`.${swalClasses.checkbox} input`)
  const textarea = getChildByClass(modal, swalClasses.textarea)

  input.oninput = () => {
    sweetAlert.resetValidationError()
  }

  input.onkeydown = (event) => {
    setTimeout(() => {
      if (event.keyCode === 13) {
        event.stopPropagation()
        sweetAlert.clickConfirm()
      }
    }, 0)
  }

  file.onchange = () => {
    sweetAlert.resetValidationError()
  }

  range.oninput = () => {
    sweetAlert.resetValidationError()
    rangeOutput.value = range.value
  }

  range.onchange = () => {
    sweetAlert.resetValidationError()
    range.previousSibling.value = range.value
  }

  select.onchange = () => {
    sweetAlert.resetValidationError()
  }

  checkbox.onchange = () => {
    sweetAlert.resetValidationError()
  }

  textarea.oninput = () => {
    sweetAlert.resetValidationError()
  }

  return modal
}

/*
 * Manipulate DOM
 */
export const elementByClass = (className) => sweetContainer.querySelector('.' + className)

export const getModal = () => document.body.querySelector('.' + swalClasses.modal) || init()

export const getIcons = () => {
  const modal = getModal()
  return modal.querySelectorAll('.' + swalClasses.icon)
}

export const getTitle = () => elementByClass(swalClasses.title)

export const getContent = () => elementByClass(swalClasses.content)

export const getImage = () => elementByClass(swalClasses.image)

export const getSpacer = () => elementByClass(swalClasses.spacer)

export const getProgressSteps = () => elementByClass(swalClasses.progresssteps)

export const getValidationError = () => elementByClass(swalClasses.validationerror)

export const getConfirmButton = () => elementByClass(swalClasses.confirm)

export const getCancelButton = () => elementByClass(swalClasses.cancel)

export const getCloseButton = () => elementByClass(swalClasses.close)

export const getFocusableElements = (focusCancel) => {
  const buttons = [getConfirmButton(), getCancelButton()]
  if (focusCancel) {
    buttons.reverse()
  }
  return buttons.concat(Array.prototype.slice.call(
    getModal().querySelectorAll('button:not([class^=' + swalPrefix + ']), input:not([type=hidden]), textarea, select')
  ))
}

export const hasClass = (elem, className) => {
  if (elem.classList) {
    return elem.classList.contains(className)
  }
  return false
}

export const focusInput = (input) => {
  input.focus()

  // place cursor at end of text in text input
  if (input.type !== 'file') {
    // http://stackoverflow.com/a/2345915/1331425
    const val = input.value
    input.value = ''
    input.value = val
  }
}

export const addClass = (elem, className) => {
  if (!elem || !className) {
    return
  }
  const classes = className.split(/\s+/).filter(Boolean)
  classes.forEach((className) => {
    elem.classList.add(className)
  })
}

export const removeClass = (elem, className) => {
  if (!elem || !className) {
    return
  }
  const classes = className.split(/\s+/).filter(Boolean)
  classes.forEach((className) => {
    elem.classList.remove(className)
  })
}

export const getChildByClass = (elem, className) => {
  for (let i = 0; i < elem.childNodes.length; i++) {
    if (hasClass(elem.childNodes[i], className)) {
      return elem.childNodes[i]
    }
  }
}

export const show = (elem, display) => {
  if (!display) {
    display = 'block'
  }
  elem.style.opacity = ''
  elem.style.display = display
}

export const hide = (elem) => {
  elem.style.opacity = ''
  elem.style.display = 'none'
}

export const empty = (elem) => {
  while (elem.firstChild) {
    elem.removeChild(elem.firstChild)
  }
}

// borrowed from jqeury $(elem).is(':visible') implementation
export const isVisible = (elem) => elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length

export const removeStyleProperty = (elem, property) => {
  if (elem.style.removeProperty) {
    elem.style.removeProperty(property)
  } else {
    elem.style.removeAttribute(property)
  }
}

export const getTopMargin = (elem) => {
  const elemDisplay = elem.style.display
  elem.style.left = '-9999px'
  elem.style.display = 'block'

  const height = elem.clientHeight

  elem.style.left = ''
  elem.style.display = elemDisplay
  return ('-' + parseInt(height / 2, 10) + 'px')
}

export const fadeIn = (elem, interval) => {
  if (+elem.style.opacity < 1) {
    interval = interval || 16
    elem.style.opacity = 0
    elem.style.display = 'block'
    let last = +new Date()
    const tick = () => {
      const newOpacity = +elem.style.opacity + (new Date() - last) / 100
      elem.style.opacity = (newOpacity > 1) ? 1 : newOpacity
      last = +new Date()

      if (+elem.style.opacity < 1) {
        setTimeout(tick, interval)
      }
    }
    tick()
  }
}

export const fadeOut = (elem, interval) => {
  if (+elem.style.opacity > 0) {
    interval = interval || 16
    const opacity = elem.style.opacity
    let last = +new Date()
    const tick = () => {
      const change = new Date() - last
      const newOpacity = +elem.style.opacity - change / (opacity * 100)
      elem.style.opacity = newOpacity
      last = +new Date()

      if (+elem.style.opacity > 0) {
        setTimeout(tick, interval)
      } else {
        hide(elem)
      }
    }
    tick()
  }
}

export const fireClick = (node) => {
  // Taken from http://www.nonobtrusive.com/2011/11/29/programatically-fire-crossbrowser-click-event-with-javascript/
  // Then fixed for today's Chrome browser.
  if (typeof MouseEvent === 'function') {
    // Up-to-date approach
    const mevt = new MouseEvent('click', {
      view: window,
      bubbles: false,
      cancelable: true
    })
    node.dispatchEvent(mevt)
  } else if (document.createEvent) {
    // Fallback
    const evt = document.createEvent('MouseEvents')
    evt.initEvent('click', false, false)
    node.dispatchEvent(evt)
  } else if (document.createEventObject) {
    node.fireEvent('onclick')
  } else if (typeof node.onclick === 'function') {
    node.onclick()
  }
}

export const animationEndEvent = (() => {
  const testEl = document.createElement('div')
  const transEndEventNames = {
    'WebkitAnimation': 'webkitAnimationEnd',
    'OAnimation': 'oAnimationEnd oanimationend',
    'msAnimation': 'MSAnimationEnd',
    'animation': 'animationend'
  }
  for (const i in transEndEventNames) {
    if (transEndEventNames.hasOwnProperty(i) &&
      testEl.style[i] !== undefined) {
      return transEndEventNames[i]
    }
  }

  return false
})()

// Reset the page to its previous state
export const resetPrevState = () => {
  const modal = getModal()
  window.onkeydown = states.previousWindowKeyDown
  if (states.previousActiveElement && states.previousActiveElement.focus) {
    states.previousActiveElement.focus()
  }
  clearTimeout(modal.timeout)
}

// Measure width of scrollbar
// https://github.com/twbs/bootstrap/blob/master/js/modal.js#L279-L286
export const measureScrollbar = () => {
  const scrollDiv = document.createElement('div')
  scrollDiv.style.width = '50px'
  scrollDiv.style.height = '50px'
  scrollDiv.style.overflow = 'scroll'
  document.body.appendChild(scrollDiv)
  const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
  document.body.removeChild(scrollDiv)
  return scrollbarWidth
}

// JavaScript Debounce Function
// Simplivied version of https://davidwalsh.name/javascript-debounce-function
export const debounce = (func, wait) => {
  let timeout
  return () => {
    const later = () => {
      timeout = null
      func()
    }
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
  }
}
