import { clamp } from 'lodash'
import React, { useState, useRef } from 'react'
import {
  ScrollView,
  View,
  NativeSyntheticEvent,
  NativeScrollEvent,
  ViewStyle,
  StyleProp,
  useWindowDimensions,
} from 'react-native'

import { spaces } from '../Spaces'
import { memoMap } from '../util'
import { CarouselIndicator } from './CarouselIndicator'

/** a number between 0 and 1 */
type Percent = number

export interface CarouselProps<T> {
  items: T[]
  renderItem: ({
    item,
    index,
    style,
  }: {
    item: T
    index: number
    style?: StyleProp<ViewStyle>
  }) => React.ReactNode
  onSwipe?: (item: T, rank: number) => void
  style?: StyleProp<ViewStyle>
  indicatorStyle?: StyleProp<ViewStyle>
  reserveIndicatorSpace?: boolean
  scale?: Percent
}

function useCarouselIndex<T>({
  itemWidth,
  items,
}: {
  itemWidth: number
  onSwipe?: (item: T, rank: number) => void
  items: T[]
}) {
  const [itemIndex, setItemIndex] = useState(0)
  const onMomentumScrollStart = (
    e: NativeSyntheticEvent<NativeScrollEvent>
  ) => {
    const offset = e.nativeEvent.contentOffset.x
    const i = clamp(Math.round(offset / itemWidth), 0, items.length - 1)

    setItemIndex(i)
  }
  return [itemIndex, onMomentumScrollStart] as const
}

export function Carousel<T>({
  items,
  renderItem,
  onSwipe,
  style,
  indicatorStyle,
  reserveIndicatorSpace = false,
  scale = 1,
}: CarouselProps<T>): React.ReactElement {
  const gaps = items.length - 1
  // Ignore scaling for single-item carousel
  const itemScale = gaps === 0 ? 1 : scale
  const margin = spaces.insets.medium

  const { width } = useWindowDimensions()
  const itemWidth = (width - 2 * margin) * itemScale
  const [index, setIndex] = useCarouselIndex({ itemWidth, items })
  const scrollView = useRef<ScrollView>(null)

  const containerWidth = gaps * (itemWidth + 2 * margin) + width

  return (
    <View style={style}>
      <ScrollView
        onScroll={setIndex}
        onMomentumScrollEnd={() => {
          const item = items[index]
          if (item) {
            onSwipe?.(item, index)
          }
        }}
        scrollEventThrottle={100}
        horizontal
        showsHorizontalScrollIndicator={false}
        contentContainerStyle={{ width: containerWidth }}
        pagingEnabled
        snapToInterval={itemWidth + 2 * margin}
        decelerationRate="fast"
        snapToAlignment="start"
        ref={scrollView}
      >
        {memoMap(items, (item, index) =>
          renderItem({
            item,
            index,
            style: {
              width: index === gaps ? width - 2 * margin : itemWidth,
            },
          })
        )}
      </ScrollView>
      {(reserveIndicatorSpace || items.length > 1) && (
        <CarouselIndicator
          count={items.length}
          current={index}
          containerStyle={indicatorStyle}
        />
      )}
    </View>
  )
}
