<template>
  <div :class="[cn.b(), cn.is('invalid', invalid)]">
    <div :class="[cn.e('label')]">
      <slot name="title">
        <p v-if="!hideTitle">
          {{ title }}
        </p>
      </slot>
    </div>
    <div :class="[cn.e('wrapper')]">
      <input
        v-for="i in 6"
        :key="i"
        ref="digitInputs"
        type="text"
        :class="[cn.e('digit')]"
        :value="value.charAt(i - 1)"
        inputmode="numeric"
        @focus="handleFocus(i)"
        @blur="onBlur"
        @keydown="handleKeydown(i, $event)"
        @paste.prevent="handleClipboardPaste"
      >
    </div>
    <p
      v-if="invalid"
      :class="[cn.e('validation-message')]"
    >
      {{ realMessage }}
    </p>
  </div>
</template>

<script setup>
import { computed, nextTick, onMounted, ref, toRef } from 'vue'
import { useField } from 'vee-validate'
import useClassname from '../../composables/use-classname'

const props = defineProps({
  id: {
    type: String,
    required: true,
  },
  title: {
    type: String,
    default: '2FA',
  },
  modelValue: {
    type: String,
    default: '',
  },
  invalid: {
    type: Boolean,
    default: false,
  },
  autofocus: {
    type: Boolean,
    default: false,
  },
  hideTitle: {
    type: Boolean,
    default: false,
  },
})

defineEmits(['update:modelValue'])

const { t } = useI18n({ useScope: 'global' })

const cn = useClassname('ui-code-input')
const { value, errorMessage, meta, handleChange, handleBlur } = useField(
  toRef(props, 'id'),
  'required|length:6',
  {
    validateOnValueUpdate: false,
    initialValue: props.modelValue,
    syncVModel: true,
  },
)

const digitInputs = ref(null)
let ignoreBlurEvent = false

const invalid = computed(
  () => props.invalid || (!!errorMessage.value && meta.touched),
)

const realMessage = computed(() =>
  props.invalid ? t('validation.invalid_code') : errorMessage.value,
)

function handleFocus(index) {
  if (value.value.length < 6 && index > value.value.length)
    digitInputs.value[value.value.length].focus()
}

async function handleKeydown(index, event) {
  const currentValue = value.value

  let nextInput
  if (/^\d+$/.test(event.key)) {
    const value
      = currentValue.substring(0, index - 1)
      + event.key
      + currentValue.substring(index)

    handleChange(value, false)

    nextInput = digitInputs.value[index]
  }
  else if (event.key === 'Backspace' && currentValue) {
    const currentChar = currentValue.substring(index - 1, index)

    handleChange(value.value.substring(0, index - 1), false)

    if (!currentChar)
      nextInput = digitInputs.value[Math.max(index - 2, 0)]
  }

  if (
    !event.ctrlKey
    && !event.metaKey
    && !['Tab', 'Enter'].includes(event.key)
  )
    event.preventDefault()

  await nextTick()

  if (nextInput) {
    ignoreBlurEvent = true

    event.target.blur()
    nextInput.focus()

    ignoreBlurEvent = false
  }
}

function handleClipboardPaste(event) {
  const value = event.clipboardData?.getData('text')

  if (/^\d{6}/.test(value)) {
    handleChange(value.substring(0, 6))
    event.target.blur()
  }
}

function onBlur(event) {
  if (ignoreBlurEvent)
    return

  handleBlur(event)
}

onMounted(() => {
  if (props.autofocus)
    digitInputs.value[0].focus()
})
</script>

<style lang="scss">
.ui-code-input {
  text-align: left;

  &__wrapper {
    display: flex;
  }

  &__label {
    @include txt-m-b('code-input-label');

    margin-bottom: var(--code-input-label-margin, 8px);
  }

  &__digit {
    --border-color: #{$clr-grey-300};
    --border-width: 1px;

    width: var(--code-input-size, 62px);
    height: var(--code-input-size, 62px);
    border-radius: var(--code-input-border-radius, 16px);
    outline: var(--border-width) solid var(--border-color);
    outline-offset: calc(var(--border-width) * -1);
    border: none;
    text-align: center;
    padding: 0;
    transition: outline-color 0.2s ease-out;
    caret-color: $clr-cyan-500;

    &:focus {
      --border-width: 2px;
      --border-color: #{$clr-cyan-500};
    }

    &:not(:first-child) {
      margin-left: var(--code-input-gap, 25px);
    }
  }

  &.is-invalid {
    --code-input-border-color: #{$clr-red-500};
  }

  &__validation-message {
    margin-top: 8px;
    color: $clr-red-500;
  }
}
</style>
