<!-- webview only --><!-- slot -->
<template>
    <section class="bl___swiper-container" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @touchcancel="handleTouchEnd">
        <section :class="['bl___swiper-wrapper', {vertical}]" ref="wrapper" :style="{
                'transition': `all ${transition}ms`,
                'transform': translateString,
                [`padding-${vertical ? 'top' : 'left'}`]: previousMargin,
                [`padding-${vertical ? 'bottom' : 'right'}`]: nextMargin
            }"
            @transitionend="emitAnimationEnd"
            @webkittransitionend="emitAnimationEnd">
            <div class="bl___swiper-clone" ref="before" v-if="circular"></div>
            <slot></slot>
            <div class="bl___swiper-clone" ref="after" v-if="circular"></div>
        </section>
        <section :class="['bl___indicator-container', {vertical}]" v-if="indicatorDots">
            <section class="bl___indicator-item" v-for="index in pageLength" :key="index" :style="{'background-color': (index - 1) === indicatorIndex ? indicatorActiveColor : indicatorColor}"></section>
        </section>
    </section>
</template>

<script>
let startTime = 0;
let startPosition = { x: 0, y: 0 };
let lastPosition = { x: 0, y: 0 };
let locked = false;
let swipeHorizontal = false;
let circularTimer = null;
let shouldUpdateIndex = false;

/**
 * 计算初始滑动方向
 *
 * @param {Number} nowPos
 * @param {Number} lastPos
 * @returns boolean
 */
function calcIfSwipeHorizontal(nowPos, lastPos) {
    return Math.abs(nowPos.x - lastPos.x) > Math.abs(nowPos.y - lastPos.y)
}

export default {
    name: 'Bili_Swiper',
    props: {
        indicatorDots: {
            type: Boolean,
            default: false
        },
        indicatorColor: {
            type: String,
            default: 'rgba(0, 0, 0, .3)'
        },
        indicatorActiveColor: {
            type: String,
            default: '#000000'
        },
        autoplay: {
            type: Boolean,
            default: false
        },
        current: {
            type: Number,
            default: 0
        },
        currentItemId: {
            type: String,
            default: ""
        },
        interval: {
            type: Number,
            default: 5000
        },
        duration: {
            type: Number,
            default: 500
        },
        circular: {
            type: Boolean,
            default: false
        },
        vertical: {
            type: Boolean,
            default: false
        },
        previousMargin: {
            type: String,
            default: "0px"
        },
        nextMargin: {
            type: String,
            default: "0px"
        },
        displayMultipleItems: {
            type: Number,
            default: 1
        },
        skipHiddenItemLayout: {
            type: Boolean,
            default: false
        },
        canTouch: {
            type: Boolean,
            default: true
        },
        stop: {
            type: Boolean,
            default: false
        },
        stretch: {
            type: Boolean,
            default: true
        }
    },
    data() {
        return {
            pageLength: 0,
            currentIndex: 0,
            itemIdList: [],
            touching: false,
            translate: 0,
            width: 0,
            height: 0,
            autoplayTimeout: -1,
            lastSource: '',
            canTouchEnd: true
        }
    },
    computed: {
        transition() {
            return this.touching ? 0 : this.duration;
        },
        translateString() {
            return `translate${this.vertical ? 'Y' : 'X'}(${this.translate}px)`;
        },
        wrapperSize() {
            if (shouldUpdateIndex) {
                shouldUpdateIndex = false;
                setTimeout(() => {
                    this.setIndex(this.currentIndex, true);
                }, 0)
            }
            return this.vertical ? this.height : this.width;
        },
        indicatorIndex() {
            if (this.circular) {
                if (this.currentIndex === 0) {
                    return this.pageLength - 1;
                }

                if (this.currentIndex === this.pageLength + 1) {
                    return 0;
                }

                return this.currentIndex - 1;
            }

            return this.currentIndex;
        },
        property() {
            return this.vertical ? 'y' : 'x';
        },
        realIndex() {
            return this.circular ? this.currentIndex - 1 : this.currentIndex;
        }
    },
    watch: {
        current: {
            handler() {
                if (this.circular) {
                    this.setIndex(this.current + 1, true);
                } else {
                    this.setIndex(this.current, true);
                }

                this.lastSource = '';

                this.$emit('change', {
                    type: 'change',
                    detail: {
                        current: this.indicatorIndex,
                        currentItemId: this.itemIdList[this.indicatorIndex],
                        source: ''
                    }
                });
            },
            immediate: true
        },
        currentItemId() {
            const index = this.itemIdList.indexOf(this.currentItemId + '');
            if (index < 0) return;

            if (this.circular) {
                this.setIndex(index + 1);
            } else {
                this.setIndex(index);
            }

            this.lastSource = '';

            this.$emit('change', {
                type: 'change',
                detail: {
                    current: this.indicatorIndex,
                    currentItemId: this.itemIdList[this.indicatorIndex],
                    source: ''
                }
            });
        },
        autoplay(value) {
            if (!value) {
                this.stopAutoPlay();
            } else {
                this.startAutoPlay();
            }
        },
    },
    mounted() {
        // 监听slot中子元素变化
        new MutationObserver((mutations) => {
            this.updateChildren(mutations);
        })
            .observe(this.$refs.wrapper, {
                childList: true,
            });

        // 监听自身样式变化
        new MutationObserver((mutations) => {
            this.updateSize(mutations);
            this.translate = -this.currentIndex * this.wrapperSize;
        })
            .observe(this.$el, {
                attributes: true,
            });

        // dom相关操作初始化
        setTimeout(() => {
            this.updateChildren();
            this.updateSize();
            this.translate = -this.currentIndex * this.wrapperSize;

            // 初始化位置
            this.setIndex(this.circular ? this.current + 1 : this.current, true);
            if (this.currentItemId) {
                const index = this.itemIdList.indexOf(this.currentItemId + '');
                if (index >= 0) {
                    this.setIndex(this.circular ? index + 1 : index, true);
                }
            }
        }, 0);

        // 自动播放
        this.autoplay && this.startAutoPlay();

    },
    methods: {
        updateChildren() {
            if (this.$slots.default) {
                const childList = this.$slots.default.filter(item => item.tag && /SwiperItem/.test(item.tag))
                this.pageLength = childList.length;
                this.itemIdList = childList.map(swiperItem => (swiperItem.componentInstance.$props.itemId + ''));
                if (this.circular) {
                    this.$refs.before.innerHTML = '';
                    this.$refs.before.appendChild(childList[childList.length - 1].elm.cloneNode(true));
                    this.$refs.after.innerHTML = '';
                    this.$refs.after.appendChild(childList[0].elm.cloneNode(true));
                }
            } else {
                this.pageLength = 0;
                this.itemIdList = [];
            }
        },
        updateSize() {
            const computed = window.getComputedStyle(this.$refs.wrapper);
            this.width = parseInt(computed.width) - parseInt(computed.paddingLeft) - parseInt(computed.paddingRight);
            this.height = parseInt(computed.height) - parseInt(computed.paddingTop) - parseInt(computed.paddingBottom);
        },
        handleTouchStart(e) {
            if (this.stop) {
                e.stopPropagation();
            }
            if (!this.canTouch) {
                return;
            }
            if (this.vertical && !this.height || !this.vertical && !this.width) {
                this.updateSize();
            }
            if (circularTimer) {
                clearTimeout(circularTimer);
            }

            this.autoplay && this.stopAutoPlay();
            this.touching = true;
            startTime = Date.now();
            startPosition = {
                x: e.touches[0].clientX,
                y: e.touches[0].clientY,
            };
            lastPosition = {
                x: e.touches[0].clientX,
                y: e.touches[0].clientY,
            }

            if (this.circular) {
                if (this.currentIndex === 0) {
                    this.currentIndex = this.pageLength;
                    this.translate = -this.wrapperSize * this.pageLength;
                }
                else if (this.currentIndex === this.pageLength + 1) {
                    this.currentIndex = 1;
                    this.translate = -this.wrapperSize;
                }
            }
        },
        handleTouchMove(e) {
            if (this.stop) {
                e.stopPropagation();
            }
            if (!this.canTouch) {
                return;
            }

            const currentPosition = {
                x: e.touches[0].clientX,
                y: e.touches[0].clientY,
            };

            if (!locked) {
                swipeHorizontal = calcIfSwipeHorizontal(currentPosition, lastPosition);
                locked = true;
            }

            if (locked && swipeHorizontal === this.vertical) {
                return;
            }

            e.preventDefault();
            
            let swipeRate = !this.circular && (this.translate > 0 || this.translate < -this.wrapperSize * (this.pageLength - 1)) ? 0.3 : 1;
            let move = currentPosition[this.property] - lastPosition[this.property];

            if (!this.stretch && !this.circular && (this.currentIndex === 0 && move > 0 || this.currentIndex === this.pageLength - 1 && move < 0)) {
                this.canTouchEnd = false;
            } else {
                this.translate += move * swipeRate;
                this.canTouchEnd = true;
            }

            lastPosition = currentPosition;
        },
        handleTouchEnd(e) {
            if (this.stop) {
                e.stopPropagation();
            }
            if (!this.canTouch || !this.canTouchEnd) {
                return;
            }
            let circularTranslate = '';
            if (Date.now() - startTime < 300) {
                let duration = lastPosition[this.property] - startPosition[this.property];
                const normalize = Math.abs(duration) > 20 ? duration / Math.abs(duration) : 0;

                if (normalize !== 0) {
                    const preResult = this.currentIndex - normalize;

                    if (this.circular) {
                        if (this.translate > -this.wrapperSize) {
                            // 滑动到最左边的情况
                            circularTranslate = 0;
                        } else if (this.translate < -this.wrapperSize * this.pageLength) {
                            // 滑动到最右边的情况
                            circularTranslate = -this.wrapperSize * (this.pageLength + 1);
                        }

                        this.currentIndex = preResult;
                    } else {
                        this.currentIndex = Math.max(0, Math.min(this.pageLength - 1, preResult));
                    }

                    this.$emit('change', {
                        type: 'change',
                        detail: {
                            current: this.indicatorIndex,
                            currentItemId: this.itemIdList[this.indicatorIndex],
                            source: 'touch'
                        }
                    });
                }
            } else {
                if (Math.min(this.pageLength, Math.max(1, -Math.round(this.translate / this.wrapperSize))) !== this.currentIndex) {
                    if (this.circular) {
                        if (this.translate > -this.wrapperSize) {
                            // 滑动到最左边的情况
                            circularTranslate = 0;
                        } else if (this.translate < -this.wrapperSize * this.pageLength) {
                            // 滑动到最右边的情况
                            circularTranslate = -this.wrapperSize * (this.pageLength + 1);
                        }

                        this.currentIndex = Math.min(this.pageLength, Math.max(1, -Math.round(this.translate / this.wrapperSize)));
                    } else {
                        this.currentIndex = Math.max(0, Math.min(this.pageLength - 1, -Math.round(this.translate / this.wrapperSize)));
                    }

                    this.$emit('change', {
                        type: 'change',
                        detail: {
                            current: this.indicatorIndex,
                            currentItemId: this.itemIdList[this.indicatorIndex],
                            source: 'touch'
                        }
                    });
                }
            }

            setTimeout(() => {
                this.touching = false;
                this.translate = circularTranslate !== '' ? circularTranslate : -this.currentIndex * this.wrapperSize;
                locked = false;
            }, 0);

            this.autoplay && this.startAutoPlay();

            this.lastSource = 'touch';
        },
        emitAnimationEnd() {
            this.$emit('animationfinish', {
                type: 'animationfinish',
                detail: {
                    current: this.indicatorIndex,
                    currentItemId: this.itemIdList[this.indicatorIndex],
                    source: this.lastSource
                }
            });
        },
        startAutoPlay() {
            if (this.vertical && !this.height || !this.vertical && !this.width) {
                this.updateSize();
            }
            this.autoplayTimeout = setTimeout(() => {
                this.touching = false;
                if (!this.circular) {
                    this.currentIndex = this.currentIndex === this.pageLength - 1 ? 0 : this.currentIndex + 1;
                    setTimeout(() => {
                        this.translate = -this.currentIndex * this.wrapperSize;
                    }, 0);
                } else {
                    const preResult = this.currentIndex;
                    if (preResult === this.pageLength) {
                        if (this.translate === 0) {
                            this.setIndex(1);
                        }
                        else {
                            this.setIndex(this.pageLength + 1);
                        }
                    } else if (preResult === this.pageLength + 1) {
                        this.touching = true;
                        this.translate = -this.wrapperSize;
                        this.currentIndex = 2;
                        circularTimer = setTimeout(() => {
                            this.setIndex(2);
                        }, 100);
                    } else {
                        this.currentIndex = preResult + 1;
                        setTimeout(() => {
                            this.translate = -this.currentIndex * this.wrapperSize;
                        }, 0);
                    }
                }

                this.lastSource = 'autoplay';

                this.$emit('change', {
                    type: 'change',
                    detail: {
                        current: this.indicatorIndex,
                        currentItemId: this.itemIdList[this.indicatorIndex],
                        source: 'autoplay'
                    }
                });

                this.startAutoPlay();
            }, this.interval);
        },
        stopAutoPlay() {
            window.clearInterval(this.autoplayTimeout);
        },
        setIndex(index, forbidAnimation) {
            if (this.wrapperSize === 0) {
                shouldUpdateIndex = true;
            }
            this.touching = forbidAnimation || false;
            this.currentIndex = index;
            this.translate = -this.currentIndex * this.wrapperSize;
        }
    }
}
</script>

<style lang="less">
@indicatorSize: 8px;

.bl___swiper-container {
    display: block;
    height: 150px;
    position: relative;
    overflow: hidden;

    .bl___swiper-wrapper {
        display: flex;
        width: 100%;
        height: 100%;
        flex-direction: row;
        will-change: transform;
        align-items: stretch;
        box-sizing: border-box;

        &.vertical {
            flex-direction: column;
        }

        & > .bl___swiper-clone,
        & > .bl___swiper-item {
            display: block;
            overflow: hidden;
            flex: 0 0 100%;
            width: 100%;
            height: 100%;

            .bl___swiper-item {
                width: 100%;
                height: 100%;
            }
        }

        & > :not(.bl___swiper-item):not(.bl___swiper-clone) {
            width: 0px !important;
            height: 0px !important;
            display: none !important;
        }
    }

    .bl___indicator-container {
        position: absolute;
        overflow: hidden;
        width: 100%;
        height: @indicatorSize;
        bottom: @indicatorSize;
        display: flex;
        flex-direction: row;
        justify-content: center;
        align-items: center;

        .bl___indicator-item {
            width: @indicatorSize;
            height: @indicatorSize;
            border-radius: @indicatorSize;

            & + .bl___indicator-item {
                margin-left: @indicatorSize;
            }
        }

        &.vertical {
            width: @indicatorSize;
            height: 100%;
            top: 0;
            right: @indicatorSize;
            flex-direction: column;

            .bl___indicator-item + .bl___indicator-item {
                margin-left: 0;
                margin-top: @indicatorSize;
            }
        }
    }
}
</style>
