<template>
  <div
    class="el-slider__button-wrapper"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
    @mousedown="onButtonDown"
    @touchstart="onButtonDown"
    :class="{ hover: hovering, dragging: dragging }"
    :style="wrapperStyle"
    ref="button"
    tabindex="0"
    @focus="handleMouseEnter"
    @blur="handleMouseLeave"
    @keydown.left="onLeftKeyDown"
    @keydown.right="onRightKeyDown"
    @keydown.down.prevent="onLeftKeyDown"
    @keydown.up.prevent="onRightKeyDown">
    <el-tooltip
      placement="top"
      ref="tooltip"
      :popper-class="tooltipClass"
      :disabled="!showTooltip">
      <span slot="content">{{ formatValue }}</span>
      <div
        class="el-slider__button"
        :class="{ hover: hovering, dragging: dragging }"></div>
    </el-tooltip>
  </div>
</template>

<script>
// import ElTooltip from 'element-ui/packages/tooltip'
export default {
  name: 'ElSliderButton',
  components: {
    // ElTooltip,
  },
  props: {
    value: {
      type: Number,
      default: 0,
    },
    vertical: {
      type: Boolean,
      default: false,
    },
    tooltipClass: String,
    marks: Object,
  },
  data() {
    return {
      hovering: false,
      dragging: false,
      isClick: false,
      startX: 0,
      currentX: 0,
      startY: 0,
      currentY: 0,
      startPosition: 0,
      newPosition: null,
      oldValue: this.value,
    }
  },
  computed: {
    disabled() {
      return this.$parent.sliderDisabled
    },
    max() {
      return this.$parent.max
    },
    min() {
      return this.$parent.min
    },
    step() {
      return this.$parent.step
    },
    showTooltip() {
      return this.$parent.showTooltip
    },
    precision() {
      return this.$parent.precision
    },
    currentPosition() {
      if (this.marks) {
        // 同父组件index中barSize的计算逻辑. 直接从那边抄过来的. 不要在这里改, 在index改完再抄过来.
        // 注意父组件的 this.firstValue, 这里是 this.value
        const marks = Object.keys(this.marks).map(Number)
        // 找出目标值位于marks的那个档位, 即 即将到达哪一个'档'
        const index = marks.findIndex(i => this.value < i)

        if (index === -1) return '100%'
        // 经过后一个档位之后占总进度条的比例 + (当前值超出后一个档位的差值 / 前一个档位与后一个档位的差值) / (进度被marks划分成的档位数量)
        // 上述公式要除档位数量是因为, 当前值与档位差值占单个档位区间的百分比 !== 单个区间值占总进度条的百分比
        const value =
          (index - 1) / (marks.length - 1) +
          (this.value - marks[index - 1]) /
            (marks[index] - marks[index - 1]) /
            (marks.length - 1)

        return value > 1 ? '100%' : `${value * 100}%`
      } else {
        // 这里是原本的逻辑. 没有传marks的.
        return `${((this.value - this.min) / (this.max - this.min)) * 100}%`
      }
    },
    enableFormat() {
      return this.$parent.formatTooltip instanceof Function
    },
    formatValue() {
      return (
        (this.enableFormat && this.$parent.formatTooltip(this.value)) ||
        this.value
      )
    },
    wrapperStyle() {
      return this.vertical
        ? { bottom: this.currentPosition }
        : { left: this.currentPosition }
    },
  },
  watch: {
    dragging(val) {
      this.$parent.dragging = val
    },
  },
  methods: {
    displayTooltip() {
      this.$refs.tooltip && (this.$refs.tooltip.showPopper = true)
    },
    hideTooltip() {
      this.$refs.tooltip && (this.$refs.tooltip.showPopper = false)
    },
    handleMouseEnter() {
      this.hovering = true
      this.displayTooltip()
    },
    handleMouseLeave() {
      this.hovering = false
      this.hideTooltip()
    },
    onButtonDown(event) {
      if (this.disabled) return
      event.preventDefault()
      this.onDragStart(event)
      window.addEventListener('mousemove', this.onDragging)
      window.addEventListener('touchmove', this.onDragging)
      window.addEventListener('mouseup', this.onDragEnd)
      window.addEventListener('touchend', this.onDragEnd)
      window.addEventListener('contextmenu', this.onDragEnd)
    },
    onLeftKeyDown() {
      if (this.disabled) return
      this.newPosition =
        parseFloat(this.currentPosition) -
        (this.step / (this.max - this.min)) * 100
      this.setPosition(this.newPosition)
      this.$parent.emitChange()
    },
    onRightKeyDown() {
      if (this.disabled) return
      this.newPosition =
        parseFloat(this.currentPosition) +
        (this.step / (this.max - this.min)) * 100
      this.setPosition(this.newPosition)
      this.$parent.emitChange()
    },
    onDragStart(event) {
      this.dragging = true
      this.isClick = true
      if (event.type === 'touchstart') {
        event.clientY = event.touches[0].clientY
        event.clientX = event.touches[0].clientX
      }
      if (this.vertical) {
        this.startY = event.clientY
      } else {
        this.startX = event.clientX
      }
      this.startPosition = parseFloat(this.currentPosition)
      this.newPosition = this.startPosition
    },
    onDragging(event) {
      if (this.dragging) {
        this.isClick = false
        this.displayTooltip()
        this.$parent.resetSize()
        let diff = 0
        if (event.type === 'touchmove') {
          event.clientY = event.touches[0].clientY
          event.clientX = event.touches[0].clientX
        }
        if (this.vertical) {
          this.currentY = event.clientY
          diff = ((this.startY - this.currentY) / this.$parent.sliderSize) * 100
        } else {
          this.currentX = event.clientX
          diff = ((this.currentX - this.startX) / this.$parent.sliderSize) * 100
        }
        this.newPosition = this.startPosition + diff
        this.setPosition(this.newPosition)
      }
    },
    onDragEnd() {
      if (this.dragging) {
        /*
         * 防止在 mouseup 后立即触发 click，导致滑块有几率产生一小段位移
         * 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
         */
        setTimeout(() => {
          this.dragging = false
          this.hideTooltip()
          if (!this.isClick) {
            this.setPosition(this.newPosition)
            this.$parent.emitChange()
          }
        }, 0)
        window.removeEventListener('mousemove', this.onDragging)
        window.removeEventListener('touchmove', this.onDragging)
        window.removeEventListener('mouseup', this.onDragEnd)
        window.removeEventListener('touchend', this.onDragEnd)
        window.removeEventListener('contextmenu', this.onDragEnd)
      }
    },
    setPosition(newPosition) {
      if (newPosition === null || isNaN(newPosition)) return
      if (newPosition < 0) {
        newPosition = 0
      } else if (newPosition > 100) {
        newPosition = 100
      }
      const lengthPerStep = 100 / ((this.max - this.min) / this.step)
      const steps = Math.round(newPosition / lengthPerStep)
      let value =
        steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min
      if (this.marks) {
        // 处理传递marks时的逻辑. 没有传marks的保持原样.
        // 有marks时无视原计算逻辑. 重新赋值
        const marks = Object.keys(this.marks).map((current, index, arr) => {
          return {
            value: Number(current),
            perc: (100 * index) / (arr.length - 1),
          }
        })

        // 找出目标值位于marks的那个档位, 即 即将到达哪一个'档'
        if (newPosition === 100) {
          value = marks[marks.length - 1].value
        } else {
          const index = marks.findIndex(item => item.perc > newPosition)

          // 后一个区间的代表值 + ((区间数量 * 超出后一个区间的百分比值 / 100) * 所处区间的值差)
          if (index === -1) {
            // 在最后一个区间
            value =
              marks[marks.length - 2].value +
              (marks.length - 1) *
                (((newPosition - marks[marks.length - 2].perc) / 100) *
                  (marks[marks.length - 1].value -
                    marks[marks.length - 2].value))
          } else {
            value =
              marks[index - 1].value +
              (marks.length - 1) *
                (((newPosition - marks[index - 1].perc) / 100) *
                  (marks[index].value - marks[index - 1].value))
          }
        }
      }

      value = parseFloat(value.toFixed(this.precision))
      this.$emit('input', value)
      this.$nextTick(() => {
        this.displayTooltip()
        this.$refs.tooltip && this.$refs.tooltip.updatePopper()
      })
      if (!this.dragging && this.value !== this.oldValue) {
        this.oldValue = this.value
      }
    },
  },
}
</script>
