<template>
	<view class="cl-input-otp" :class="[pt.className]">
		<view class="cl-input-otp__inner">
			<cl-input
				v-model="value"
				ref="inputRef"
				:type="inputType"
				:pt="{
					className: '!h-full'
				}"
				:disabled="disabled"
				:autofocus="autofocus"
				:maxlength="length"
				:hold-keyboard="false"
				:clearable="false"
				@input="onInput"
				@focus="animateCursor(true)"
				@blur="animateCursor(true)"
			></cl-input>
		</view>

		<view class="cl-input-otp__list" :class="[pt.list?.className]">
			<view
				v-for="(item, index) in list"
				:key="index"
				class="cl-input-otp__item"
				:class="[
					{
						'is-disabled': disabled,
						'is-dark': isDark,
						'is-active': value.length == index && isFocus
					},
					pt.item?.className
				]"
			>
				<text
					class="cl-input-otp__value"
					:class="[
						{
							'is-disabled': disabled,
							'is-dark': isDark
						},
						pt.value?.className
					]"
					>{{ item }}</text
				>
				<view
					class="cl-input-otp__cursor"
					:class="[pt.cursor?.className]"
					:style="{
						opacity: cursorOpacity
					}"
					v-if="value.length == index && isFocus && item == ''"
				></view>
			</view>
		</view>
	</view>
</template>

<script setup lang="ts">
import { computed, ref, watch, type PropType, type Ref } from "vue";
import type { ClInputType, PassThroughProps } from "../../types";
import { isDark, parsePt } from "@/cool";

defineOptions({
	name: "cl-input-otp"
});

// 定义组件属性
const props = defineProps({
	// 透传样式
	pt: {
		type: Object,
		default: () => ({})
	},
	// 绑定值
	modelValue: {
		type: String,
		default: ""
	},
	// 是否自动聚焦
	autofocus: {
		type: Boolean,
		default: false
	},
	// 验证码位数
	length: {
		type: Number,
		default: 4
	},
	// 是否禁用
	disabled: {
		type: Boolean,
		default: false
	},
	// 输入框类型
	inputType: {
		type: String as PropType<ClInputType>,
		default: "number"
	}
});

/**
 * 事件定义
 * update:modelValue - 更新绑定值
 * done - 输入完成
 */
const emit = defineEmits(["update:modelValue", "done"]);

/**
 * 透传样式类型定义
 */
type PassThrough = {
	className?: string;
	list?: PassThroughProps;
	item?: PassThroughProps;
	cursor?: PassThroughProps;
	value?: PassThroughProps;
};

// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));

// 输入框引用
const inputRef = ref<ClInputComponentPublicInstance | null>(null);

// 输入值
const value = ref(props.modelValue) as Ref<string>;

/**
 * 是否聚焦状态
 */
const isFocus = computed<boolean>(() => {
	if (props.disabled) {
		return false;
	}

	if (inputRef.value == null) {
		return false;
	}

	return (inputRef.value as ClInputComponentPublicInstance).isFocus;
});

/**
 * 验证码数组
 * 根据长度生成空数组,每个位置填充对应的输入值
 */
const list = computed<string[]>(() => {
	const arr = [] as string[];

	for (let i = 0; i < props.length; i++) {
		arr.push(value.value.charAt(i));
	}

	return arr;
});

/**
 * 监听绑定值变化
 * 同步更新内部值
 */
watch(
	computed(() => props.modelValue),
	(val: string) => {
		value.value = val;
	}
);

/**
 * 输入事件处理
 * @param val 输入值
 */
function onInput(val: string) {
	// 更新绑定值
	emit("update:modelValue", val);

	// 输入完成时触发done事件
	if (val.length == props.length) {
		uni.hideKeyboard();
		emit("done", val);
	}
}

/**
 * 光标闪烁透明度值
 * 范围: 0.3-1.0
 */
const cursorOpacity = ref(0.3);

/**
 * 光标闪烁动画帧ID
 */
let cursorAnimationId = 0;

/**
 * 控制光标闪烁动画
 * @param isIncreasing 透明度是否递增
 */
function animateCursor(isIncreasing: boolean) {
	// #ifdef APP
	// 未获得焦点时不执行动画
	if (!isFocus.value) {
		return;
	}

	// 取消上一次动画
	if (cursorAnimationId != 0) {
		cancelAnimationFrame(cursorAnimationId);
		cursorAnimationId = 0;
	}

	// 执行动画帧
	cursorAnimationId = requestAnimationFrame(() => {
		// 根据方向调整透明度值
		cursorOpacity.value += isIncreasing ? 0.01 : -0.01;

		// 到达边界值时改变方向
		if (cursorOpacity.value > 1) {
			animateCursor(false);
		} else if (cursorOpacity.value <= 0.3) {
			animateCursor(true);
		} else {
			animateCursor(isIncreasing);
		}
	});
	// #endif
}
</script>

<style lang="scss" scoped>
.cl-input-otp {
	@apply relative;

	&__inner {
		@apply absolute top-0 h-full z-10;
		opacity: 0;
		left: -100%;
		width: 200%;
	}

	&__list {
		@apply flex flex-row relative;
		margin: 0 -10rpx;
	}

	&__item {
		@apply flex flex-row items-center justify-center;
		@apply border border-solid border-surface-200 rounded-lg bg-white;
		height: 80rpx;
		width: 80rpx;
		margin: 0 10rpx;

		&.is-disabled {
			@apply bg-surface-100 opacity-70;
		}

		&.is-dark {
			@apply bg-surface-800 border-surface-600;

			&.is-disabled {
				@apply bg-surface-700;
			}
		}

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

	&__value {
		@apply text-surface-700 text-md;

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

	&__cursor {
		@apply absolute;
		@apply bg-primary-500;
		width: 2rpx;
		height: 36rpx;
	}

	// #ifdef H5 || MP
	&__cursor {
		animation: flash 1s infinite ease;
	}

	@keyframes flash {
		0% {
			opacity: 0.3;
		}

		50% {
			opacity: 1;
		}

		100% {
			opacity: 0.3;
		}
	}
	// #endif
}
</style>
