<template>
    <div :class="sliderClass">
        <div :class="sliderTrackerClass"
             @click="handleClick">
            <div :class="sliderTrackerBarClass">
                <div :class="sliderTrackerBarInnerClass"
                     :style="innerStyle"></div>
            </div>
        </div>
        <div v-if="showLabel"
             :class="sliderLabelClass">
            <div v-for="(item) in labels"
                 :key="item.val"
                 class="label-item"
                 :style="{left: `${item.offset}px`}"
                 @click="handleLabelClick(item.val)">
                <slot :value="item.val">
                    <p class="value">{{item.val}}</p>
                </slot>
            </div>
        </div>
        <div ref="thumb"
             :class="sliderThumbClass"
             :style="thumbStyle"
             @mousedown="handleMouseDown"
             @mouseenter="handleMouseEnter"
             @mouseleave="handleMouseLeave"></div>
    </div>
</template>

<script>
import { prefix, on, off } from 'jdd-design/utils';
export default {
    name: `${prefix}-slider`,
    props: {
        max: {
            type: Number,
            default: 100
        },
        min: {
            type: Number,
            default: 0
        },
        value: Number,
        showTip: Boolean,
        disabled: Boolean,
        showLabel: Boolean,
        formatFunc: {
            type: Function,
            default: val => {
                return Number(Number.prototype.toFixed.call(val, 2));
            }
        },
        thumbSize: {
            type: Number,
            default: 16
        },
        trackerWidth: {
            type: Number,
            default: 4
        },
        steps: {
            type: Number
        }
    },
    data() {
        return {
            dragging: false,
            hover: false,
            isClick: false,
            startX: 0,
            currentX: 0,
            startPosition: 0,
            currentPosition: 0,
            currentValue: 0,
            width: 1,
            innerStyle: {},
            thumbStyle: {},
            labels: []
        };
    },
    watch: {
        currentValue: function(val) {
            this.setStyle();
            this.$emit('input', this.formatFunc(val));
        },
        value: function(val) {
            this.currentValue = val || 0;
            this.currentPosition =
                ((this.currentValue - this.min) / (this.max - this.min)) * 100;
            this.setStyle();
        }
    },
    computed: {
        sliderClass() {
            let clsNames = [`${prefix}-slider`];
            return clsNames;
        },
        sliderLabelClass() {
            let clsNames = [`${prefix}-slider-label`];
            return clsNames;
        },
        sliderThumbClass() {
            let clsNames = [`${prefix}-slider-thumb`];
            this.hover && clsNames.push('is-hover');
            this.dragging && clsNames.push('is-dragging');
            return clsNames;
        },
        sliderTrackerClass() {
            let clsNames = [`${prefix}-slider-tracker`];
            return clsNames;
        },
        sliderTrackerBarClass() {
            let clsNames = [`${prefix}-slider-tracker-bar`];
            return clsNames;
        },
        sliderTrackerBarInnerClass() {
            let clsNames = [`${prefix}-slider-tracker-bar__inner`];
            return clsNames;
        }
    },
    methods: {
        handleLabelClick(val) {
            this.currentValue = val;
        },
        handleClick(e) {
            this.currentX = e.clientX;
            let diff = ((this.currentX - this.startX) / this.width) * 100;

            let newPosition = this.startPosition + diff;
            this.setPosition(newPosition);
        },
        handleMouseEnter() {
            this.hover = true;
        },
        handleMouseLeave() {
            this.hover = false;
        },
        handleMouseDown(e) {
            if (this.disabled) return;
            e.preventDefault();
            this.dragging = true;
            this.isClick = true;
            this.startX = e.clientX;
            this.startPosition = this.currentPosition;
            on(document, 'mousemove', this.handleMouseMove);
            on(document, 'mouseup', this.handleMouseUp);
            on(document, 'contextmenu', this.handleMouseUp);
        },
        handleMouseMove(e) {
            if (this.dragging) {
                this.isClick = false;
                this.currentX = e.clientX;
                let diff = ((this.currentX - this.startX) / this.width) * 100;

                let newPosition = this.startPosition + diff;
                this.setPosition(newPosition);
            }
        },
        handleMouseUp(e) {
            if (this.dragging) {
                /*
                 * 防止在 mouseup 后立即触发 click，导致滑块有几率产生一小段位移
                 * 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
                 */
                this.currentX = e.clientX;
                let diff = ((this.currentX - this.startX) / this.width) * 100;
                let newPosition = this.startPosition + diff;
                setTimeout(() => {
                    this.dragging = false;
                    if (!this.isClick) {
                        this.setPosition(newPosition);
                    }
                }, 0);
                off(document, 'mousemove', this.handleMouseMove);
                off(document, 'mouseup', this.handleMouseUp);
                off(document, 'contextmenu', this.handleMouseUp);
            }
        },
        setPosition(newPosition) {
            if (newPosition === null || isNaN(newPosition)) return;
            if (newPosition < 0) {
                newPosition = 0;
            } else if (newPosition > 100) {
                newPosition = 100;
            }
            this.currentValue =
                (newPosition * (this.max - this.min)) / 100 + this.min;
        },
        setStyle() {
            this.width = this.$el.clientWidth;
            this.currentPosition =
                ((this.currentValue - this.min) / (this.max - this.min)) * 100;
            this.innerStyle = {
                width: `${this.currentPosition}%`
            };
            this.thumbStyle = {
                width: `${this.thumbSize}px`,
                height: `${this.thumbSize}px`,
                left: `${(this.currentPosition / 100) * this.width -
                    this.thumbSize / 2}px`
            };
        }
    },
    mounted() {
        this.currentValue = this.value || 0;
        this.$nextTick(() => {
            this.startX =
                this.$refs.thumb.getBoundingClientRect().left +
                this.thumbSize / 2;
            if (this.steps) {
                let space = Math.ceil((this.max - this.min) / this.steps);
                let val = this.min;
                let arr = [];
                for (var i = 0; i < this.steps; i++) {
                    let pos = ((val - this.min) / (this.max - this.min)) * 100;
                    let offset = (pos / 100) * this.width;
                    arr[i] = {
                        val,
                        pos,
                        offset
                    };
                    val += space;
                }
                arr.push({
                    val: this.max,
                    pos: 100,
                    offset: this.width
                });

                this.labels = arr;
            }
        });
        on(window, 'resize', this.setStyle);
    },
    beforeDestroy() {
        off(window, 'resize', this.setStyle);
    }
};
</script>

<style>
</style>
