import React, {
  forwardRef,
  useCallback,
  useEffect,
  useRef,
  useState,
} from 'react';
import type {
  LayoutChangeEvent,
  ListRenderItem,
  StyleProp,
  ViewStyle,
  ViewToken,
} from 'react-native';
import { FlatList, Platform, View } from 'react-native';
import { useTheme } from '../../theme';
import {
  StyledCard,
  StyledItemWrapper,
  StyledPageControl,
  StyledWrapper,
} from './StyledCardCarousel';
import { ITEM_WIDTH_RATE, VIEW_POSITION_CENTER } from './contants';

export type CardCarouselHandles = {
  snapToIndex: (index: number) => void;
};

export interface CardCarouselProps {
  /**
   * Whether to scroll automatically.
   */
  autoPlay?: boolean;
  /**
   * Set interval of each slide.
   */
  autoPlayInterval?: number;
  /**
   * onItemIndexChange event handler receiving index of selected Item.
   */
  onItemIndexChange?: (index: number) => void;
  /**
   * Carousel items.
   */
  items: React.ReactNode[];
  /**
   * Indicates hide or show page control.
   */
  hidePageControl?: boolean;
  /**
   * Additional styles
   */
  style?: StyleProp<ViewStyle>;
  /**
   * Testing id of the component.
   */
  testID?: string;
  /**
   * Component ref.
   */
  ref?: React.Ref<CardCarouselHandles>;
  /*
   * onLayout event handler.
   */
  onLayout?: (event: LayoutChangeEvent) => void;
  /**
   * Gap between items.
   */
  gap?: 'xsmall' | 'small' | 'medium';
}

export const getCardCarouselValidIndex = (index: number, length: number) => {
  return Math.min(Math.max(index, 0), length - 1);
};

export const CardCarousel = forwardRef<CardCarouselHandles, CardCarouselProps>(
  (
    {
      onItemIndexChange,
      items,
      hidePageControl = false,
      style,
      testID,
      autoPlay = false,
      autoPlayInterval = 3000,
      onLayout,
      gap = 'medium',
    }: CardCarouselProps,
    ref?: React.Ref<CardCarouselHandles>
  ) => {
    const [currentIndex, setCurrentIndex] = useState(0);

    const theme = useTheme();
    const [flatListWidth, setFlatListWidth] = useState(0);
    const itemWidth = flatListWidth * ITEM_WIDTH_RATE;
    const carouselRef = useRef<FlatList>(null);

    const viewPosition =
      Platform.OS === 'ios' ? VIEW_POSITION_CENTER : undefined;
    const snapToIndex = useCallback(
      (index: number) => {
        const validIndex = getCardCarouselValidIndex(index, items.length);

        carouselRef.current?.scrollToIndex({
          index: validIndex,
          animated: true,
          viewPosition,
        });
      },
      [items.length, viewPosition]
    );

    /*
     * snap to the next index. If the curent index is the last one, snap to the first one.
     */
    const snapToNext = useCallback(() => {
      let nextIndex = currentIndex + 1;
      if (nextIndex >= items.length) {
        nextIndex = 0;
      }
      carouselRef.current?.scrollToIndex({
        index: nextIndex,
        animated: true,
        viewPosition,
      });
    }, [currentIndex, items.length, viewPosition]);

    React.useImperativeHandle(
      ref,
      () => ({
        snapToIndex: (index: number) => {
          snapToIndex(index);
        },
        // we don't expose this method, it's for testing https://medium.com/developer-rants/how-to-test-useref-without-mocking-useref-699165f4994e
        getFlatListRef: () => carouselRef.current,
      }),
      [snapToIndex]
    );

    useEffect(() => {
      let timer: ReturnType<typeof setInterval>;
      if (autoPlay) {
        timer = setInterval(() => {
          snapToNext();
        }, autoPlayInterval);
      }
      return () => {
        clearInterval(timer);
      };
    }, [autoPlay, snapToNext, currentIndex, autoPlayInterval]);

    const visibleSlideChanged = useCallback(
      ({ viewableItems }: { viewableItems: ViewToken[] }) => {
        if (!viewableItems || (viewableItems && !viewableItems.length)) return;
        const { index } = viewableItems[0];

        setCurrentIndex(index || 0);
        if (onItemIndexChange) {
          onItemIndexChange(index || 0);
        }
      },
      [onItemIndexChange]
    );

    const getItemLayout = useCallback(
      (_: ArrayLike<React.ReactNode> | null | undefined, index: number) => ({
        length: itemWidth,
        offset: itemWidth * index,
        index,
      }),
      [itemWidth]
    );

    const flatListOnLayout = useCallback(
      (e: LayoutChangeEvent) => {
        setFlatListWidth(e.nativeEvent.layout.width);
        setTimeout(() => {
          onLayout?.(e);
        });
      },
      [setFlatListWidth, onLayout]
    );

    const renderItem: ListRenderItem<React.ReactNode> = useCallback(
      ({ item }) => {
        return (
          <StyledItemWrapper themeGap={gap} style={{ width: itemWidth }}>
            <StyledCard>{item}</StyledCard>
          </StyledItemWrapper>
        );
      },
      [itemWidth, gap]
    );

    const { contentContainerPaddingHorizontal } =
      theme.__hd__.cardCarousel.space;
    return (
      <StyledWrapper style={style} testID={testID}>
        <FlatList<React.ReactNode>
          contentInset={{
            top: 0,
            left: contentContainerPaddingHorizontal,
            bottom: 0,
            right: contentContainerPaddingHorizontal,
          }}
          ListHeaderComponent={Platform.select({
            android: (
              <View style={{ width: contentContainerPaddingHorizontal }} />
            ),
          })}
          ListFooterComponent={Platform.select({
            android: (
              <View style={{ width: contentContainerPaddingHorizontal }} />
            ),
          })}
          onLayout={flatListOnLayout}
          data={items}
          horizontal
          showsHorizontalScrollIndicator={false}
          pagingEnabled
          bounces={false}
          scrollEventThrottle={32}
          snapToAlignment="center"
          getItemLayout={getItemLayout}
          ref={carouselRef}
          renderItem={renderItem}
          keyExtractor={(_, index) => `${index}`}
          decelerationRate="fast"
          renderToHardwareTextureAndroid
          snapToInterval={Platform.select({
            ios: itemWidth,
          })}
          onViewableItemsChanged={visibleSlideChanged}
          viewabilityConfig={{
            itemVisiblePercentThreshold: 80,
          }}
        />

        {!hidePageControl && (
          <StyledPageControl
            testID="pageControl"
            currentPage={currentIndex}
            numberOfPages={items.length}
          />
        )}
      </StyledWrapper>
    );
  }
);

CardCarousel.displayName = 'CardCarousel';
