<script setup>
import {
  ref,
  computed,
  onMounted,
  onUnmounted,
  watch,
  provide,
  useSlots,
  shallowRef,
  markRaw
} from "vue";
import { useNamespace, useLocale } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { carouselProps, carouselEmits, CAROUSEL_INJECTION_KEY } from "./carousel";
defineOptions({
  name: "YhCarousel"
});
const props = defineProps(carouselProps);
const emit = defineEmits(carouselEmits);
const _slots = useSlots();
const ns = useNamespace("carousel");
const { t } = useLocale();
const { themeStyle } = useComponentTheme(
  "carousel",
  computed(() => props.themeOverrides)
);
const internalIndex = shallowRef(props.defaultIndex);
const prevIndex = shallowRef(-1);
const isHovering = shallowRef(false);
let itemIdCounter = 0;
const _getStableId = () => `yh-carousel-item-${itemIdCounter++}`;
const itemMap = /* @__PURE__ */ new Map();
const itemIds = ref([]);
const itemCount = computed(() => itemIds.value.length);
let rafId = null;
const timer = shallowRef(null);
const containerRef = shallowRef(null);
const renderBuffer = 2;
const visibleRange = computed(() => {
  const total = itemCount.value;
  if (total === 0) return { start: 0, end: 0 };
  const cur = internalIndex.value;
  const start = Math.max(0, cur - renderBuffer);
  const end = Math.min(total, cur + renderBuffer + 1);
  return { start, end };
});
const shouldRenderItem = (index) => {
  const { start, end } = visibleRange.value;
  return index >= start && index < end;
};
const currentIndex = computed({
  get: () => internalIndex.value,
  set: (val) => {
    const total = itemCount.value;
    if (total === 0) return;
    let next2 = val;
    if (val < 0) {
      next2 = props.loop ? total - 1 : 0;
    } else if (val >= total) {
      next2 = props.loop ? 0 : total - 1;
    }
    if (next2 !== internalIndex.value) {
      const prev2 = internalIndex.value;
      prevIndex.value = prev2;
      internalIndex.value = next2;
      emit("update:currentIndex", next2);
      emit("change", next2, prev2);
    }
  }
});
const trackStyle = computed(() => {
  if (props.effect !== "slide") {
    return {
      position: "relative",
      width: "100%",
      height: "100%",
      perspective: "1200px",
      transformStyle: "preserve-3d"
    };
  }
  const isVertical = props.direction === "vertical";
  const translateP = internalIndex.value * 100;
  const space = internalIndex.value * props.spaceBetween;
  const transform = isVertical ? `translate3d(0, calc(-${translateP}% - ${space}px), 0)` : `translate3d(calc(-${translateP}% - ${space}px), 0, 0)`;
  return {
    position: "relative",
    width: "100%",
    height: "100%",
    display: "flex",
    willChange: "transform",
    // GPU 提示
    transition: `transform ${props.duration}ms var(--yh-carousel-transition-timing)`,
    transform,
    flexDirection: isVertical ? "column" : "row",
    gap: `${props.spaceBetween}px`
  };
});
const startAutoplay = () => {
  if (!props.autoplay || props.interval <= 0) return;
  stopAutoplay();
  const isTest = typeof globalThis !== "undefined" && "vi" in globalThis;
  if (isTest) {
    timer.value = setInterval(() => {
      if (props.pauseOnHover && isHovering.value) return;
      currentIndex.value++;
    }, props.interval);
  } else {
    timer.value = 1;
    let lastTime = performance.now();
    const tick = (currentTime) => {
      if (timer.value === null) return;
      if (currentTime - lastTime >= props.interval) {
        if (!(props.pauseOnHover && isHovering.value)) {
          currentIndex.value++;
        }
        lastTime = currentTime;
      }
      rafId = requestAnimationFrame(tick);
    };
    rafId = requestAnimationFrame(tick);
  }
};
const stopAutoplay = () => {
  if (rafId !== null) {
    cancelAnimationFrame(rafId);
    rafId = null;
  }
  timer.value = null;
};
const prev = () => currentIndex.value--;
const next = () => currentIndex.value++;
const jump = (index) => {
  currentIndex.value = index;
};
let keydownRaf = null;
const handleKeyDown = (e) => {
  if (!props.keyboard) return;
  if (keydownRaf !== null) return;
  keydownRaf = requestAnimationFrame(() => {
    keydownRaf = null;
    if (e.key === "ArrowLeft" || e.key === "ArrowUp") prev();
    if (e.key === "ArrowRight" || e.key === "ArrowDown") next();
  });
};
let wheelRaf = null;
const handleWheel = (e) => {
  if (!props.mousewheel) return;
  e.preventDefault();
  if (wheelRaf !== null) return;
  wheelRaf = requestAnimationFrame(() => {
    wheelRaf = null;
    if (e.deltaY > 0) next();
    else if (e.deltaY < 0) prev();
  });
};
const handleDotTrigger = (index, type) => {
  if (props.dotTrigger === type) jump(index);
};
const getCurrentIndex = () => internalIndex.value;
const effectiveDotPlacement = computed(() => props.dotPlacement);
const dotsAtClass = computed(() => {
  const p = props.dotPlacement;
  const side = p.replace(/^inner-/, "");
  return side === "left" || side === "right" || side === "top" || side === "bottom" ? `dots-at-${side}` : "";
});
defineExpose({
  prev,
  next,
  jump,
  to: jump,
  getCurrentIndex,
  currentIndex: internalIndex
});
provide(
  CAROUSEL_INJECTION_KEY,
  markRaw({
    props,
    itemCount,
    currentIndex: internalIndex,
    prevIndex,
    direction: computed(() => props.direction),
    effect: computed(() => props.effect),
    shouldRenderItem,
    // 新增：判断是否渲染
    addItem: (id) => {
      if (!itemIds.value.includes(id)) {
        itemIds.value.push(id);
        itemMap.set(id, { index: itemIds.value.length - 1 });
      }
    },
    removeItem: (id) => {
      const idx = itemIds.value.indexOf(id);
      if (idx !== -1) {
        itemIds.value.splice(idx, 1);
        itemMap.delete(id);
        itemIds.value.forEach((itemId, i) => {
          const item = itemMap.get(itemId);
          if (item) item.index = i;
        });
      }
    },
    getItemIndex: (id) => {
      const item = itemMap.get(id);
      return item ? item.index : -1;
    },
    jump
  })
);
onMounted(() => {
  startAutoplay();
  if (props.keyboard) {
    window.addEventListener("keydown", handleKeyDown);
  }
});
onUnmounted(() => {
  stopAutoplay();
  if (keydownRaf !== null) cancelAnimationFrame(keydownRaf);
  if (wheelRaf !== null) cancelAnimationFrame(wheelRaf);
  window.removeEventListener("keydown", handleKeyDown);
});
watch(
  () => props.autoplay,
  (val) => {
    if (val) {
      startAutoplay();
    } else {
      stopAutoplay();
    }
  }
);
</script>

<template>
  <div
    ref="containerRef"
    :class="[ns.b(), ns.m(props.direction), ns.m(props.effect), {
  'has-arrow': props.showArrow !== 'never' && itemCount > 1
}, dotsAtClass]"
    :style="themeStyle"
    @mouseenter="isHovering = true"
    @mouseleave="isHovering = false"
    @wheel="handleWheel"
  >
    <!-- 轮播主轨道 -->
    <div
      :class="[ns.e('slides'), ns.is('vertical', props.direction === 'vertical')]"
      :style="trackStyle"
    >
      <slot />
    </div>

    <!-- 指示点 -->
    <slot
      name="dots"
      v-if="props.showDots && itemCount > 0"
      :total="itemCount"
      :current-index="internalIndex"
      :to="jump"
    >
      <div
        :class="[ns.e('dots'), ns.em('dots', effectiveDotPlacement), ns.em('dots', props.dotType)]"
      >
        <div
          v-for="i in itemCount"
          :key="i"
          :class="[ns.e('dots-item'), ns.is('active', internalIndex === i - 1)]"
          @click="handleDotTrigger(i - 1, 'click')"
          @mouseenter="handleDotTrigger(i - 1, 'hover')"
        ></div>
      </div>
    </slot>

    <!-- 左右箭头 -->
    <slot
      name="arrow"
      v-if="props.showArrow !== 'never' && itemCount > 1"
      :total="itemCount"
      :current-index="internalIndex"
      :to="jump"
      :prev="prev"
      :next="next"
    >
      <div
        :class="[ns.e('arrow'), ns.em('arrow', 'prev'), ns.is('visible', props.showArrow === 'always' || props.showArrow === 'hover' && isHovering), ns.is('vertical', props.direction === 'vertical')]"
        :aria-label="t('carousel.prev')"
        @click="prev"
      >
        <slot name="prev-arrow">
          <svg
            viewBox="0 0 1024 1024"
            :width="20"
            :height="20"
            fill="currentColor"
            :class="ns.e('arrow-icon')"
          >
            <path
              d="M603.3 172.7c11-10.4 28.5-9.9 38.9 1.1s9.9 28.5-1.1 38.9L322.6 512l318.5 299.3c11 10.4 11.5 27.9 1.1 38.9s-27.9 11.5-38.9 1.1L256 512l347.3-339.3z"
            />
          </svg>
        </slot>
      </div>
      <div
        :class="[ns.e('arrow'), ns.em('arrow', 'next'), ns.is('visible', props.showArrow === 'always' || props.showArrow === 'hover' && isHovering), ns.is('vertical', props.direction === 'vertical')]"
        :aria-label="t('carousel.next')"
        @click="next"
      >
        <slot name="next-arrow">
          <svg
            viewBox="0 0 1024 1024"
            :width="20"
            :height="20"
            fill="currentColor"
            :class="ns.e('arrow-icon')"
          >
            <path
              d="M420.7 851.3c-11 10.4-28.5 9.9-38.9-1.1s-9.9-28.5 1.1-38.9L701.4 512 382.9 212.7c-11-10.4-11.5-27.9-1.1-38.9s27.9-11.5 38.9-1.1L768 512l-347.3 339.3z"
            />
          </svg>
        </slot>
      </div>
    </slot>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-carousel {
  --yh-carousel-dot-color: rgba(255, 255, 255, 0.4);
  --yh-carousel-dot-active-color: #ffffff;
  --yh-carousel-dot-size: 8px;
  --yh-carousel-dot-active-width: 24px;
  --yh-carousel-arrow-bg: rgba(255, 255, 255, 0.2);
  --yh-carousel-arrow-hover-bg: rgba(255, 255, 255, 0.35);
  --yh-carousel-arrow-color: #ffffff;
  --yh-carousel-arrow-size: 36px;
  --yh-carousel-arrow-icon-size: 20px;
  --yh-carousel-transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  user-select: none;
  background-color: transparent;
}
.yh-carousel__slides {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  transition-timing-function: var(--yh-carousel-transition-timing);
  will-change: transform;
}
.yh-carousel__slides.is-vertical {
  flex-direction: column;
}

.yh-carousel__dots {
  position: absolute;
  z-index: 20;
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: center;
  padding: 12px;
  pointer-events: none;
}
.yh-carousel__dots--bottom {
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
}

.yh-carousel__dots--top {
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
}

.yh-carousel__dots--left {
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  flex-direction: column;
}

.yh-carousel__dots--right {
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  flex-direction: column;
}

.yh-carousel__dots--inner-bottom {
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}

.yh-carousel__dots--inner-top {
  top: 0;
  left: 50%;
  transform: translateX(-50%);
}

.yh-carousel__dots--inner-left {
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  flex-direction: column;
}

.yh-carousel__dots--inner-right {
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  flex-direction: column;
}

.yh-carousel__dots-item {
  width: var(--yh-carousel-dot-size);
  height: var(--yh-carousel-dot-size);
  border-radius: calc(var(--yh-carousel-dot-size) / 2);
  background-color: var(--yh-carousel-dot-color);
  cursor: pointer;
  pointer-events: auto;
  transition: all 0.3s var(--yh-carousel-transition-timing);
}
.yh-carousel__dots-item:hover {
  background-color: var(--yh-carousel-dot-active-color);
  opacity: 0.8;
}
.yh-carousel__dots-item.is-active {
  width: var(--yh-carousel-dot-active-width);
  background-color: var(--yh-carousel-dot-active-color);
}

.yh-carousel {
  --yh-carousel-arrow-zone: 52px;
}
.yh-carousel:not(.yh-carousel--vertical).has-arrow.dots-at-left .yh-carousel__arrow--prev {
  left: var(--yh-carousel-arrow-zone);
}
.yh-carousel:not(.yh-carousel--vertical).has-arrow.dots-at-right .yh-carousel__arrow--next {
  right: var(--yh-carousel-arrow-zone);
}
.yh-carousel__arrow {
  position: absolute;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--yh-carousel-arrow-size);
  height: var(--yh-carousel-arrow-size);
  border-radius: 50%;
  background-color: var(--yh-carousel-arrow-bg);
  color: var(--yh-carousel-arrow-color);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: opacity 0.3s ease, transform 0.3s ease, background-color 0.3s ease;
  opacity: 0;
  --arrow-inset: 20px;
}
.yh-carousel__arrow:hover {
  background-color: var(--yh-carousel-arrow-hover-bg);
  /* 不添加 transform，避免横向/纵向箭头 hover 时出现下移或位移 */
}
.yh-carousel__arrow--prev {
  left: var(--arrow-inset);
  top: 50%;
  transform: translateY(-50%);
}

.yh-carousel__arrow--next {
  right: var(--arrow-inset);
  top: 50%;
  transform: translateY(-50%);
}

.yh-carousel__arrow.is-visible {
  opacity: 1;
}

.yh-carousel__arrow-icon {
  width: var(--yh-carousel-arrow-icon-size);
  height: var(--yh-carousel-arrow-icon-size);
  flex-shrink: 0;
}

.yh-carousel.yh-carousel--vertical .yh-carousel__arrow--prev {
  left: 50%;
  right: auto;
  top: var(--arrow-inset, 20px);
  bottom: auto;
  transform: translateX(-50%);
}
.yh-carousel.yh-carousel--vertical .yh-carousel__arrow--next {
  left: 50%;
  right: auto;
  top: auto;
  bottom: var(--arrow-inset, 20px);
  transform: translateX(-50%);
}
.yh-carousel.yh-carousel--vertical.has-arrow.dots-at-top .yh-carousel__arrow--prev {
  top: var(--yh-carousel-arrow-zone);
}
.yh-carousel.yh-carousel--vertical.has-arrow.dots-at-bottom .yh-carousel__arrow--next {
  bottom: var(--yh-carousel-arrow-zone);
}
.yh-carousel.yh-carousel--vertical .yh-carousel__arrow--prev:hover,
.yh-carousel.yh-carousel--vertical .yh-carousel__arrow--next:hover {
  /* 仅背景色变化，无位移/缩放 */
  transform: translateX(-50%);
}
.yh-carousel.yh-carousel--vertical .yh-carousel__arrow--prev .yh-carousel__arrow-icon {
  transform: rotate(-90deg); /* 左箭头逆时针90° → 指向上 */
}
.yh-carousel.yh-carousel--vertical .yh-carousel__arrow--next .yh-carousel__arrow-icon {
  transform: rotate(-90deg); /* 右箭头逆时针90° → 指向下 */
}
.yh-carousel:hover .yh-carousel__arrow--hover {
  opacity: 1;
}
.yh-carousel:hover .yh-carousel__arrow--hover.yh-carousel-arrow--prev {
  transform: translateY(-50%) translateX(0);
}
.yh-carousel:hover .yh-carousel__arrow--hover.yh-carousel-arrow--next {
  transform: translateY(-50%) translateX(0);
}

.yh-carousel__arrow-always {
  opacity: 1;
}
.yh-carousel__arrow-always--prev {
  transform: translateY(-50%) translateX(0);
}

.yh-carousel__arrow-always--next {
  transform: translateY(-50%) translateX(0);
}

.yh-carousel-item {
  flex-shrink: 0;
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
}
.yh-carousel-item.is-fade {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  z-index: 1;
  transition: opacity var(--yh-carousel-transition-timing);
}
.yh-carousel-item.is-fade.is-active {
  opacity: 1;
  z-index: 2;
}

.yh-carousel-item.is-card {
  position: absolute;
  top: 0;
  left: 0;
  transition: transform 0.4s var(--yh-carousel-transition-timing), opacity 0.4s ease;
  opacity: 0;
  z-index: 1;
}
.yh-carousel-item.is-card.is-in-stage {
  opacity: 1;
  z-index: 2;
  cursor: pointer;
}

.yh-carousel-item.is-card.is-active {
  z-index: 10;
}

/* Dot Type: Line 支持 */
.yh-carousel__dots--line .yh-carousel__dots-item {
  width: 24px;
  height: 4px;
  border-radius: 2px;
  background-color: var(--yh-carousel-dot-color, rgba(255, 255, 255, 0.4));
  transition: all 0.3s;
}
.yh-carousel__dots--line .yh-carousel__dots-item.is-active {
  width: 32px;
  background-color: var(--yh-carousel-dot-active-color, #fff);
}

/* left/right 的 line 为纵向条 */
.yh-carousel__dots--left.yh-carousel__dots--line,
.yh-carousel__dots--right.yh-carousel__dots--line {
  transform: translateY(-50%);
}
.yh-carousel__dots--left.yh-carousel__dots--line .yh-carousel__dots-item,
.yh-carousel__dots--right.yh-carousel__dots--line .yh-carousel__dots-item {
  width: 4px;
  height: 24px;
  border-radius: 2px;
}
.yh-carousel__dots--left.yh-carousel__dots--line .yh-carousel__dots-item.is-active,
.yh-carousel__dots--right.yh-carousel__dots--line .yh-carousel__dots-item.is-active {
  width: 4px;
  height: 32px;
}
</style>
