<template>
  <div class="digit-container" :style="{ height: itemHeight + 'px', width: itemWidth + 'px' }">
    <div class="digit-list" ref="listRef" :style="listStyle" @transitionend="onTransitionEnd">
      <div
        v-for="(n, i) in numbers"
        :key="i"
        class="digit"
        :style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
      >
        {{ n }}
      </div>
    </div>
  </div>
</template>

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

defineOptions({
  name: 'DigitRoll',
})

const props = defineProps<{
  digit: number
  duration?: number
  delay?: number
  height?: number
  width?: number
  cycles?: number
  initialRoll?: boolean
}>()

const duration = props.duration ?? 800
const delay = props.delay ?? 0
const itemHeight = props.height ?? 60
const itemWidth = props.width ?? 40
const cycles = Math.max(6, props.cycles ?? 20)

const numbers = computed(() => {
  const arr: number[] = []
  for (let c = 0; c < cycles; c++) {
    for (let d = 0; d < 10; d++) arr.push(d)
  }
  return arr
})

const middleCycleIndex = Math.floor(cycles / 2) * 10
const currentIndex = ref(middleCycleIndex + (props.digit % 10))
const transitioning = ref(false)
const listRef = ref<HTMLElement | null>(null)
let pendingTimer: number | null = null

const listStyle = computed(() => ({
  transform: `translateY(-${currentIndex.value * itemHeight}px)`,
  transitionProperty: 'transform',
  transitionDuration: transitioning.value ? `${duration}ms` : '0ms',
  transitionTimingFunction: 'cubic-bezier(0.2, 0.9, 0.2, 1)',
}))

function normalizeIndexAfterTransition() {
  const digit = currentIndex.value % 10
  transitioning.value = false
  nextTick(() => {
    currentIndex.value = middleCycleIndex + digit
  })
}

function onTransitionEnd(e?: TransitionEvent) {
  if (!e || e.propertyName === 'transform') {
    normalizeIndexAfterTransition()
  }
}

function rollTo(target: number, startDelay = 0) {
  if (pendingTimer) {
    clearTimeout(pendingTimer)
    pendingTimer = null
  }

  const currentDigit = currentIndex.value % 10
  const offset = ((target - currentDigit + 10) % 10) + 10
  const targetIndex = currentIndex.value + offset

  pendingTimer = window.setTimeout(() => {
    transitioning.value = true
    currentIndex.value = targetIndex
    pendingTimer = null
  }, startDelay)
}

watch(
  () => props.digit,
  (newVal, oldVal) => {
    if (newVal == null) return
    if (newVal !== oldVal) rollTo(newVal, delay)
  },
  { immediate: false }
)

onMounted(() => {
  currentIndex.value = middleCycleIndex
  if (props.initialRoll) {
    rollTo(props.digit, delay)
  } else {
    currentIndex.value = middleCycleIndex + (props.digit % 10)
  }
})
</script>

<style scoped lang="less">
.digit-container {
  overflow: hidden;
  display: inline-block;
  vertical-align: middle;
  border-radius: 6px;
  background: #0b173f;
  border: 1px solid #2a3b5a;
  box-shadow: 0 0 8px rgba(77, 201, 230, 0.15);
}

.digit-list {
  display: flex;
  flex-direction: column;
  will-change: transform;
}

.digit {
  text-align: center;
  font-size: 36px;
  font-weight: 700;
  color: #e0e0e0;
  user-select: none;
}
</style>
