<template>
	<view class="cl-index-bar" :class="[pt.className]">
		<view
			class="cl-index-bar__list"
			@touchstart="onTouchStart"
			@touchmove.stop.prevent="onTouchMove"
			@touchend="onTouchEnd"
		>
			<view class="cl-index-bar__item" v-for="(item, index) in list" :key="index">
				<view
					class="cl-index-bar__item-inner"
					:class="{
						'is-active': activeIndex == index
					}"
				>
					<text
						class="cl-index-bar__item-text"
						:class="{
							'is-active': activeIndex == index || isDark
						}"
						>{{ item }}</text
					>
				</view>
			</view>
		</view>
	</view>

	<view class="cl-index-bar__alert" v-show="showAlert">
		<view class="cl-index-bar__alert-icon dark:!bg-surface-800">
			<view class="cl-index-bar__alert-arrow dark:!bg-surface-800"></view>
			<text class="cl-index-bar__alert-text dark:!text-white">{{ alertText }}</text>
		</view>
	</view>
</template>

<script setup lang="ts">
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch, type PropType } from "vue";
import { isDark, isEmpty, parsePt } from "@/cool";

defineOptions({
	name: "cl-index-bar"
});

const props = defineProps({
	pt: {
		type: Object,
		default: () => ({})
	},
	modelValue: {
		type: Number,
		default: 0
	},
	list: {
		type: Array as PropType<string[]>,
		default: () => []
	}
});

const emit = defineEmits(["update:modelValue", "change"]);

const { proxy } = getCurrentInstance()!;

type PassThrough = {
	className?: string;
};

const pt = computed(() => parsePt<PassThrough>(props.pt));

// 存储索引条整体的位置信息
const barRect = ref({
	height: 0,
	width: 0,
	left: 0,
	top: 0
} as NodeInfo);

// 存储所有索引项的位置信息数组
const itemsRect = ref<NodeInfo[]>([]);

// 是否正在触摸
const isTouching = ref(false);

// 是否显示提示弹窗
const showAlert = ref(false);

// 当前提示弹窗显示的文本
const alertText = ref("");

// 当前触摸过程中的临时索引
const activeIndex = ref(-1);

/**
 * 获取索引条及其所有子项的位置信息
 * 用于后续触摸时判断手指所在的索引项
 */
function getRect() {
	nextTick(() => {
		setTimeout(() => {
			uni.createSelectorQuery()
				.in(proxy)
				.select(".cl-index-bar")
				.boundingClientRect()
				.exec((bar) => {
					if (isEmpty(bar)) {
						return;
					}

					// 获取索引条整体的位置信息
					barRect.value = bar[0] as NodeInfo;

					// 获取所有索引项的位置信息
					uni.createSelectorQuery()
						.in(proxy)
						.selectAll(".cl-index-bar__item")
						.boundingClientRect()
						.exec((items) => {
							if (isEmpty(items)) {
								getRect();
								return;
							}

							itemsRect.value = items[0] as NodeInfo[];
						});
				});
		}, 350);
	});
}

/**
 * 根据触摸点的Y坐标，计算出最接近的索引项下标
 * @param clientY 触摸点的Y坐标（相对于屏幕）
 * @returns 最接近的索引项下标
 */
function getIndex(clientY: number): number {
	if (itemsRect.value.length == 0) {
		// 没有索引项时，默认返回0
		return 0;
	}

	// 初始化最接近的索引和最小距离
	let closestIndex = 0;
	let minDistance = Number.MAX_VALUE;

	// 遍历所有索引项，找到距离触摸点最近的项
	for (let i = 0; i < itemsRect.value.length; i++) {
		const item = itemsRect.value[i];
		// 计算每个item的中心点Y坐标
		const itemCenterY = (item.top ?? 0) + (item.height ?? 0) / 2;
		// 计算触摸点到中心点的距离
		const distance = Math.abs(clientY - itemCenterY);

		// 更新最小距离和索引
		if (distance < minDistance) {
			minDistance = distance;
			closestIndex = i;
		}
	}

	// 边界处理，防止越界
	if (closestIndex < 0) {
		closestIndex = 0;
	} else if (closestIndex >= props.list.length) {
		closestIndex = props.list.length - 1;
	}

	return closestIndex;
}

/**
 * 更新触摸过程中的显示状态
 * @param index 新的索引
 */
function updateActive(index: number) {
	// 更新当前触摸索引
	activeIndex.value = index;
	// 更新弹窗提示文本
	alertText.value = props.list[index];
}

/**
 * 触摸开始事件处理
 * @param e 触摸事件对象
 */
function onTouchStart(e: TouchEvent) {
	// 标记为正在触摸
	isTouching.value = true;

	// 显示提示弹窗
	showAlert.value = true;

	// 获取第一个触摸点
	const touch = e.touches[0];

	// 计算对应的索引
	const index = getIndex(touch.clientY);

	// 更新显示状态
	updateActive(index);
}

/**
 * 触摸移动事件处理
 * @param e 触摸事件对象
 */
function onTouchMove(e: TouchEvent) {
	// 未处于触摸状态时不处理
	if (!isTouching.value) return;

	// 获取第一个触摸点
	const touch = e.touches[0];

	// 计算对应的索引
	const index = getIndex(touch.clientY);

	// 更新显示状态
	updateActive(index);
}

/**
 * 触摸结束事件处理
 * 结束后延迟隐藏提示弹窗，并确认最终选中的索引
 */
function onTouchEnd() {
	isTouching.value = false; // 标记为未触摸

	// 更新值
	if (props.modelValue != activeIndex.value) {
		emit("update:modelValue", activeIndex.value);
		emit("change", activeIndex.value);
	}

	// 延迟500ms后隐藏提示弹窗，提升用户体验
	setTimeout(() => {
		showAlert.value = false;
	}, 500);
}

watch(
	computed(() => props.modelValue),
	(val: number) => {
		activeIndex.value = val;
	},
	{
		immediate: true
	}
);

onMounted(() => {
	watch(
		computed(() => props.list),
		() => {
			getRect();
		},
		{
			immediate: true
		}
	);
});
</script>

<style lang="scss" scoped>
.cl-index-bar {
	@apply flex flex-col items-center justify-center;
	@apply absolute bottom-0 right-0 h-full;
	z-index: 110;

	&__item {
		@apply flex flex-col items-center justify-center;
		width: 50rpx;
		height: 34rpx;

		&-inner {
			@apply rounded-full flex flex-row items-center justify-center;
			width: 30rpx;
			height: 30rpx;

			&.is-active {
				@apply bg-primary-500;
			}
		}

		&-text {
			@apply text-xs text-surface-500;

			&.is-active {
				@apply text-white;
			}
		}
	}
}

.cl-index-bar__alert {
	@apply absolute bottom-0 right-8 h-full flex flex-col items-center justify-center;
	width: 120rpx;
	z-index: 110;

	&-icon {
		@apply rounded-full flex flex-row items-center justify-center;
		@apply bg-surface-300;
		height: 80rpx;
		width: 80rpx;
		overflow: visible;
	}

	&-arrow {
		@apply bg-surface-300 absolute;
		right: -8rpx;
		height: 40rpx;
		width: 40rpx;
		transform: rotate(45deg);
	}

	&-text {
		@apply text-white text-2xl;
	}
}
</style>
