<script setup lang="ts">
/**
 * Props-driven carousel display.
 *
 * Wraps `embla-carousel-vue` to render a slideshow of image / html / text
 * slides. Read-only — no `modelValue`. Supports looping, autoplay (via a
 * lightweight `setInterval`, since `embla-carousel-autoplay` is not a
 * dependency here), prev/next arrows, and dot indicators.
 *
 *   <CarouselDisplay :slides="[{ src: '/a.jpg', alt: 'A' }, { src: '/b.jpg' }]" />
 */
import { computed, onBeforeUnmount, ref, watch } from "vue"
import emblaCarouselVue from "embla-carousel-vue"
import { ChevronLeft, ChevronRight } from "@lucide/vue"
import { cn } from "@/lib/utils"

export interface CarouselSlide {
  src?: string
  alt?: string
  caption?: string
  html?: string
}

export interface CarouselDisplayProps {
  slides?: CarouselSlide[]
  loop?: boolean
  autoplay?: boolean
  autoplayDelayMs?: number
  showIndicators?: boolean
  showArrows?: boolean
  classList?: string[] | string
}

const props = withDefaults(defineProps<CarouselDisplayProps>(), {
  slides: () => [],
  loop: true,
  autoplay: false,
  autoplayDelayMs: 5000,
  showIndicators: true,
  showArrows: true,
})

const [emblaRef, emblaApi] = emblaCarouselVue(
  computed(() => ({ loop: props.loop })),
)

const selectedIndex = ref<number>(0)
const slideCount = computed<number>(() => props.slides.length)

function onSelect(): void {
  const api = emblaApi.value
  if (!api) {
    return
  }
  selectedIndex.value = api.selectedScrollSnap()
}

watch(emblaApi, (api) => {
  if (!api) {
    return
  }
  onSelect()
  api.on("select", onSelect)
  api.on("reInit", onSelect)
})

let autoplayTimer: ReturnType<typeof setInterval> | null = null

function clearAutoplay(): void {
  if (autoplayTimer !== null) {
    clearInterval(autoplayTimer)
    autoplayTimer = null
  }
}

function startAutoplay(): void {
  clearAutoplay()
  if (!props.autoplay || slideCount.value <= 1) {
    return
  }
  autoplayTimer = setInterval(() => {
    emblaApi.value?.scrollNext()
  }, props.autoplayDelayMs)
}

watch(
  () => [props.autoplay, props.autoplayDelayMs, slideCount.value] as const,
  () => {
    startAutoplay()
  },
  { immediate: true },
)

onBeforeUnmount(() => {
  clearAutoplay()
})

function scrollPrev(): void {
  emblaApi.value?.scrollPrev()
}

function scrollNext(): void {
  emblaApi.value?.scrollNext()
}

function scrollTo(index: number): void {
  emblaApi.value?.scrollTo(index)
}

const rootClass = computed<string>(() => {
  const cl = props.classList
  if (!cl) {
    return ""
  }
  return Array.isArray(cl) ? cl.join(" ") : cl
})
</script>

<template>
  <div
    :class="cn('relative w-full overflow-hidden', rootClass)"
    data-testid="carousel-display"
  >
    <div
      v-if="slideCount === 0"
      class="flex h-32 w-full items-center justify-center rounded border border-dashed bg-muted text-sm text-muted-foreground"
      data-testid="carousel-display-empty"
    >
      No slides
    </div>
    <template v-else>
      <div ref="emblaRef" class="overflow-hidden" data-testid="carousel-viewport">
        <div class="flex">
          <div
            v-for="(slide, index) in slides"
            :key="index"
            class="min-w-0 flex-[0_0_100%]"
            data-testid="carousel-slide"
          >
            <img
              v-if="slide.src"
              :src="slide.src"
              :alt="slide.alt ?? ''"
              class="block h-auto w-full rounded"
              loading="lazy"
            >
            <div
              v-else-if="slide.html"
              class="h-full w-full"
              v-html="slide.html"
            />
            <div
              v-else
              class="flex h-full w-full items-center justify-center p-6 text-center text-sm"
            >
              {{ slide.caption ?? "" }}
            </div>
            <p
              v-if="slide.src && slide.caption"
              class="mt-2 text-sm text-muted-foreground"
              data-testid="carousel-slide-caption"
            >
              {{ slide.caption }}
            </p>
          </div>
        </div>
      </div>

      <template v-if="showArrows && slideCount > 1">
        <button
          type="button"
          class="absolute left-2 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-full bg-background/70 text-foreground shadow ring-1 ring-border hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          aria-label="Previous slide"
          data-testid="carousel-prev"
          @click="scrollPrev"
        >
          <ChevronLeft class="size-4" />
        </button>
        <button
          type="button"
          class="absolute right-2 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-full bg-background/70 text-foreground shadow ring-1 ring-border hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          aria-label="Next slide"
          data-testid="carousel-next"
          @click="scrollNext"
        >
          <ChevronRight class="size-4" />
        </button>
      </template>

      <div
        v-if="showIndicators && slideCount > 1"
        class="mt-2 flex items-center justify-center gap-1.5"
        data-testid="carousel-indicators"
      >
        <button
          v-for="(_, index) in slides"
          :key="index"
          type="button"
          class="h-2 w-2 rounded-full bg-muted-foreground/40 transition-colors hover:bg-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          :class="index === selectedIndex ? 'bg-primary' : ''"
          :aria-label="`Go to slide ${index + 1}`"
          :aria-current="index === selectedIndex ? 'true' : undefined"
          data-testid="carousel-indicator"
          @click="scrollTo(index)"
        />
      </div>
    </template>
  </div>
</template>
