<template>
    <!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸，所以在外面套一个"壳" -->
    <view>
        <view class="u-index-bar">
            <slot />
            <view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove"
             @touchend.stop.prevent="onTouchStop" @touchcancel.stop.prevent="onTouchStop">
                <view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}"
                 :data-index="index">
                    {{ item }}
                </view>
            </view>
            <view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{
                zIndex: alertZIndex
            }">
                <text>{{indexList[touchmoveIndex]}}</text>
            </view>
        </view>
    </view>
</template>

<script>
    var indexList = function() {
        var indexList = [];
        var charCodeOfA = 'A'.charCodeAt(0);
        for (var i = 0; i < 26; i++) {
            indexList.push(String.fromCharCode(charCodeOfA + i));
        }
        return indexList;
    };

    /**
     * indexList 索引列表
     * @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
     * @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
     * @property {Number String} scroll-top 当前滚动高度，自定义组件无法获得滚动条事件，所以依赖接入方传入
     * @property {Array} index-list 索引字符列表，数组（默认A-Z）
     * @property {Number String} z-index 锚点吸顶时的层级（默认965）
     * @property {Boolean} sticky 是否开启锚点自动吸顶（默认true）
     * @property {Number String} offset-top 锚点自动吸顶时与顶部的距离（默认0）
     * @property {String} highlight-color 锚点和右边索引字符高亮颜色（默认#2979ff）
     * @event {Function} select 选中右边索引字符时触发
     * @example <u-index-list :scrollTop="scrollTop"></u-index-list>
     */
    export default {
        name: "u-index-list",
        props: {
            sticky: {
                type: Boolean,
                default: true
            },
            zIndex: {
                type: [Number, String],
                default: ''
            },
            scrollTop: {
                type: [Number, String],
                default: 0,
            },
            offsetTop: {
                type: [Number, String],
                default: 0
            },
            indexList: {
                type: Array,
                default () {
                    return indexList()
                }
            },
            activeColor: {
                type: String,
                default: '#2979ff'
            }
        },
        created() {
            // #ifdef H5
            this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44;
            // #endif
            // #ifndef H5
            this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0;
            // #endif
            // 只能在created生命周期定义children，如果在data定义，会因为循环引用而报错
            this.children = [];
        },
        data() {
            return {
                activeAnchorIndex: 0,
                showSidebar: true,
                // children: [],
                touchmove: false,
                touchmoveIndex: 0,
            }
        },
        watch: {
            scrollTop() {
                this.updateData()
            }
        },
        computed: {
            // 弹出toast的z-index值
            alertZIndex() {
                return this.$u.zIndex.toast;
            }
        },
        methods: {
            updateData() {
                this.timer && clearTimeout(this.timer);
                this.timer = setTimeout(() => {
                    this.showSidebar = !!this.children.length;
                    this.setRect().then(() => {
                        this.onScroll();
                    });
                }, 0);
            },
            setRect() {
                return Promise.all([
                    this.setAnchorsRect(),
                    this.setListRect(),
                    this.setSiderbarRect()
                ]);
            },
            setAnchorsRect() {
                return Promise.all(this.children.map((anchor, index) => anchor
                    .$uGetRect('.u-index-anchor-wrapper')
                    .then((rect) => {
                        Object.assign(anchor, {
                            height: rect.height,
                            top: rect.top
                        });
                    })));
            },
            setListRect() {
                return this.$uGetRect('.u-index-bar').then((rect) => {
                    Object.assign(this, {
                        height: rect.height,
                        top: rect.top + this.scrollTop
                    });
                });
            },
            setSiderbarRect() {
                return this.$uGetRect('.u-index-bar__sidebar').then(rect => {
                    this.sidebar = {
                        height: rect.height,
                        top: rect.top
                    };
                });
            },
            getActiveAnchorIndex() {
                const {
                    children
                } = this;
                const {
                    sticky
                } = this;
                for (let i = this.children.length - 1; i >= 0; i--) {
                    const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
                    const reachTop = sticky ? preAnchorHeight : 0;
                    if (reachTop >= children[i].top) {
                        return i;
                    }
                }
                return -1;
            },
            onScroll() {
                const {
                    children = []
                } = this;
                if (!children.length) {
                    return;
                }
                const {
                    sticky,
                    stickyOffsetTop,
                    zIndex,
                    scrollTop,
                    activeColor
                } = this;
                const active = this.getActiveAnchorIndex();
                this.activeAnchorIndex = active;
                if (sticky) {
                    let isActiveAnchorSticky = false;
                    if (active !== -1) {
                        isActiveAnchorSticky =
                            children[active].top <= 0;
                    }
                    children.forEach((item, index) => {
                        if (index === active) {
                            let wrapperStyle = '';
                            let anchorStyle = {
                                color: `${activeColor}`
                            };
                            if (isActiveAnchorSticky) {
                                wrapperStyle = {
                                    height: `${children[index].height}px`
                                };
                                anchorStyle = {
                                    position: 'fixed',
                                    top: `${stickyOffsetTop}px`,
                                    zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
                                    color: `${activeColor}`
                                };
                            }
                            item.active = active;
                            item.wrapperStyle = wrapperStyle;
                            item.anchorStyle = anchorStyle;
                        } else if (index === active - 1) {
                            const currentAnchor = children[index];
                            const currentOffsetTop = currentAnchor.top;
                            const targetOffsetTop = index === children.length - 1 ?
                                this.top :
                                children[index + 1].top;
                            const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
                            const translateY = parentOffsetHeight - currentAnchor.height;
                            const anchorStyle = {
                                position: 'relative',
                                transform: `translate3d(0, ${translateY}px, 0)`,
                                zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
                                color: `${activeColor}`
                            };
                            item.active = active;
                            item.anchorStyle = anchorStyle;
                        } else {
                            item.active = false;
                            item.anchorStyle = '';
                            item.wrapperStyle = '';
                        }
                    });
                }
            },
            onTouchMove(event) {
                this.touchmove = true;
                const sidebarLength = this.children.length;
                const touch = event.touches[0];
                const itemHeight = this.sidebar.height / sidebarLength;
                let clientY = 0;
                clientY = touch.clientY;
                let index = Math.floor((clientY - this.sidebar.top) / itemHeight);
                if (index < 0) {
                    index = 0;
                } else if (index > sidebarLength - 1) {
                    index = sidebarLength - 1;
                }
                this.touchmoveIndex = index;
                this.scrollToAnchor(index);
            },
            onTouchStop() {
                this.touchmove = false;
                this.scrollToAnchorIndex = null;
            },
            scrollToAnchor(index) {
                if (this.scrollToAnchorIndex === index) {
                    return;
                }
                this.scrollToAnchorIndex = index;
                const anchor = this.children.find((item) => item.index === this.indexList[index]);
                if (anchor) {
                    this.$emit('select', anchor.index);
                    uni.pageScrollTo({
                        duration: 0,
                        scrollTop: anchor.top + this.scrollTop
                    });
                }
            }
        }
    };
</script>

<style lang="scss" scoped>
    @import "../../libs/css/style.components.scss";
    
    .u-index-bar {
        position: relative
    }

    .u-index-bar__sidebar {
        position: fixed;
        top: 50%;
        right: 0;
        @include vue-flex;
        flex-direction: column;
        text-align: center;
        transform: translateY(-50%);
        user-select: none;
        z-index: 99;
    }

    .u-index-bar__index {
        font-weight: 500;
        padding: 8rpx 18rpx;
        font-size: 22rpx;
        line-height: 1
    }

    .u-indexed-list-alert {
        position: fixed;
        width: 120rpx;
        height: 120rpx;
        right: 90rpx;
        top: 50%;
        margin-top: -60rpx;
        border-radius: 24rpx;
        font-size: 50rpx;
        color: #fff;
        background-color: rgba(0, 0, 0, 0.65);
        @include vue-flex;
        justify-content: center;
        align-items: center;
        padding: 0;
        z-index: 9999999;
    }

    .u-indexed-list-alert text {
        line-height: 50rpx;
    }
</style>
