/**
 * A directive that helps with auto scrolling when the mouse pointer reaches the
 * edge of the element.
 *
 * v-auto-scroll="{
 *   // Indicates whether we should scroll vertically or horizontally by providing
 *   // `vertical` or `horizontal`.
 *   orientation: 'vertical'
 *   // Dynamic indication if the auto scrolling is enabled. This is typically used
 *   // we should only auto scroll if dragging a certain element.
 *   enabled: () => true,
 *   // The speed that should be scrolled with.
 *   speed: 3,
 *   // Indicates the percentage of the edges that should trigger the auto
 *   // scrolling. If 10 is provided, then the auto scrolling starts when the mouse
 *   // pointer is in 10% of the edge of the element.
 *   padding: 10,
 *   // The element that should be scrolled in. By default this is the element
 *   // that's binding by the directive, but it can optionally be changed.
 *   scrollElement: () => DOMElement
 * }"
 */
export default {
  bind(el, binding) {
    binding.def.update(el, binding)

    el.autoScrollTimeout = null
    el.autoScrollLastMoveEvent = null

    const autoscrollLoop = () => {
      if (!el.autoScrollConfig.enabled()) {
        clearTimeout(el.autoScrollTimeout)
        el.autoScrollTimeout = null
        return
      }

      const scrollElement = el.autoScrollConfig.scrollElement()
      const rect = scrollElement.getBoundingClientRect()
      let size
      let autoScrollMouseStart

      if (el.autoScrollConfig.orientation === 'horizontal') {
        size = rect.right - rect.left
        autoScrollMouseStart = el.autoScrollLastMoveEvent.clientX - rect.left
      } else {
        size = rect.bottom - rect.top
        autoScrollMouseStart = el.autoScrollLastMoveEvent.clientY - rect.top
      }

      const autoScrollMouseEnd = size - autoScrollMouseStart
      const side = Math.ceil((size / 100) * el.autoScrollConfig.padding)

      let speed = 0
      if (autoScrollMouseStart < side) {
        speed = -(
          el.autoScrollConfig.speed -
          Math.ceil(
            (Math.max(0, autoScrollMouseStart) / side) *
              el.autoScrollConfig.speed
          )
        )
      } else if (autoScrollMouseEnd < side) {
        speed =
          el.autoScrollConfig.speed -
          Math.ceil(
            (Math.max(0, autoScrollMouseEnd) / side) * el.autoScrollConfig.speed
          )
      }

      // If the speed is either a positive or negative, so not 0, we know that we
      // need to start auto scrolling.
      if (speed !== 0) {
        // Only update the element if the `onScroll` functions returns `true`. This
        // is because in some cases, scrolling is handled in another way. This is
        // for example the case with the `GridView`.
        if (el.autoScrollConfig.onScroll(speed)) {
          if (el.autoScrollConfig.orientation === 'horizontal') {
            scrollElement.scrollLeft += speed
          } else {
            scrollElement.scrollTop += speed
          }
        }
        el.autoScrollTimeout = setTimeout(() => {
          autoscrollLoop()
        }, 2)
      } else {
        clearTimeout(el.autoScrollTimeout)
        el.autoScrollTimeout = null
      }
    }
    el.autoScrollMouseMoveEvent = (event) => {
      el.autoScrollLastMoveEvent = event

      if (el.autoScrollTimeout === null) {
        autoscrollLoop()
      }
    }
    el.addEventListener('mousemove', el.autoScrollMouseMoveEvent)
  },
  update(el, binding) {
    const defaultEnabled = () => true
    const defaultScrollElement = () => el
    const defaultOnScroll = () => true
    el.autoScrollConfig = {
      orientation: binding.value.orientation || 'vertical',
      enabled: binding.value.enabled || defaultEnabled,
      speed: binding.value.speed || 3,
      padding: binding.value.padding || 10,
      scrollElement: binding.value.scrollElement || defaultScrollElement,
      onScroll: binding.value.onScroll || defaultOnScroll,
    }
  },
  unbind(el) {
    el.removeEventListener('mousemove', el.autoScrollMouseMoveEvent)
  },
}