import {
  Children,
  isValidElement,
  useState,
  type ReactNode,
} from 'react'
import {ScrollView, View, type LayoutChangeEvent} from 'react-native'

import {atoms as a, tokens, type ViewStyleProp} from '@/sprinkles'

type CarouselProps = {
  children: ReactNode
  snap?: boolean
  gap?: number
  gutter?: number
  bleed?: boolean
  style?: ViewStyleProp
  contentContainerStyle?: ViewStyleProp
}

// horizontal scroller that snaps each item's leading edge to the start of
// the viewport. measures items so cards can be any width. `gutter` pads the
// row and, with `bleed`, cancels the parent's padding so items run to the
// screen edge. pass `snap={false}` for free-scrolling rows (eg chips)
export function Carousel({
  children,
  snap = true,
  gap = tokens.space.sm,
  gutter = tokens.space.md,
  bleed = true,
  style,
  contentContainerStyle,
}: CarouselProps) {
  const items = Children.toArray(children)
  const [offsets, setOffsets] = useState<number[]>([])

  const measure = (i: number) => (e: LayoutChangeEvent) => {
    const x = Math.max(0, e.nativeEvent.layout.x - gutter)
    setOffsets((prev) => {
      if (prev[i] === x) return prev
      const next = prev.slice()
      next[i] = x
      return next
    })
  }

  const snapOffsets = offsets.slice(0, items.length)
  const ready =
    snap &&
    items.length > 0 &&
    snapOffsets.length === items.length &&
    snapOffsets.every((o) => o != null)

  return (
    <ScrollView
      horizontal
      showsHorizontalScrollIndicator={false}
      decelerationRate={ready ? 'fast' : undefined}
      snapToOffsets={ready ? snapOffsets : undefined}
      style={[bleed && {marginHorizontal: -gutter}, style]}
      contentContainerStyle={[
        a.flex_row,
        {gap, paddingHorizontal: gutter},
        contentContainerStyle,
      ]}
    >
      {items.map((child, i) => (
        <View
          key={isValidElement(child) ? (child.key ?? i) : i}
          onLayout={snap ? measure(i) : undefined}
        >
          {child}
        </View>
      ))}
    </ScrollView>
  )
}
