<template>
	<view
		class="cl-input-otp"
		:class="[
			{
				'cl-input-otp--disabled': disabled
			},
			pt.className
		]"
	>
		<view class="cl-input-otp__inner" @tap="onCursor()">
			<cl-input
				v-model="value"
				ref="inputRef"
				:type="inputType"
				:pt="{
					className: '!h-full'
				}"
				:disabled="disabled"
				:autofocus="autofocus"
				:maxlength="length"
				:hold-keyboard="false"
				:clearable="false"
				@change="onChange"
			></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
				]"
			>
				<cl-text
					:color="value.length >= index && isFocus ? 'primary' : ''"
					:pt="{
						className: pt.value?.className
					}"
					>{{ item }}</cl-text
				>
				<view
					ref="cursorRef"
					class="cl-input-otp__cursor"
					:class="[pt.cursor?.className]"
					v-if="value.length == index && isFocus && item == ''"
				></view>
			</view>
		</view>
	</view>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch, type PropType, type Ref } from "vue";
import type { ClInputType, PassThroughProps } from "../../types";
import { createAnimation, isDark, isEmpty, last, 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 cursorRef = ref<UniElement[]>([]);

// 输入值
const value = ref(props.modelValue);

/**
 * 是否聚焦状态
 */
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;
});

/**
 * 光标动画
 */
async function onCursor() {
	await nextTick();

	if (isEmpty(cursorRef.value)) {
		return;
	}

	// 开始动画
	// #ifdef APP
	createAnimation(last(cursorRef.value), {
		duration: 600,
		loop: -1,
		alternate: true
	})
		.opacity("0", "1")
		.play();
	// #endif
}

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

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

	// 更新光标动画
	onCursor();
}

onMounted(() => {
	watch(
		computed(() => props.modelValue),
		(val: string) => {
			value.value = val;
			onCursor();
		},
		{
			immediate: true
		}
	);
});
</script>

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

	&__inner {
		@apply absolute top-0 h-full z-10;
		opacity: 0;
		// 小程序隐藏 placeholder
		left: -100%;
		width: 200%;
	}

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

	&__item {
		@apply flex flex-row items-center justify-center duration-100;
		@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;
		}
	}

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

		// #ifndef APP
		animation: blink 1s infinite;

		@keyframes blink {
			0% {
				opacity: 1;
			}
			50% {
				opacity: 0;
			}
		}
		// #endif
	}

	&--disabled {
		@apply opacity-50;
	}
}
</style>
