<template>
  <span class="cma-count-up" :class="{ 'is-flashing': flashing }" ref="elRef">
    {{ displayValue }}
  </span>
</template>

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

const props = withDefaults(defineProps<{
  /** 目标数值 */
  value: number
  /** 小数位 */
  decimals?: number
  /** 是否加千分位 */
  thousand?: boolean
  /** 动画时长 ms */
  duration?: number
  /** 前缀 */
  prefix?: string
  /** 后缀 */
  suffix?: string
}>(), {
  decimals: 0,
  thousand: false,
  duration: 800,
  prefix: '',
  suffix: '',
})

const displayValue = ref(props.prefix + format(props.value) + props.suffix)
const elRef = ref<HTMLElement | null>(null)
const flashing = ref(false)
let rafId: number | null = null
let lastVal = props.value

function format(n: number): string {
  const fixed = n.toFixed(props.decimals)
  if (!props.thousand) return fixed
  const [int, dec] = fixed.split('.')
  const withComma = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
  return dec ? `${withComma}.${dec}` : withComma
}

function animate(from: number, to: number) {
  if (rafId) cancelAnimationFrame(rafId)
  if (from === to) {
    displayValue.value = props.prefix + format(to) + props.suffix
    return
  }
  const start = performance.now()
  const diff = to - from
  const tick = (now: number) => {
    const t = Math.min((now - start) / props.duration, 1)
    // easeOutCubic
    const eased = 1 - Math.pow(1 - t, 3)
    const val = from + diff * eased
    displayValue.value = props.prefix + format(val) + props.suffix
    if (t < 1) {
      rafId = requestAnimationFrame(tick)
    } else {
      // 结束闪烁提示
      flashing.value = true
      setTimeout(() => { flashing.value = false }, 600)
    }
  }
  rafId = requestAnimationFrame(tick)
}

watch(() => props.value, (nv, ov) => {
  animate(ov ?? 0, nv)
  lastVal = nv
})

onMounted(() => { lastVal = props.value })
onBeforeUnmount(() => { if (rafId) cancelAnimationFrame(rafId) })
</script>
