<script setup>
import { computed, onBeforeUnmount, ref, watch } from 'vue'

const props = defineProps({
  /** 目标数值 */
  value: {
    type: [Number, String],
    default: 0
  },
  /** int | money | area — 与共同富裕展示口径一致 */
  format: {
    type: String,
    default: 'int'
  },
  duration: {
    type: Number,
    default: 1400
  }
})

const animatedNum = ref(0)
let rafId = 0

function toNumber(val) {
  if (val === null || val === undefined || val === '') {
    return 0
  }
  const n = Number(val)
  return Number.isNaN(n) ? 0 : n
}

function formatNumber(num, formatType) {
  const n = Number(num)
  const safe = Number.isNaN(n) ? 0 : n
  if (formatType === 'money' || formatType === 'area') {
    return safe.toLocaleString('zh-CN', {
      minimumFractionDigits: 0,
      maximumFractionDigits: 2
    })
  }
  return safe.toLocaleString('zh-CN', {
    minimumFractionDigits: 0,
    maximumFractionDigits: 0
  })
}

const columns = computed(() => {
  const str = formatNumber(animatedNum.value, props.format)
  return str.split('').map((ch) => {
    if (!/\d/.test(ch)) {
      return { type: 'sep', char: ch }
    }
    return { type: 'digit', digit: Number(ch) }
  })
})

function easeOutCubic(t) {
  return 1 - (1 - t) ** 3
}

function cancelAnim() {
  if (rafId) {
    cancelAnimationFrame(rafId)
    rafId = 0
  }
}

function animateTo(target) {
  cancelAnim()
  const from = animatedNum.value
  const to = target
  if (from === to) {
    return
  }
  const start = performance.now()
  const run = (now) => {
    const t = Math.min(1, (now - start) / props.duration)
    animatedNum.value = from + (to - from) * easeOutCubic(t)
    if (t < 1) {
      rafId = requestAnimationFrame(run)
    } else {
      animatedNum.value = to
      rafId = 0
    }
  }
  rafId = requestAnimationFrame(run)
}

watch(
  () => props.value,
  (val) => {
    animateTo(toNumber(val))
  },
  { immediate: true }
)

onBeforeUnmount(cancelAnim)
</script>

<template>
  <strong class="screen-scroll-number" aria-live="polite">
    <span
      v-for="(col, index) in columns"
      :key="index"
      class="screen-scroll-number__col"
      :class="{ 'is-sep': col.type === 'sep' }"
    >
      <span v-if="col.type === 'sep'" class="screen-scroll-number__sep">{{ col.char }}</span>
      <span v-else class="screen-scroll-number__wheel">
        <span
          class="screen-scroll-number__strip"
          :style="{ transform: `translate3d(0, -${col.digit * 10}%, 0)` }"
        >
          <span v-for="n in 10" :key="n" class="screen-scroll-number__cell">{{ n - 1 }}</span>
        </span>
      </span>
    </span>
  </strong>
</template>

<style scoped>
.screen-scroll-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: baseline;
  font-size: inherit;
  font-weight: inherit;
  line-height: 1;
}

.screen-scroll-number__col {
  display: inline-block;
  vertical-align: top;
}

.screen-scroll-number__col.is-sep {
  line-height: 1em;
}

.screen-scroll-number__sep {
  display: inline-block;
  line-height: 1em;
}

.screen-scroll-number__wheel {
  display: inline-block;
  height: 1em;
  overflow: hidden;
  line-height: 1;
}

.screen-scroll-number__strip {
  display: flex;
  flex-direction: column;
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

.screen-scroll-number__cell {
  height: 1em;
  line-height: 1em;
  display: block;
  text-align: center;
}
</style>
