import type { Dispatch, ReactElement, SetStateAction } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import type {
  StyleProp,
  ViewProps,
  ViewStyle,
  ViewToken,
  LayoutChangeEvent,
} from 'react-native';
import { Animated, FlatList, View } from 'react-native';

import { useTheme } from '../../theme';
import { useDeprecation } from '../../utils/hooks';
import { CardCarousel } from './CardCarousel';
import CarouselItem from './CarouselItem';
import {
  StyledBackDrop,
  StyledCarouselFooterWrapper,
  StyledCarouselView,
  StyledPageControl,
  StyledPageControlWrapper,
} from './StyledCarousel';
import type { CarouselData } from './types';

interface CarouselProps extends ViewProps {
  /**
   * Additional style.
   */
  style?: StyleProp<ViewStyle>;
  /**
   * Testing id of the component.
   */
  testID?: string;
  /**
   * onItemIndexChange event handler receiving index of selected Item.
   */
  onItemIndexChange?: Dispatch<SetStateAction<number>>;
  /**
   * Carousel data.
   */
  items: CarouselData[];
  /**
   * Render action elements function.
   */
  renderActions?: (pageIndex: number) => ReactElement;
  /**
   * @deprecated will be removed in 9.0.0
   *
   * Should show paginations
   */
  shouldShowPagination?: (pageIndex: number) => boolean;
  /**
   * Current selected item index.
   */
  selectedItemIndex?: number;
  /**
   * Position of the page control
   *
   * pageControlPosition['bottom'] - @deprecated
   */
  pageControlPosition?: 'top' | 'bottom';
}

function useStateFromProp<T>(
  initialValue: T
): [T, Dispatch<SetStateAction<T>>] {
  const [value, setValue] = useState(initialValue);

  useEffect(() => setValue(initialValue), [initialValue]);

  return [value, setValue];
}

const noop = (_: number): boolean => {
  return true;
};

const Carousel = ({
  items,
  onItemIndexChange,
  renderActions,
  selectedItemIndex = 0,
  style,
  shouldShowPagination = noop,
  testID,
  pageControlPosition = 'top',
  ...nativeProps
}: CarouselProps) => {
  useDeprecation(
    `shouldShowPagination prop has been deprecated`,
    shouldShowPagination !== noop
  );
  useDeprecation(
    `The use of 'pageControlPosition == bottom' has been deprecated`,
    pageControlPosition === 'bottom'
  );

  const theme = useTheme();
  const carouselRef = useRef<FlatList>(null);

  const [currentSlideIndex, setCurrentSlideIndex] =
    useStateFromProp(selectedItemIndex);

  const shouldRenderPagination =
    items.length > 1 && shouldShowPagination(currentSlideIndex);

  const internalOnItemIndexChange = useCallback(
    (index: number) => {
      setCurrentSlideIndex(index);

      if (onItemIndexChange) {
        onItemIndexChange(index);
      }
    },
    [setCurrentSlideIndex, onItemIndexChange]
  );
  const [flatListWidth, setFlatListWidth] = useState(0);
  const itemWidth = flatListWidth;
  const flatListOnLayout = useCallback(
    (e: LayoutChangeEvent) => {
      setFlatListWidth(e.nativeEvent.layout.width);
    },
    [setFlatListWidth]
  );
  const width = flatListWidth;

  const scrollX = useRef(new Animated.Value(0)).current;
  useEffect(() => {
    // must use setTimeout since when layout is mounted, the pagination dots are not set correct scrollX
    const handle = setTimeout(() => {
      scrollX.setValue(currentSlideIndex * width);
      carouselRef.current?.scrollToOffset({
        offset: currentSlideIndex * width,
        animated: true,
      });
    }, 100);
    return () => {
      clearTimeout(handle);
    };
  }, [currentSlideIndex, carouselRef, scrollX, width]);
  const viewConfig = useRef({ viewAreaCoveragePercentThreshold: 50 }).current;

  const onViewCallBack = useRef(
    (info: { viewableItems: Array<ViewToken>; changed: Array<ViewToken> }) => {
      const firstVisibleItem = info.viewableItems.find(
        (view) => view.index != null && view.isViewable
      );
      if (firstVisibleItem) {
        internalOnItemIndexChange(firstVisibleItem.index || 0);
      }
    }
  );

  return (
    <View style={style} testID={testID} {...nativeProps}>
      <StyledBackDrop
        themeSlideBackground={
          items[currentSlideIndex]?.background ||
          theme.colors.defaultGlobalSurface
        }
      />

      <StyledPageControlWrapper>
        {pageControlPosition === 'top' && shouldRenderPagination && (
          <StyledPageControl
            numberOfPages={items.length}
            currentPage={currentSlideIndex}
          />
        )}
      </StyledPageControlWrapper>

      <StyledCarouselView>
        <FlatList<CarouselData>
          onLayout={flatListOnLayout}
          testID={testID ? `${testID}_flatlist` : undefined}
          horizontal
          showsHorizontalScrollIndicator={false}
          pagingEnabled
          bounces={false}
          data={items}
          onViewableItemsChanged={onViewCallBack.current}
          viewabilityConfig={viewConfig}
          scrollEventThrottle={32}
          ref={carouselRef}
          onScroll={Animated.event(
            [{ nativeEvent: { contentOffset: { x: scrollX } } }],
            { useNativeDriver: false }
          )}
          renderItem={({ item }) => {
            if (!item) return null;

            const { image, heading, body, content } = item;
            return (
              <CarouselItem
                image={image}
                heading={heading}
                body={body}
                content={content}
                width={width}
              />
            );
          }}
          getItemLayout={(_, index) => ({
            length: itemWidth,
            offset: itemWidth * index,
            index,
          })}
        />
        <StyledCarouselFooterWrapper>
          {renderActions && renderActions(currentSlideIndex)}
          {pageControlPosition === 'bottom' && shouldRenderPagination && (
            <StyledPageControl
              numberOfPages={items.length}
              currentPage={currentSlideIndex}
            />
          )}
        </StyledCarouselFooterWrapper>
      </StyledCarouselView>
    </View>
  );
};

export default Object.assign(Carousel, {
  Card: CardCarousel,
});
