<template>
	<!-- 支付宝小程序使用getRect()获取组件的根元素尺寸，所以在外面套一个"壳" -->
	<view>
		<view class="u-index-bar">
			<slot />
			<view
        v-if="state.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 props.indexList"
          :key="index"
          class="u-index-bar__index"
          :style="{
            zIndex: props.zIndex + 1,
            color: state.activeAnchorIndex === index ? props.activeColor : ''
          }"
				 :data-index="index">
					{{ item }}
				</view>
			</view>
			<view
        class="u-indexed-list-alert"
        v-if="state.touchmove && props.indexList[state.touchmoveIndex]"
        :style="{
          zIndex: alertZIndex
        }"
      >
				<text>
          {{ props.indexList[state.touchmoveIndex] }}
        </text>
			</view>
		</view>
	</view>
</template>

<script lang="ts">
  export default {
    name: 'u-index-list',
  }
</script>

<script setup lang="ts">
  import {
    getCurrentInstance,
    reactive,
    toRefs,
    computed,
    watch,
    withDefaults
  } from 'vue';
  import { ComponentInternalInstance } from "@vue/runtime-core";
  import { useComposable } from '../../libs/hooks';

	const indexList= function(): string[]  {
		const _indList = [];
    const charCodeOfA = 'A'.charCodeAt(0);
		for (let i = 0; i < 26; i++) {
      _indList.push(String.fromCharCode(charCodeOfA + i));
		}
		return _indList;
	};

	/**
	 * 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>
	 */
  interface UIndexListState {
    activeAnchorIndex: number;
    showSidebar: boolean;
    // children: any[];
    touchmove: boolean;
    touchmoveIndex: number;
    stickyOffsetTop: number;
    scrollToAnchorIndex: number | null;
    timer: number | null;
    sidebar: {
      top: number;
      height: number;
    };
    top: number;
    height: number;
  }
  interface UIndexListProps {
    sticky?: boolean;
    zIndex?: number | string;
    scrollTop?: number | string;
    offsetTop?: number | string;
    indexList?: string[];
    activeColor?: string;
  }

  const props = withDefaults(defineProps<UIndexListProps>(), {
    sticky: true,
    zIndex: '',
    scrollTop: 0,
    offsetTop: 0,
    indexList: () => indexList(),
    activeColor: '#2979ff',
  });

  const emit = defineEmits<{
    (e: 'select', index: string | number): void;
  }>();

  const { guid, addUnit, getRect, Z_INDEX_CONFIG } = useComposable();

  const instance: ComponentInternalInstance | null = getCurrentInstance();

  const state = reactive<UIndexListState>({
    activeAnchorIndex: 0,
    showSidebar: true,
    // children: [],
    touchmove: false,
    touchmoveIndex: 0,

    stickyOffsetTop: 0,
    scrollToAnchorIndex: null,
    timer: null,
    sidebar: {
      top: 0,
      height: 0,
    },
    top: 0,
    height: 0,
  });

  // #ifdef H5
  state.stickyOffsetTop = props.offsetTop ? uni.upx2px(Number(props.offsetTop)) : 44;
  // #endif
  // #ifndef H5
  state.stickyOffsetTop = props.offsetTop ? uni.upx2px(Number(props.offsetTop)) : 0;
  // #endif
  // 只能在created生命周期定义children，如果在data定义，会因为循环引用而报错
  const children = [];

  // 弹出toast的z-index值
  const alertZIndex = computed(() => {
    return Z_INDEX_CONFIG.toast;
  });

  const updateData = () => {
    state.timer && clearTimeout(state.timer);
    state.timer = setTimeout(() => {
      state.showSidebar = !!children.length;
      setRect().then(() => {
        onScroll();
      });
    }, 0);
  };

  const setRect = () => {
    return Promise.all([
      setAnchorsRect(),
      setListRect(),
      setSiderbarRect()
    ]);
  };

  const setAnchorsRect = () => {
    return Promise.all(children?.map((anchor, index) => anchor
      .getRect('.u-index-anchor-wrapper')
      .then(
        (rect) => {
          Object.assign(anchor, {
            height: rect.height,
            top: rect.top
          });
        })
      )
    );
  };

  const setListRect = () => {
    return getRect('.u-index-bar').then((rect) => {
      state.height = rect.height;
      state.top = rect.top + props.scrollTop;
    });
  };

  const setSiderbarRect = () => {
    return getRect('.u-index-bar__sidebar').then(rect => {
      state.sidebar = {
        height: rect.height,
        top: rect.top
      };
    });
  };

  const getActiveAnchorIndex = () => {
    for (let i = children.length - 1; i >= 0; i--) {
      const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
      const reachTop = props.sticky ? preAnchorHeight : 0;
      if (reachTop >= children[i].top) {
        return i;
      }
    }
    return -1;
  };

  const onScroll = () => {
    if (!children.length) {
      return;
    }
    const active = getActiveAnchorIndex();
    state.activeAnchorIndex = active;
    if (props.sticky) {
      let isActiveAnchorSticky = false;
      if (active !== -1) {
        isActiveAnchorSticky = children[active].top <= 0;
      }
      children.forEach((item, index) => {
        if (index === active) {
          let wrapperStyle = '';
          let anchorStyle = {
            color: `${props.activeColor}`
          };
          if (isActiveAnchorSticky) {
            wrapperStyle = {
              height: `${children[index].height}px`
            };
            anchorStyle = {
              position: 'fixed',
              top: `${state.stickyOffsetTop}px`,
              zIndex: `${props.zIndex ? props.zIndex : Z_INDEX_CONFIG.indexListSticky}`,
              color: `${props.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 ? state.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: `${props.zIndex ? props.zIndex : Z_INDEX_CONFIG.indexListSticky}`,
            color: `${props.activeColor}`
          };
          item.active = active;
          item.anchorStyle = anchorStyle;
        } else {
          item.active = false;
          item.anchorStyle = '';
          item.wrapperStyle = '';
        }
      });
    }
  };

  const onTouchStop = () => {
    state.touchmove = false;
    state.scrollToAnchorIndex = null;
  };

  const scrollToAnchor = (index: number) => {
    if (state.scrollToAnchorIndex === index) {
      return;
    }
    state.scrollToAnchorIndex = index;
    const anchor = children.find((item) => item.index === props.indexList[index]);
    if (anchor) {
      emit('select', anchor.index);
      uni.pageScrollTo({
        duration: 0,
        scrollTop: anchor.top + props.scrollTop
      });
    }
  };

  const onTouchMove = (event: TouchEvent) => {
    state.touchmove = true;
    const sidebarLength = children.length;
    const touch = event.touches[0];
    const itemHeight = state.sidebar.height / sidebarLength;
    let clientY = 0;
    clientY = touch.clientY;
    let index = Math.floor((clientY - state.sidebar.top) / itemHeight);
    if (index < 0) {
      index = 0;
    } else if (index > sidebarLength - 1) {
      index = sidebarLength - 1;
    }
    state.touchmoveIndex = index;
    scrollToAnchor(index);
  };

  watch(() => props.scrollTop, () =>　{
    updateData();
  });


  defineExpose({
    ...toRefs(state),
    alertZIndex,
    updateData,
    setRect,
    setAnchorsRect,
    setListRect,
    setSiderbarRect,
    getActiveAnchorIndex,
    onScroll,
    onTouchMove,
    onTouchStop,
    scrollToAnchor,
  });
</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>
