<template>
  <div
    class="vm-marquee"
    :class="{ 'is-paused': hoverPaused }"
    @mouseenter="onEnter"
    @mouseleave="onLeave"
  >
    <div v-if="showIcon" class="vm-marquee__icon" aria-hidden="true">
      <slot name="icon">
        <VmRiIcon v-if="icon" :name="icon" />
      </slot>
    </div>
    <div ref="viewportRef" class="vm-marquee__viewport">
      <div ref="trackRef" class="vm-marquee__track">
        {{ currentText }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {
  computed,
  nextTick,
  onMounted,
  onUnmounted,
  ref,
  useSlots,
  watch,
} from 'vue'
import VmRiIcon from './vm-ri-icon.vue'

defineOptions({ name: 'vm-marquee' })

const props = withDefaults(
  defineProps<{
    /** 消息列表；仅 1 条时单条循环 */
    messages?: string[]
    /** Remix Icon 名，如 ri-notification-3-line；也可用 #icon 插槽完全自定义 */
    icon?: string
    /** 单条滚完后的停顿（毫秒） */
    pause?: number
    /** 滚动速度（px/s） */
    speed?: number
    /** 悬停暂停 */
    pauseOnHover?: boolean
  }>(),
  {
    messages: () => [],
    pause: 3000,
    speed: 60,
    pauseOnHover: true,
  },
)

const slots = useSlots()
const showIcon = computed(() => Boolean(props.icon) || Boolean(slots.icon))

const list = computed(() =>
  (props.messages || []).map((s) => String(s ?? '').trim()).filter(Boolean),
)

const index = ref(0)
const currentText = computed(() => list.value[index.value] ?? '')

const viewportRef = ref<HTMLElement | null>(null)
const trackRef = ref<HTMLElement | null>(null)
const hoverPaused = ref(false)

let anim: Animation | null = null
let pauseTimer: ReturnType<typeof setTimeout> | null = null
let pauseResolve: (() => void) | null = null
/** 递增后旧循环全部退出，避免 messages 变更时双循环 */
let loopGen = 0

function clearPauseTimer() {
  if (pauseTimer != null) {
    clearTimeout(pauseTimer)
    pauseTimer = null
  }
  if (pauseResolve) {
    const resolve = pauseResolve
    pauseResolve = null
    resolve()
  }
}

function cancelAnim() {
  anim?.cancel()
  anim = null
}

function advanceIndex() {
  const n = list.value.length
  if (n <= 0) {
    index.value = 0
    return
  }
  index.value = (index.value + 1) % n
}

function sleep(ms: number) {
  return new Promise<void>((resolve) => {
    clearPauseTimer()
    pauseResolve = resolve
    pauseTimer = setTimeout(() => {
      pauseTimer = null
      pauseResolve = null
      resolve()
    }, ms)
  })
}

async function waitWhileHovered(gen: number) {
  while (hoverPaused.value && gen === loopGen) {
    await sleep(100)
  }
}

async function runOnce(gen: number): Promise<void> {
  const viewport = viewportRef.value
  const track = trackRef.value
  if (!viewport || !track || !list.value.length || gen !== loopGen) return

  await nextTick()
  if (gen !== loopGen) return
  await nextTick()
  if (gen !== loopGen) return

  const from = viewport.clientWidth
  const textW = Math.max(track.scrollWidth, 1)
  const to = -textW
  const distance = from - to
  const duration = Math.max(
    800,
    Math.round((distance / Math.max(props.speed, 1)) * 1000),
  )

  cancelAnim()
  // translateY(-50%) 与 top:50% 配合做垂直居中（动画不能只用 translateX）
  const tx = (x: number) => `translateY(-50%) translateX(${x}px)`
  track.style.transform = tx(from)

  anim = track.animate(
    [{ transform: tx(from) }, { transform: tx(to) }],
    {
      duration,
      easing: 'linear',
      fill: 'forwards',
    },
  )

  if (hoverPaused.value) anim.pause()

  try {
    await anim.finished
  } catch {
    // cancel() 会 reject
  } finally {
    if (anim) anim = null
  }
}

async function loop(gen: number) {
  while (gen === loopGen && list.value.length) {
    if (index.value >= list.value.length) index.value = 0
    await runOnce(gen)
    if (gen !== loopGen || !list.value.length) break
    await waitWhileHovered(gen)
    if (gen !== loopGen) break
    await sleep(Math.max(0, props.pause))
    if (gen !== loopGen) break
    await waitWhileHovered(gen)
    if (gen !== loopGen) break
    advanceIndex()
  }
}

function restart() {
  loopGen += 1
  const gen = loopGen
  clearPauseTimer()
  cancelAnim()
  if (index.value >= list.value.length) index.value = 0
  if (!list.value.length) return
  void loop(gen)
}

function onEnter() {
  if (!props.pauseOnHover) return
  hoverPaused.value = true
  anim?.pause()
}

function onLeave() {
  if (!props.pauseOnHover) return
  hoverPaused.value = false
  anim?.play()
}

onMounted(() => {
  restart()
})

onUnmounted(() => {
  loopGen += 1
  clearPauseTimer()
  cancelAnim()
})

watch(
  () => [props.messages, props.speed, props.pause] as const,
  () => {
    restart()
  },
  { deep: true },
)
</script>

<style lang="scss" scoped>
.vm-marquee {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  min-height: var(--vm-control-height, 32px);
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--vm-control-radius, 8px);
  background: var(--muted);
  color: var(--foreground);
  overflow: hidden;
}

.vm-marquee__icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--brand, #4e5dff);
  font-size: 16px;
  line-height: 1;
}

.vm-marquee__viewport {
  position: relative;
  flex: 1;
  align-self: stretch;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.vm-marquee__track {
  position: absolute;
  top: 50%;
  left: 0;
  white-space: nowrap;
  font-size: 13px;
  line-height: 1.5;
  transform: translateY(-50%);
  will-change: transform;
}
</style>
