'use client'

import styles from './styles.module.scss'
import Image, { StaticImageData } from 'next/image'
import {
  CSSProperties,
  MouseEvent as ReactMouseEvent,
  Ref,
  TouchEvent as ReactTouchEvent,
  useCallback,
  useEffect,
  useRef,
  useState,
} from 'react'

type ImageType =
  | {
      src: string
      width: number
      height: number
    }
  | StaticImageData

export type GalleryItem = {
  id: string
  thumbnail: ImageType
  image: ImageType
  alt: string
  caption?: string
}

type GalleryProps = {
  items: GalleryItem[]
}

/** Horizontal gap between thumbnails — keep in sync with `$gap` in styles.module.scss. */
const GAP = 30
/** Autoscroll speed in pixels per second. */
const SPEED = 30
/** Pointer travel (px) past which a press counts as a drag, not a click. */
const DRAG_THRESHOLD = 5
/** Minimum swipe distance (px) to trigger navigation / close in the lightbox. */
const SWIPE_THRESHOLD = 50
/** Fade duration (ms) — keep in sync with the animations in styles.module.scss. */
const FADE_MS = 100

export const Gallery = ({ items }: GalleryProps) => {
  const [lightboxIndex, setLightboxIndex] = useState<number | null>(null)
  // Render a second copy of the strip only once we know it should loop (client, motion-ok, overflowing).
  const [loop, setLoop] = useState(false)

  const wrapperRef = useRef<HTMLDivElement>(null)
  const firstGroupRef = useRef<HTMLDivElement>(null)
  const pausedRef = useRef(false)
  const lightboxOpenRef = useRef(false)
  const dragRef = useRef({ active: false, startX: 0, scrollStart: 0, moved: false })

  const close = useCallback(() => setLightboxIndex(null), [])
  const showNext = useCallback(
    () => setLightboxIndex((i) => (i === null ? i : (i + 1) % items.length)),
    [items.length],
  )
  const showPrev = useCallback(
    () => setLightboxIndex((i) => (i === null ? i : (i - 1 + items.length) % items.length)),
    [items.length],
  )

  // Keep the autoscroll loop paused while the lightbox is open.
  useEffect(() => {
    lightboxOpenRef.current = lightboxIndex !== null
  }, [lightboxIndex])

  // Decide whether the strip should autoscroll/loop at all.
  useEffect(() => {
    const wrapper = wrapperRef.current
    if (!wrapper || items.length < 2) return
    const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches
    if (prefersReduced) return
    if (wrapper.scrollWidth <= wrapper.clientWidth) return
    setLoop(true)
  }, [items.length])

  // Continuous, seamless autoscroll via requestAnimationFrame on the duplicated strip.
  useEffect(() => {
    if (!loop) return
    const wrapper = wrapperRef.current
    const firstGroup = firstGroupRef.current
    if (!wrapper || !firstGroup) return

    let raf = 0
    let last: number | null = null
    const step = (now: number) => {
      if (last !== null && !pausedRef.current && !lightboxOpenRef.current) {
        const dt = (now - last) / 1000
        wrapper.scrollLeft += SPEED * dt
        const period = firstGroup.offsetWidth + GAP
        if (period > 0 && wrapper.scrollLeft >= period) {
          wrapper.scrollLeft -= period
        }
      }
      last = now
      raf = requestAnimationFrame(step)
    }
    raf = requestAnimationFrame(step)
    return () => cancelAnimationFrame(raf)
  }, [loop])

  // --- Pointer drag-to-scroll (mouse). Touch uses native overflow scrolling. ---
  const onMouseEnter = useCallback(() => {
    pausedRef.current = true
  }, [])
  const onMouseLeave = useCallback(() => {
    pausedRef.current = false
    dragRef.current.active = false
  }, [])
  const onMouseDown = useCallback((e: ReactMouseEvent) => {
    const wrapper = wrapperRef.current
    if (!wrapper) return
    dragRef.current = {
      active: true,
      startX: e.clientX,
      scrollStart: wrapper.scrollLeft,
      moved: false,
    }
  }, [])
  const onMouseMove = useCallback((e: ReactMouseEvent) => {
    const drag = dragRef.current
    const wrapper = wrapperRef.current
    if (!drag.active || !wrapper) return
    const dx = e.clientX - drag.startX
    if (Math.abs(dx) > DRAG_THRESHOLD) drag.moved = true
    wrapper.scrollLeft = drag.scrollStart - dx
  }, [])
  const onMouseUp = useCallback(() => {
    dragRef.current.active = false
  }, [])

  // Touch: pause autoscroll while a finger is down; native scrolling moves the strip.
  const onTouchStart = useCallback(() => {
    pausedRef.current = true
  }, [])
  const onTouchEnd = useCallback(() => {
    pausedRef.current = false
  }, [])

  // Keyboard: pause autoscroll while a thumbnail is focused so it can be activated (WCAG 2.2.2).
  const onFocus = useCallback(() => {
    pausedRef.current = true
  }, [])
  const onBlur = useCallback(() => {
    pausedRef.current = false
  }, [])

  const handleThumbClick = useCallback((index: number) => {
    // Ignore the click that ends a drag.
    if (dragRef.current.moved) return
    setLightboxIndex(index)
  }, [])

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

  const renderGroup = (copy: number, ref?: Ref<HTMLDivElement>, hidden = false) => (
    <div className={styles.group} ref={ref} aria-hidden={hidden || undefined}>
      {items.map((item, index) => (
        <Image
          key={`${item.id}-${copy}`}
          className={styles.item}
          src={item.thumbnail.src}
          width={item.thumbnail.width}
          height={item.thumbnail.height}
          alt={item.alt}
          unoptimized
          draggable={false}
          tabIndex={hidden ? -1 : 0}
          role="button"
          aria-haspopup="dialog"
          onClick={() => handleThumbClick(index)}
          onKeyDown={(e) => {
            if (e.key === 'Enter' || e.key === ' ') {
              e.preventDefault()
              handleThumbClick(index)
            }
          }}
        />
      ))}
    </div>
  )

  return (
    <>
      <div
        className={styles.wrapper}
        ref={wrapperRef}
        onMouseEnter={onMouseEnter}
        onMouseLeave={onMouseLeave}
        onMouseDown={onMouseDown}
        onMouseMove={onMouseMove}
        onMouseUp={onMouseUp}
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}
        onFocus={onFocus}
        onBlur={onBlur}
      >
        <div className={styles.track}>
          {renderGroup(0, firstGroupRef)}
          {loop && renderGroup(1, undefined, true)}
        </div>
      </div>

      {lightboxIndex !== null && (
        <Lightbox
          item={items[lightboxIndex]}
          index={lightboxIndex}
          total={items.length}
          hasMultiple={items.length > 1}
          onClose={close}
          onNext={showNext}
          onPrev={showPrev}
        />
      )}
    </>
  )
}

type LightboxProps = {
  item: GalleryItem
  index: number
  total: number
  hasMultiple: boolean
  onClose: () => void
  onNext: () => void
  onPrev: () => void
}

// Visually hide content while keeping it available to assistive technology.
const srOnly: CSSProperties = {
  position: 'absolute',
  width: 1,
  height: 1,
  padding: 0,
  margin: -1,
  overflow: 'hidden',
  clipPath: 'inset(50%)',
  whiteSpace: 'nowrap',
  border: 0,
}

const Lightbox = ({ item, index, total, hasMultiple, onClose, onNext, onPrev }: LightboxProps) => {
  const dialogRef = useRef<HTMLDivElement>(null)
  const touchRef = useRef({ x: 0, y: 0 })
  const closeTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)

  // Play the exit fade before the parent unmounts us.
  const [closing, setClosing] = useState(false)

  // Fade the whole lightbox out, then actually close once the animation ends.
  const requestClose = useCallback(() => {
    setClosing((alreadyClosing) => {
      if (!alreadyClosing) closeTimer.current = setTimeout(onClose, FADE_MS)
      return true
    })
  }, [onClose])

  // Lock body scroll and restore focus on close.
  useEffect(() => {
    const previouslyFocused = document.activeElement as HTMLElement | null
    const previousOverflow = document.body.style.overflow
    document.body.style.overflow = 'hidden'
    dialogRef.current?.focus()
    return () => {
      if (closeTimer.current) clearTimeout(closeTimer.current)
      document.body.style.overflow = previousOverflow
      previouslyFocused?.focus?.()
    }
  }, [])

  // Keyboard navigation + focus trap (keep Tab within the modal — WCAG 2.4.3).
  useEffect(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        requestClose()
      } else if (e.key === 'ArrowRight') {
        onNext()
      } else if (e.key === 'ArrowLeft') {
        onPrev()
      } else if (e.key === 'Tab') {
        const dialog = dialogRef.current
        if (!dialog) return
        const focusables = Array.from(dialog.querySelectorAll<HTMLElement>('button'))
        if (focusables.length === 0) {
          e.preventDefault()
          return
        }
        const first = focusables[0]
        const last = focusables[focusables.length - 1]
        const active = document.activeElement
        if (e.shiftKey && (active === first || active === dialog)) {
          e.preventDefault()
          last.focus()
        } else if (!e.shiftKey && active === last) {
          e.preventDefault()
          first.focus()
        }
      }
    }
    window.addEventListener('keydown', onKeyDown)
    return () => window.removeEventListener('keydown', onKeyDown)
  }, [requestClose, onNext, onPrev])

  const onTouchStart = (e: ReactTouchEvent) => {
    const t = e.changedTouches[0]
    touchRef.current = { x: t.clientX, y: t.clientY }
  }
  const onTouchEnd = (e: ReactTouchEvent) => {
    const t = e.changedTouches[0]
    const dx = t.clientX - touchRef.current.x
    const dy = t.clientY - touchRef.current.y
    if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > SWIPE_THRESHOLD) {
      if (dx < 0) onNext()
      else onPrev()
    } else if (dy > SWIPE_THRESHOLD && Math.abs(dy) > Math.abs(dx)) {
      requestClose()
    }
  }

  const stop = (e: ReactMouseEvent) => e.stopPropagation()

  return (
    <div
      className={`${styles.display} ${closing ? styles.closing : ''}`}
      ref={dialogRef}
      role="dialog"
      aria-modal="true"
      aria-label="Bildergalerie"
      tabIndex={-1}
      onClick={requestClose}
      onTouchStart={onTouchStart}
      onTouchEnd={onTouchEnd}
    >
      <div style={srOnly} aria-live="polite">
        {`Bild ${index + 1} von ${total}: ${item.alt}`}
      </div>

      <button
        type="button"
        className={styles.close}
        aria-label="Schließen"
        onClick={(e) => {
          stop(e)
          requestClose()
        }}
      >
        ×
      </button>

      {hasMultiple && (
        <button
          type="button"
          className={`${styles.nav} ${styles.prev}`}
          aria-label="Vorheriges Bild"
          onClick={(e) => {
            stop(e)
            onPrev()
          }}
        />
      )}

      <Image
        className={styles.displayImage}
        src={item.image.src}
        width={item.image.width}
        height={item.image.height}
        alt={item.alt}
        unoptimized
        onClick={stop}
      />

      {hasMultiple && (
        <button
          type="button"
          className={`${styles.nav} ${styles.next}`}
          aria-label="Nächstes Bild"
          onClick={(e) => {
            stop(e)
            onNext()
          }}
        />
      )}

      {item.caption && (
        <div className={styles.captionBar} onClick={stop}>
          {item.caption}
        </div>
      )}
    </div>
  )
}
