<template>
  <div class="captcha">
    <input
      v-for="(c, index) in getCtData"
      :key="index"
      type="text"
      v-model="getCtData[index]"
      ref="inputRefs"
      inputmode="numeric"
      pattern="[0-9]*"
      @input="e => onInput(e.target.value, index)"
      @keydown="e => onKeydown(e, index)"
      @keypress="e => onKeypress(e)"
      @focus="onFocus"
      @pause="onPause"
      :disabled="loading"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'

interface Props {
  ct: string[]
}

interface Emits {
  (e: 'sendCaptcha', password: string): void
}

const props = defineProps<Props>()
const emit = defineEmits<Emits>()

const loading = ref(false)
const timeout = ref<NodeJS.Timeout | null>(null)
const inputRefs = ref<HTMLInputElement[]>([])

const getCtData = computed({
  get: () => props.ct,
  set: (value: string[]) => {
    // 这里需要特殊处理，因为computed通常是只读的
    // 但原代码中直接修改了getCtData，所以这里需要emit一个事件或者使用其他方式
    // 由于这是父组件传来的props，我们需要通过emit通知父组件更新
    props.ct.splice(0, props.ct.length, ...value)
  }
})

const ctSize = computed(() => getCtData.value.length)

const cIndex = computed(() => {
  let i = getCtData.value.findIndex(item => item === '')
  i = (i + ctSize.value) % ctSize.value
  return i
})

const lastCode = computed(() => getCtData.value[ctSize.value - 1])

watch(cIndex, () => {
  resetCaret()
})

watch(lastCode, (newVal, oldVal) => {
  if (newVal && newVal !== oldVal) {
    inputRefs.value[ctSize.value - 1]?.blur()
    sendCaptcha()
  }
})

onMounted(() => {
  resetCaret()
})

const onInput = (val: string, index: number) => {
  if (timeout.value) {
    clearTimeout(timeout.value)
  }
  timeout.value = setTimeout(() => {
    val = String(val).replace(/\D/g, '')
    getCtData.value[index] = val
    if (index === ctSize.value - 1) {
      getCtData.value[ctSize.value - 1] = val[0] // 最后一个码，只允许输入一个字符。
    } else if (val.length > 1) {
      let i = index
      for (i = index; i < ctSize.value && i - index < val.length; i++) {
        getCtData.value[i] = val[i - index]
      }
      resetCaret()
    } else if (!(val + '')) {
      getCtData.value[index] = ''
    }
  }, 10)
}

const onPause = () => {}

const resetCaret = () => {
  inputRefs.value[ctSize.value - 1]?.focus()
}

const onFocus = () => {
  // 监听 focus 事件，将光标重定位到"第一个空白符的位置"。
  let index = getCtData.value.findIndex(item => item === '')
  index = (index + ctSize.value) % ctSize.value
  inputRefs.value[index]?.focus()
}

const onKeypress = (e: KeyboardEvent) => {
  // 只允许输入数字0-9
  const char = String.fromCharCode((e as any).which)
  if (!/[0-9]/.test(char)) {
    e.preventDefault()
  }
}

const onKeydown = (e: KeyboardEvent, index: number) => {
  // 处理删除键
  if (e.key === 'Backspace' || e.key === 'Delete') {
    const val = (e.target as HTMLInputElement).value
    if (val === '') {
      // 删除上一个input里的值，并对其focus。
      if (index > 0) {
        getCtData.value[index - 1] = ''
        inputRefs.value[index - 1]?.focus()
      }
    }
  }
  // 阻止其他非数字字符
  else if (
    e.key &&
    !/[0-9]/.test(e.key) &&
    ![
      'Backspace',
      'Delete',
      'Tab',
      'Enter',
      'ArrowLeft',
      'ArrowRight',
      'ArrowUp',
      'ArrowDown'
    ].includes(e.key)
  ) {
    e.preventDefault()
  }
}

const sendCaptcha = () => {
  const password = getCtData.value.map(item => item).join('')
  emit('sendCaptcha', password)
}

const reset = () => {
  // 重置。一般是验证码错误时触发。
  getCtData.value = getCtData.value.map(() => '')
  resetCaret()
}

// 暴露reset方法给父组件使用
defineExpose({
  reset
})
</script>

<style scoped lang="less">
.captcha {
  width: 100%;
  display: flex;
  justify-content: space-between;
}
input {
  width: 6rem;
  height: 6rem;
  border: 0.2rem solid #e6e6e6;
  border-radius: 0.8rem;
  text-align: center;
  font-size: 2.4rem;
  line-height: 6rem;
  outline: none;
  background-color: #f6f6f4;
}
input:last-of-type {
  margin-right: 0;
}
input:disabled {
  color: #000;
  background-color: #f6f6f4;
}
.msg {
  text-align: center;
}
</style>
