import { Image } from 'expo-image'
import { useCallback, useEffect, useMemo } from 'react'
import type { PanGesture } from 'react-native-gesture-handler'
import type { SharedValue } from 'react-native-reanimated'
import Carousel from 'react-native-reanimated-carousel'
import MediaPagination from 'src/components/common/media/MediaPagination'
import type { MediaAttachment } from 'src/lib/api-types'
import { toImageUri } from 'src/lib/media-source'
import { YStack } from 'tamagui'

// Shared album carousel behind FeedPost's `PostAlbumMedia` (full-bleed feed)
// and StatusMediaPreview's `AlbumPreview` (embedded quoted-status / comment
// cards). Both surfaces previously hand-rolled an identical Carousel + pan
// config + 25-slice cap + adjacent-slide prefetch + MediaPagination block;
// this is the single source.
//
// Each surface supplies its own slide leaf via `renderSlide`, because the tap
// behaviour differs: the feed renders bare images under the post cell's shared
// tap / double-tap gesture (handled by FeedPost's outer GestureDetector), while
// embedded cards wrap each slide in a press target that opens the lightbox.
//
// `progress` is owned by the caller, never created here: FeedPost resets it to
// slide 0 on FlashList cell recycle, and PostActions reads it for the active
// slide's alt text. A SharedValue created inside this component would break
// both of those.

export type MediaCarouselSlide = {
  item: MediaAttachment
  index: number
  width: number
  height: number
}

type Props = {
  /** Full attachment list; sliced to the 25-slide cap internally. */
  media: MediaAttachment[]
  width: number
  height: number
  progress: SharedValue<number>
  // Returns a single element (the Carousel's `renderItem` requires a
  // ReactElement, not the broader ReactNode), so each surface's slide leaf
  // must be wrapped in one node.
  renderSlide: (slide: MediaCarouselSlide) => React.ReactElement
  /** Embedded cards round + clip the carousel (a value — including 0 — opts
   * into `overflow:'hidden'`); the feed passes nothing and renders flush. */
  borderRadius?: number
}

// Pixelfed caps albums at 25 attachments; mirror that ceiling so a malformed
// payload can't mount an unbounded carousel.
const MAX_SLIDES = 25

// Keep the pan on the UI thread and hand off to the parent FlashList's vertical
// scroll after 8dp of vertical travel; 12dp of horizontal travel claims the
// swipe. Never set `.runOnJS(true)` — it dispatches every pan update to the JS
// thread, which is catastrophic for swipe feel.
function configurePan(g: PanGesture) {
  return g.activeOffsetX([-12, 12]).failOffsetY([-8, 8])
}

export default function MediaCarousel({
  media,
  width,
  height,
  progress,
  renderSlide,
  borderRadius,
}: Props) {
  const slides = useMemo(() => media.slice(0, MAX_SLIDES), [media])

  // Warm the decode cache for the next 1–2 slides so the first horizontal
  // swipe doesn't stall on image decode. `windowSize={2}` already mounts
  // [current, current+1] but its decode races the swipe on slower devices.
  // Bounded to 2 URLs to cap memory (expo/expo#40544 — memory-disk doesn't
  // auto-purge); the `memory-disk` policy matches the slide render so the
  // prefetched entry is the same one the carousel will hit.
  useEffect(() => {
    const urls = slides.slice(1, 3).map((m) => toImageUri(m))
    if (urls.length === 0) return
    Image.prefetch(urls, 'memory-disk').catch(() => {})
  }, [slides])

  const renderItem = useCallback(
    ({ index }: { index: number }) =>
      renderSlide({ item: slides[index], index, width, height }),
    [renderSlide, slides, width, height]
  )

  return (
    <YStack
      width={width}
      borderRadius={borderRadius}
      overflow={borderRadius != null ? 'hidden' : undefined}
      zIndex={1}
    >
      <Carousel
        onConfigurePanGesture={configurePan}
        width={width}
        height={height}
        vertical={false}
        loop={false}
        windowSize={2}
        onProgressChange={progress}
        data={slides}
        renderItem={renderItem}
      />
      <MediaPagination count={slides.length} progress={progress} />
    </YStack>
  )
}
