<template>
  <div @mousemove="onMouseMove" @touchmove="onTouchMove">
    <!-- @slot Content to scroll -->
    <slot />
  </div>
</template>
<script>
const THRESHOLD_MIN = 40;
const THRESHOLD_MAX = 100;
const THROTTLE_INDEX = 5;

/**
 * @description
 * Automatically scroll window or closest parent element
 * taken by selector from `parent-selector` prop
 * when mouse cursor/touch is close to its borders.
 */
export default {
  name: 'itfAutoScroll',
  props: {
    /**
       * Auto scroll disabled if set to true
       */
    disabled: {
      type: Boolean,
      default: false
    },
    /**
       * Selector for parent which is scrolled. By default it is window.
       * Can be selected by tag name, class name, element id.
       */
    parentSelector: {
      type: String,
      default: null
    }
  },
  data () {
    return {
      parent: null
    };
  },
  watch: {
    parentSelector () {
      this.resolveParentSelector();
    }
  },
  mounted () {
    this.resolveParentSelector();
  },
  methods: {
    /**
       * Scroll parent element/window accordingly to received pageX, pageY coordinates
       *
       * @param {number} pageX PageX coordinate
       * @param {number} pageY PageY coordinate
       * @param {boolean} gradually Scroll gradually. True by default
       * @public
       */
    autoScroll (pageX, pageY, gradually = true) {
      this.performAutoScroll(pageX, pageY, gradually);
    },
    /**
       * Find parent by selector. Empty parent means `window` by default.
       */
    resolveParentSelector () {
      this.parent = null;
      if (this.parentSelector && this.parentSelector !== 'window') {
        this.parent = this.$el.closest(this.parentSelector);
      }
    },
    onMouseMove (event) {
      const { pageX, pageY } = event;
      this.performAutoScroll(pageX, pageY, false);
    },
    onTouchMove (event) {
      const { pageX, pageY } = event.targetTouches[0];
      this.performAutoScroll(pageX, pageY, false);
    },
    performAutoScroll (pageX, pageY, gradually = true) {
      if (this.disabled) {
        return;
      }
      let x;
      let y;
      if (this.parent) {
        const rect = this.parent.getBoundingClientRect();
        const scrollLeft = document.documentElement
          ? document.documentElement.scrollLeft
          : document.body.scrollLeft;
        const scrollTop = document.documentElement
          ? document.documentElement.scrollTop
          : document.body.scrollTop;
        x = pageX - (rect.left + scrollLeft);
        y = pageY - (rect.top + scrollTop);
      } else {
        x = pageX - window.pageXOffset;
        y = pageY - window.pageYOffset;
      }
      const width = this.parent ? this.parent.clientWidth : window.innerWidth;
      const height = this.parent
        ? this.parent.clientHeight
        : window.innerHeight;
      const shiftX = this.getShift(width, x, gradually);
      const shiftY = this.getShift(height, y, gradually);
      if (shiftX !== 0 || shiftY !== 0) {
        (this.parent || window).scrollBy(shiftX, shiftY);
        /**
           * Autoscroll event
           * @param {number} shiftX Shift by axis X
           * @param {number} shiftY Shift by axis Y
           */
        this.$emit('autoscroll', shiftX, shiftY);
      }
    },
    /**
       * Calculate shift x/y for scrolling
       * @param {number} size
       * @param {number} position
       * @param {boolean} gradually
       * @returns {number}
       */
    getShift (size, position, gradually) {
      if (THRESHOLD_MAX <= size) {
        let threshold = size / 10;
        threshold = threshold > THRESHOLD_MAX ? THRESHOLD_MAX : threshold;
        threshold = threshold < THRESHOLD_MIN ? THRESHOLD_MIN : threshold;
        const boundTop = threshold;
        const boundBottom = size - threshold;
        if (position < boundTop) {
          return gradually ? -threshold / THROTTLE_INDEX : position - boundTop;
        }
        if (position > boundBottom) {
          return gradually
            ? threshold / THROTTLE_INDEX
            : position - boundBottom;
        }
      }
      return 0;
    }
  }
};
</script>
