import { useEffect, useMemo, useRef, useState } from 'preact/hooks'
import h from '../shared/h.js'

export function Carousel ({
  _model = {},
  children,
  autoplay: propAutoplay,
  interval: propInterval,
  dots: propDots,
  ariaLabel: propAriaLabel,
  ...props
}) {
  const autoplay = propAutoplay ?? _model.autoplay ?? false
  const interval = Math.max(1000, Number(propInterval ?? _model.interval ?? 5000) || 5000)
  const dots = propDots ?? _model.dots ?? true
  const ariaLabel = propAriaLabel ?? _model.ariaLabel ?? 'Carousel'

  const items = useMemo(() => {
    if (Array.isArray(children)) {
      return children.filter(Boolean)
    }

    return children ? [children] : []
  }, [children])

  const [index, setIndex] = useState(0)
  const [isDragging, setIsDragging] = useState(false)
  const timerRef = useRef(null)
  const trackRef = useRef(null)
  const dragStartX = useRef(0)
  const dragOffsetX = useRef(0)
  const dragActive = useRef(false)
  const rafRef = useRef(null)

  useEffect(() => {
    if (items.length === 0) {
      return
    }

    if (index > items.length - 1) {
      setIndex(0)
    }
  }, [index, items.length])

  useEffect(() => {
    if (!autoplay || items.length < 2) {
      return
    }

    timerRef.current = setInterval(() => {
      setIndex(current => (current + 1) % items.length)
    }, interval)

    return () => {
      clearInterval(timerRef.current)
    }
  }, [autoplay, interval, items.length])

  if (items.length === 0) {
    return null
  }

  const snapToIndex = targetIndex => setIndex(Math.max(0, Math.min(targetIndex, items.length - 1)))

  const handlePointerDown = event => {
    if ((event.button && event.button !== 0) || event.target.closest('a, button')) {
      return
    }

    dragActive.current = true
    dragStartX.current = event.clientX
    dragOffsetX.current = 0
    setIsDragging(true)
    event.currentTarget.setPointerCapture?.(event.pointerId)
    if (trackRef.current) {
      trackRef.current.style.transition = 'none'
    }
  }

  const handlePointerMove = event => {
    if (!dragActive.current || !trackRef.current) {
      return
    }

    dragOffsetX.current = event.clientX - dragStartX.current

    if (rafRef.current) {
      cancelAnimationFrame(rafRef.current)
    }

    rafRef.current = requestAnimationFrame(() => {
      if (trackRef.current) {
        trackRef.current.style.transform = `translateX(calc(-${index * 100}% + ${dragOffsetX.current}px))`
      }
    })
  }

  const handlePointerUp = event => {
    if (!dragActive.current) {
      return
    }

    dragActive.current = false
    setIsDragging(false)
    event.currentTarget.releasePointerCapture?.(event.pointerId)

    const threshold = 60
    if (Math.abs(dragOffsetX.current) > threshold) {
      if (dragOffsetX.current < 0) {
        setIndex(current => (current + 1) % items.length)
      } else {
        setIndex(current => (current - 1 + items.length) % items.length)
      }
    } else {
      snapToIndex(index)
    }

    dragOffsetX.current = 0
    if (trackRef.current) {
      trackRef.current.style.transition = 'transform 600ms ease'
    }

    if (rafRef.current) {
      cancelAnimationFrame(rafRef.current)
      rafRef.current = null
    }
  }

  const progressPercent = items.length > 0 ? ((index + 1) / items.length) * 100 : 0

  return h('div', {
    ...props,
    role: 'region',
    'aria-roledescription': 'carousel',
    'aria-label': ariaLabel,
    className: 'w-full relative',
    children: [
      h('div', {
        className: 'relative overflow-hidden',
        children: [
          h('div', {
            className: 'flex w-full cursor-grab active:cursor-grabbing select-none',
            ref: trackRef,
            style: {
              transform: `translateX(-${index * 100}%)`,
              transition: isDragging ? 'none' : 'transform 600ms ease',
              touchAction: 'pan-y',
              willChange: 'transform'
            },
            onPointerDown: handlePointerDown,
            onPointerMove: handlePointerMove,
            onPointerUp: handlePointerUp,
            onPointerCancel: handlePointerUp,
            onPointerLeave: handlePointerUp,
            children: items.map((item, itemIndex) => h('div', {
              className: 'min-w-full',
              'aria-hidden': index !== itemIndex,
              children: item
            }))
          }),
          null
        ]
      }),
      dots && items.length > 1 && h('div', {
        className: 'flex justify-center gap-2 sm:gap-3 mt-4',
        children: items.map((_item, itemIndex) => h('button', {
          type: 'button',
          'aria-label': `Go to slide ${itemIndex + 1}`,
          className: `h-5 w-5 sm:h-5 sm:w-5 flex items-center justify-center rounded-full ${index === itemIndex ? 'bg-bga' : 'bg-bgb/30 hover:bg-bgb'}`,
          onClick: () => setIndex(itemIndex),
          children: h('span', {
            className: `block h-1 w-1 sm:h-3 sm:w-3 rounded-full ${index === itemIndex ? 'bg-txa' : 'bg-txa/50'}`
          })
        }))
      })
    ]
  })
}
