<template>
  <div class="fullpage-container" @wheel.prevent="handleWheel" @touchstart="handleTouchStart"
    @touchend="handleTouchEnd">
    <div class="fullpage-wrapper" :style="wrapperStyle">
      <slot />
    </div>
    <div class="fullpage-dots">
      <span v-for="i in pageCount" :key="i" class="dot" :class="{ active: currentIndex === i - 1 }"
        @click="goTo(i - 1)">
        <span class="dot-inner" />
      </span>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, useSlots, nextTick } from 'vue'

const slots = useSlots()
const pageCount = computed(() => {
  const defaultSlot = slots.default?.()
  return defaultSlot ? defaultSlot.length : 0
})

const currentIndex = ref(0)
const isAnimating = ref(false)
const touchStartY = ref(0)
const direction = ref('down')

const wrapperStyle = computed(() => ({
  transform: `translateY(-${currentIndex.value * 100}vh)`,
  transition: isAnimating.value ? 'transform 0.9s cubic-bezier(0.76, 0, 0.24, 1)' : 'none',
}))

function goTo(index) {
  if (isAnimating.value || index === currentIndex.value) return
  if (index < 0 || index >= pageCount.value) return
  const prevIndex = currentIndex.value
  direction.value = index > prevIndex ? 'down' : 'up'
  isAnimating.value = true
  triggerLeaveAnimation(prevIndex)
  currentIndex.value = index
  triggerEnterAnimation(index)
  setTimeout(() => { isAnimating.value = false }, 900)
}

function triggerLeaveAnimation(index) {
  nextTick(() => {
    const pages = document.querySelectorAll('.fullpage-wrapper > *')
    const page = pages[index]
    if (!page) return
    page.classList.remove('page-leave-up', 'page-leave-down', 'page-enter-up', 'page-enter-down')
    void page.offsetWidth
    page.classList.add(direction.value === 'down' ? 'page-leave-up' : 'page-leave-down')
  })
}

function triggerEnterAnimation(index) {
  nextTick(() => {
    const pages = document.querySelectorAll('.fullpage-wrapper > *')
    const page = pages[index]
    if (!page) return
    page.classList.remove('page-leave-up', 'page-leave-down', 'page-enter-up', 'page-enter-down')
    void page.offsetWidth
    page.classList.add(direction.value === 'down' ? 'page-enter-down' : 'page-enter-up')
  })
}

function handleWheel(e) {
  if (isAnimating.value) return
  if (e.deltaY > 0) goTo(currentIndex.value + 1)
  else if (e.deltaY < 0) goTo(currentIndex.value - 1)
}

function handleTouchStart(e) {
  touchStartY.value = e.touches[0].clientY
}

function handleTouchEnd(e) {
  const deltaY = touchStartY.value - e.changedTouches[0].clientY
  if (Math.abs(deltaY) > 50) {
    if (deltaY > 0) goTo(currentIndex.value + 1)
    else goTo(currentIndex.value - 1)
  }
}

defineExpose({ currentIndex, goTo })
</script>

<style scoped>
.fullpage-container {
  width: 100%;
  height: 100vh;
  overflow: hidden;
  position: relative;
}

.fullpage-wrapper {
  width: 100%;
  will-change: transform;
}

/* 圆点导航升级 */
.fullpage-dots {
  position: fixed;
  right: 28px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 16px;
  z-index: 100;
}

.dot {
  width: 12px;
  height: 12px;
  position: relative;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.dot::before {
  content: '';
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid rgba(0, 0, 0, 0.2);
  transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.dot-inner {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.25);
  transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  display: block;
}

.dot.active::before {
  border-color: #00BFA5;
  width: 20px;
  height: 20px;
  box-shadow: 0 0 0 3px rgba(0, 191, 165, 0.15);
}

.dot.active .dot-inner {
  width: 6px;
  height: 6px;
  background: #00BFA5;
  box-shadow: 0 0 8px rgba(0, 191, 165, 0.6);
}

/* 页面内元素进场/离场动画，通过 .anim-item 标记需要动画的元素 */

:global(.page-enter-down .anim-item) {
  animation: enterFromBottom 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: 0.5s;
}

:global(.page-enter-up .anim-item) {
  animation: enterFromTop 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: 0.5s;
}

:global(.page-leave-up .anim-item) {
  animation: leaveToTop 0.32s cubic-bezier(0.76, 0, 0.24, 1) both;
}

:global(.page-leave-down .anim-item) {
  animation: leaveToBottom 0.32s cubic-bezier(0.76, 0, 0.24, 1) both;
}

/* 子元素依次错开出现（仅进场） */
:global(.page-enter-down .anim-item:nth-child(1)),
:global(.page-enter-up .anim-item:nth-child(1)) { animation-delay: 0.5s; }
:global(.page-enter-down .anim-item:nth-child(2)),
:global(.page-enter-up .anim-item:nth-child(2)) { animation-delay: 0.62s; }
:global(.page-enter-down .anim-item:nth-child(3)),
:global(.page-enter-up .anim-item:nth-child(3)) { animation-delay: 0.74s; }
:global(.page-enter-down .anim-item:nth-child(4)),
:global(.page-enter-up .anim-item:nth-child(4)) { animation-delay: 0.86s; }
:global(.page-enter-down .anim-item:nth-child(5)),
:global(.page-enter-up .anim-item:nth-child(5)) { animation-delay: 0.98s; }

@keyframes enterFromBottom {
  from { opacity: 0; transform: translateY(36px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes enterFromTop {
  from { opacity: 0; transform: translateY(-36px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes leaveToTop {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-24px); }
}

@keyframes leaveToBottom {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(24px); }
}

@media (max-width: 768px) {
  .fullpage-dots {
    right: unset;
    top: unset;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    flex-direction: row;
  }
  .dot.active::before {
    width: 16px;
    height: 16px;
  }
}
</style>
