import { createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
import { defineComponent, getCurrentInstance, reactive, ref, computed, watch } from 'vue';
import { findDOMNode } from '../_util/props-util';
import * as utils from './utils';
import Marks from './Marks';
import Hanlde from './Hanlde';
const prefixCls = 'x-slider';
const Slider = defineComponent({
  name: prefixCls,
  props: {
    value: [Number, Array],
    disabled: Boolean,
    step: {
      default: 1
    },
    min: {
      default: 0
    },
    max: {
      default: 100
    },
    range: Boolean,
    marks: Object,
    formatTooltip: Function,
    tooltip: {
      default: true
    }
  },
  emits: ['update:value', 'change'],

  setup(props, {
    attrs,
    emit
  }) {
    const {
      proxy: self
    } = getCurrentInstance();
    const sliderRef = ref();

    const _value = computed(() => Array.isArray(props.value) ? props.value : [props.value || 0]);

    const state = reactive({
      value: _value.value
    });
    watch(_value, val => {
      if (val.toString() === state.value.toString()) return;
      state.value = val;
    }); // ========================== Calc ==========================

    function getSliderLength() {
      if (!sliderRef.value) return 0;
      return sliderRef.value.getBoundingClientRect().width;
    }

    function calcValue(offset) {
      const ratio = offset / getSliderLength();
      let v = props.min + (props.max - props.min) * ratio;
      return utils.ensureValueInRange(v, props);
    }

    function calcValueByOffset(offset) {
      return trimAlignValue(calcValue(offset));
    }

    function getClosestHandle(val) {
      const arr = state.value;
      const i = arr.findIndex(e => val < e) - 1;
      if (i == -1) return 0;
      if (i == -2) return arr.length - 1;
      const a = val - arr[i],
            b = arr[i + 1] - val;
      if (a === b) return currHandle;
      return a < b ? i : i + 1;
    }

    function trimAlignValue(val) {
      val = utils.ensureValueInRange(val, props);
      return utils.ensureValuePrecision(val, props);
    } // ========================== Node ==========================


    const handleRefs = [];

    function saveHanels(vm) {
      handleRefs.push(findDOMNode(vm));
    }

    function getHanleNode() {
      return state.value.map(e => {
        const {
          min,
          max,
          disabled,
          formatTooltip,
          tooltip
        } = props;
        return _createVNode(Hanlde, _mergeProps({
          "value": e,
          "ref": saveHanels
        }, {
          prefixCls,
          min,
          max,
          disabled,
          formatTooltip,
          tooltip
        }, {
          "onMousedown": onMousedown,
          "onMouseup": onMouseup
        }), null);
      });
    }

    function getBarNode() {
      const {
        min,
        max
      } = props,
            len = max - min;
      let style = {};

      if (props.range) {
        style.left = `${(state.value[0] - min) / len * 100}%`;
        style.right = `${100 - (state.value[1] - min) / len * 100}%`;
      } else {
        style.left = `0`;
        style.right = `${100 - (state.value[0] - min) / len * 100}%`;
      }

      const barProps = {
        class: [`${prefixCls}_bar`],
        style
      };
      return _createVNode("div", barProps, null);
    } // ========================== Event ==========================


    let startPos, startOffset, dragOffset, currHandle;

    function onClickLabel(e, value) {
      const handle = getClosestHandle(value);
      state.value[handle] = value;
      onChange(state.value);
    }

    function onMousedown(e) {
      e.stopPropagation();
      e.preventDefault();
      onMouseup();
      const target = e.target;
      currHandle = handleRefs.findIndex(e => e === target);

      if (currHandle > -1) {
        dragOffset = e.offsetX + target.offsetWidth / 2;
        startOffset = target.offsetLeft;
        target.focus();
      } else {
        dragOffset = 0;
        startOffset = e.pageX - sliderRef.value.getBoundingClientRect().left;
        const val = calcValueByOffset(startOffset);
        currHandle = getClosestHandle(val);
        state.value[currHandle] = val;
        onChange(state.value);
        handleRefs[currHandle].focus();
      }

      startPos = e.pageX;
      window.addEventListener('mousemove', onMousemove);
      window.addEventListener('mouseup', onMouseup);
    }

    function onMousemove(e) {
      e.stopPropagation();
      e.preventDefault();
      const offset = e.pageX - startPos + startOffset,
            i = currHandle;
      let v = calcValueByOffset(offset);

      if (v < state.value[currHandle - 1]) {
        [state.value[currHandle - 1], state.value[currHandle]] = [v, state.value[currHandle - 1]];
        currHandle--;
      } else if (v > state.value[currHandle + 1]) {
        [state.value[currHandle + 1], state.value[currHandle]] = [v, state.value[currHandle + 1]];
        currHandle++;
      } else {
        state.value[currHandle] = v;
      }

      onChange(state.value);

      if (i != currHandle) {
        // toogle handle
        Object.defineProperty(e, 'target', {
          writable: true
        }); // @ts-ignore

        e.target = handleRefs[currHandle];
        onMousedown(e);
      }
    }

    function onMouseup() {
      handleRefs.forEach(e => e.blur());
      window.removeEventListener('mousemove', onMousemove);
      window.removeEventListener('mouseup', onMouseup);
    }

    function onChange(value) {
      value = props.range ? value : value[0];
      emit('update:value', value);
      emit('change', value);
    } // ========================== Render ==========================


    return () => {
      const sliderProps = {
        ref: sliderRef,
        class: [prefixCls, {
          [`${prefixCls}-marks`]: !!props.marks,
          [`${prefixCls}-disabled`]: props.disabled
        }],
        onMousedown
      };
      return _createVNode("div", sliderProps, [getBarNode(), _createVNode(Marks, _mergeProps({
        "prefixCls": prefixCls
      }, props, {
        "onClickLabel": onClickLabel
      }), null), getHanleNode()]);
    };
  }

});

Slider.install = app => {
  app.component(Slider.name, Slider);
};

export default Slider;