import React, { useEffect, useRef } from 'react';
import { FlatList, FlatListProps, Platform, Dimensions } from 'react-native';

import { Pagination } from '../Pagination/Pagination';
import { SwiperFlatListProps, SwiperFlatListRefProps } from './SwiperFlatListProps';

const MILLISECONDS = 1000;
const FIRST_INDEX = 0;
const ITEM_VISIBLE_PERCENT_THRESHOLD = 60;

const { width: WIDTH, height: HEIGHT } = Dimensions.get('window');

export const SwiperFlatList = React.forwardRef(
  (
    {
      vertical = false,
      children,
      data = [],
      renderItem,
      renderAll = false,
      index = FIRST_INDEX,
      // Pagination
      showPagination = false,
      PaginationComponent = Pagination,
      paginationActiveColor,
      paginationDefaultColor,
      paginationStyle,
      paginationStyleItem,
      paginationStyleItemActive,
      paginationStyleItemInactive,
      onPaginationSelectedIndex,
      // Autoplay
      autoplayDelay = 3,
      autoplay = false,
      autoplayLoop = false,
      autoplayLoopKeepAnimation = false,
      autoplayInvertDirection = false,
      // Functions
      onChangeIndex,
      onMomentumScrollEnd,
      onViewableItemsChanged,
      viewabilityConfig = {},
      disableGesture = false,
      e2eID,
      ...props
    }: SwiperFlatListProps<any>,
    ref: React.Ref<SwiperFlatListRefProps>
  ) => {
    let _data: unknown[] = [];
    let _renderItem: FlatListProps<any>['renderItem'];

    if (children) {
      _data = Array.isArray(children) ? children : [children];
      _renderItem = ({ item }) => item;
    } else if (data) {
      _data = data;
      _renderItem = renderItem;
    } else {
      console.error('Invalid props, `data` or `children` is required');
    }
    const size = _data.length;
    // Items to render in the initial batch.
    const _initialNumToRender = renderAll ? size : 1;
    const [currentIndexes, setCurrentIndexes] = React.useState({ index, prevIndex: index });
    const [ignoreOnMomentumScrollEnd, setIgnoreOnMomentumScrollEnd] = React.useState(false);
    const flatListElement = useRef<FlatList<unknown>>(null);
    const [scrollEnabled, setScrollEnabled] = React.useState(!disableGesture);

    const _onChangeIndex = React.useCallback(
      ({ index: _index, prevIndex: _prevIndex }) => {
        if (_index !== _prevIndex) {
          onChangeIndex?.({ index: _index, prevIndex: _prevIndex });
        }
      },
      [onChangeIndex]
    );

    const _scrollToIndex = (params: { index: number; animated?: boolean }, extra: { useOnChangeIndex: boolean }) => {
      const { index: indexToScroll, animated = true } = params;
      const newParams = { animated, index: indexToScroll };

      if (animated) {
        setIgnoreOnMomentumScrollEnd(true);
      }

      const next = {
        index: indexToScroll,
        prevIndex: currentIndexes.index
      };
      if (currentIndexes.index !== next.index && currentIndexes.prevIndex !== next.prevIndex) {
        setCurrentIndexes({ index: next.index, prevIndex: next.prevIndex });
      } else if (currentIndexes.index !== next.index) {
        setCurrentIndexes((prevState) => ({ ...prevState, index: next.index }));
      } else if (currentIndexes.prevIndex !== next.prevIndex) {
        setCurrentIndexes((prevState) => ({ ...prevState, prevIndex: next.prevIndex }));
      }

      if (extra.useOnChangeIndex) {
        _onChangeIndex({ index: next.index, prevIndex: next.prevIndex });
      }

      flatListElement?.current?.scrollToIndex(newParams);
    };

    React.useEffect(() => {
      _onChangeIndex({ index: currentIndexes.index, prevIndex: currentIndexes.prevIndex });
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [currentIndexes.index]);

    React.useImperativeHandle(ref, () => ({
      scrollToIndex: (item: { index: number; animated?: boolean }) => {
        setScrollEnabled(true);
        _scrollToIndex(item, { useOnChangeIndex: true });
        setScrollEnabled(!disableGesture);
      },
      getCurrentIndex: () => currentIndexes.index,
      getPrevIndex: () => currentIndexes.prevIndex,
      goToLastIndex: () => {
        setScrollEnabled(true);
        _scrollToIndex({ index: size - 1 }, { useOnChangeIndex: false });
        setScrollEnabled(!disableGesture);
      },
      goToFirstIndex: () => {
        setScrollEnabled(true);
        _scrollToIndex({ index: FIRST_INDEX }, { useOnChangeIndex: false });
        setScrollEnabled(!disableGesture);
      }
    }));

    React.useEffect(() => {
      const isLastIndexEnd = autoplayInvertDirection
        ? currentIndexes.index === FIRST_INDEX
        : currentIndexes.index === _data.length - 1;
      const shouldContinuoWithAutoplay = autoplay && !isLastIndexEnd;
      let autoplayTimer: ReturnType<typeof setTimeout>;
      if (shouldContinuoWithAutoplay || autoplayLoop) {
        autoplayTimer = setTimeout(() => {
          if (_data.length < 1) {
            return;
          }

          const nextIncrement = autoplayInvertDirection ? -1 : +1;

          let nextIndex = (currentIndexes.index + nextIncrement) % _data.length;
          if (autoplayInvertDirection && nextIndex < FIRST_INDEX) {
            nextIndex = _data.length - 1;
          }

          const animate = !isLastIndexEnd || autoplayLoopKeepAnimation;

          _scrollToIndex({ index: nextIndex, animated: animate }, { useOnChangeIndex: true });
        }, autoplayDelay * MILLISECONDS);
      }
      return () => clearTimeout(autoplayTimer);
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [currentIndexes.index]);

    const _onMomentumScrollEnd: FlatListProps<unknown>['onMomentumScrollEnd'] = (event) => {
      if (ignoreOnMomentumScrollEnd) {
        setIgnoreOnMomentumScrollEnd(false);
        return;
      }

      onMomentumScrollEnd?.({ index: currentIndexes.index }, event);
    };

    const _onViewableItemsChanged = React.useMemo<FlatListProps<unknown>['onViewableItemsChanged']>(
      () => (params) => {
        const { changed } = params;
        const newItem = changed?.[FIRST_INDEX];
        if (newItem !== undefined) {
          const nextIndex = newItem.index as number;
          if (newItem.isViewable) {
            setCurrentIndexes((prevState) => ({ ...prevState, index: nextIndex }));
          } else {
            setCurrentIndexes((prevState) => ({ ...prevState, prevIndex: nextIndex }));
          }
        }
        onViewableItemsChanged?.(params);
      },
      // eslint-disable-next-line react-hooks/exhaustive-deps
      []
    );

    const keyExtractor: FlatListProps<unknown>['keyExtractor'] = (_item, _index) => _index.toString();
    const onScrollToIndexFailed: FlatListProps<unknown>['onScrollToIndexFailed'] = (info) =>
      setTimeout(() => _scrollToIndex({ index: info.index, animated: false }, { useOnChangeIndex: true }), 100);

    const flatListProps = {
      scrollEnabled,
      ref: flatListElement,
      keyExtractor,
      horizontal: vertical ? false : true,
      showsHorizontalScrollIndicator: false,
      showsVerticalScrollIndicator: false,
      pagingEnabled: true,
      ...props,
      onMomentumScrollEnd: _onMomentumScrollEnd,
      onScrollToIndexFailed: onScrollToIndexFailed,
      data: _data,
      renderItem: _renderItem,
      initialNumToRender: _initialNumToRender,
      viewabilityConfig: {
        minimumViewTime: 200,
        itemVisiblePercentThreshold: ITEM_VISIBLE_PERCENT_THRESHOLD,
        ...viewabilityConfig
      },
      onViewableItemsChanged: _onViewableItemsChanged,
      testID: e2eID
    };

    if (Platform.OS === 'web') {
      if (props.getItemLayout === undefined) {
        const ITEM_DIMENSION = vertical ? HEIGHT : WIDTH;
        flatListProps.getItemLayout = (__data, ItemIndex: number) => {
          return {
            length: ITEM_DIMENSION,
            offset: ITEM_DIMENSION * ItemIndex,
            index: ItemIndex
          };
        };
      }

      (flatListProps as any).dataSet = { 'paging-enabled-fix': true };
    }

    const scrollToIndexForPagination = (params: { index: number; animated?: boolean }) => {
      _scrollToIndex(params, { useOnChangeIndex: false });
    };

    const paginationProps = {
      size,
      paginationIndex: currentIndexes.index,
      scrollToIndex: scrollToIndexForPagination,
      paginationActiveColor,
      paginationDefaultColor,
      paginationStyle,
      paginationStyleItem,
      paginationStyleItemActive,
      paginationStyleItemInactive,
      onPaginationSelectedIndex,
      e2eID
    };

    useEffect(() => {
      const timer = setTimeout(() => {
        _scrollToIndex({ index, animated: false }, { useOnChangeIndex: false });
      }, 100); // 延迟 100 毫秒

      return () => clearTimeout(timer);
    }, [index]);

    return (
      <React.Fragment>
        <FlatList {...flatListProps} />
        {showPagination && <PaginationComponent {...paginationProps} />}
      </React.Fragment>
    );
  }
);

type Handle<T> = T extends React.ForwardRefExoticComponent<React.RefAttributes<infer T2>> ? T2 : never;
export type SwiperFlatList = Handle<typeof SwiperFlatList>;