import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import type {TextStyle} from 'react-native';
import {
  type StyleProp,
  type ViewStyle,
  Animated,
  Platform,
  StyleSheet,
  View,
} from 'react-native';
import PickerItemComponent from '../item/PickerItem';
import {ScrollContentOffsetContext} from '../contexts/ScrollContentOffsetContext';
import {PickerItemHeightContext} from '../contexts/PickerItemHeightContext';
import useValueEventsEffect from './hooks/useValueEventsEffect';
import useSyncScrollEffect from './hooks/useSyncScrollEffect';
import type {
  KeyExtractor,
  ListMethods,
  OnValueChanged,
  OnValueChanging,
  PickerItem,
  RenderItem,
  RenderItemContainer,
  RenderList,
  RenderOverlay,
  RenderPickerItem,
} from '../types';
import Overlay from '../overlay/Overlay';
import {calcPickerHeight, createFaces} from '../item/faces';
import PickerItemContainer from '../item/PickerItemContainer';
import {useBoolean} from '../../utils/react';
import {useInit, useStableCallback} from '@rozhkov/react-useful-hooks';
import List from '../list/List';
export type PickerProps<ItemT extends PickerItem<any>> = {
  data: ReadonlyArray<ItemT>;
  value: ItemT['value'];
  extraValues?: unknown[];
  itemHeight?: number;
  visibleItemCount?: number;
  width?: number | 'auto' | `${number}%`;
  readOnly?: boolean;
  testID?: string;
  enableScrollByTapOnItem?: boolean;
  onValueChanging?: OnValueChanging<ItemT>;
  onValueChanged?: OnValueChanged<ItemT>;
  keyExtractor?: KeyExtractor<ItemT>;
  renderItem?: RenderItem<ItemT>;
  renderItemContainer?: RenderItemContainer<ItemT>;
  renderOverlay?: RenderOverlay | null;
  renderList?: RenderList<ItemT>;
  style?: StyleProp<ViewStyle>;
  itemTextStyle?: StyleProp<TextStyle>;
  overlayItemStyle?: StyleProp<ViewStyle>;
  contentContainerStyle?: StyleProp<ViewStyle>;
  scrollEventThrottle?: number;
  _enableSyncScrollAfterScrollEnd?: boolean;
  _onScrollStart?: () => void;
  _onScrollEnd?: () => void;
};
const defaultKeyExtractor: KeyExtractor<any> = (_, index) => index.toString();
const defaultRenderItem: RenderItem<PickerItem<any>> = ({
  item: {value, label},
  itemTextStyle,
}) => (
  <PickerItemComponent
    value={value}
    label={label}
    itemTextStyle={itemTextStyle}
  />
);
const defaultRenderItemContainer: RenderItemContainer<any> = ({
  key,
  ...props
}) => <PickerItemContainer key={key} {...props} />;
const defaultRenderOverlay: RenderOverlay = (props) => <Overlay {...props} />;
const defaultRenderList: RenderList<any> = (props) => {
  return <List {...props} />;
};
export const useValueIndex = (
  data: ReadonlyArray<PickerItem<any>>,
  value: any,
) => {
  return useMemo(() => {
    const index = data.findIndex((x) => x.value === value);
    return index >= 0 ? index : 0;
  }, [data, value]);
};
const Picker = <ItemT extends PickerItem<any>>({
  data,
  value,
  extraValues = [],
  width = 'auto',
  itemHeight = 48,
  visibleItemCount = 5,
  readOnly = false,
  enableScrollByTapOnItem,
  testID,
  onValueChanged,
  onValueChanging,
  keyExtractor = defaultKeyExtractor,
  renderItem = defaultRenderItem,
  renderItemContainer = defaultRenderItemContainer,
  renderOverlay = defaultRenderOverlay,
  renderList = defaultRenderList,
  style,
  itemTextStyle,
  overlayItemStyle,
  contentContainerStyle,
  _enableSyncScrollAfterScrollEnd = true,
  _onScrollStart,
  _onScrollEnd,
  ...restProps
}: PickerProps<ItemT>) => {
  const valueIndex = useValueIndex(data, value);
  const initialIndex = useInit(() => valueIndex);
  const offsetY = useMemo(
    () => new Animated.Value(valueIndex * itemHeight),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [readOnly], // when scrollEnabled changes, the events stop coming. Re-creating
  );
  const listRef = useRef<ListMethods>(null);
  const touching = useBoolean(false);
  const [faces, pickerHeight] = useMemo(() => {
    const items = createFaces(itemHeight, visibleItemCount);
    const height = calcPickerHeight(items, itemHeight);
    return [items, height];
  }, [itemHeight, visibleItemCount]);
  const renderPickerItem = useCallback<RenderPickerItem<ItemT>>(
    ({item, index, key}) =>
      renderItemContainer({
        listRef,
        key,
        item,
        index,
        faces,
        renderItem,
        itemTextStyle,
        enableScrollByTapOnItem,
        readOnly,
      }),
    [
      enableScrollByTapOnItem,
      faces,
      itemTextStyle,
      readOnly,
      renderItem,
      renderItemContainer,
    ],
  );
  const {activeIndexRef, onScrollEnd: onScrollEndForValueEvents} =
    useValueEventsEffect(
      {
        data,
        valueIndex,
        itemHeight,
        offsetYAv: offsetY,
      },
      {
        onValueChanging,
        onValueChanged,
      },
    );
  const {
    onScrollStart: onScrollStartForSyncScroll,
    onScrollEnd: onScrollEndForSyncScroll,
  } = useSyncScrollEffect({
    listRef,
    value,
    valueIndex,
    extraValues,
    activeIndexRef,
    touching: touching.value,
    enableSyncScrollAfterScrollEnd: _enableSyncScrollAfterScrollEnd,
  });
  const onScrollStart = useStableCallback(() => {
    onScrollStartForSyncScroll();
    _onScrollStart?.();
  });
  const onScrollEnd = useStableCallback(() => {
    // consistency matters
    _onScrollEnd?.();
    onScrollEndForValueEvents();
    onScrollEndForSyncScroll();
  });

  // iOS can mount the picker list with a wrong initial contentOffset, so re-apply it after mount.
  // See https://github.com/quidone/react-native-wheel-picker/issues/67.
  useEffect(() => {
    if (Platform.OS === 'ios') {
      listRef.current?.scrollToIndex({
        index: initialIndex,
        animated: false,
      });
    }
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <ScrollContentOffsetContext.Provider value={offsetY}>
      <PickerItemHeightContext.Provider value={itemHeight}>
        <View
          testID={testID}
          style={[
            styles.root,
            style,
            {
              height: pickerHeight,
              width,
            },
          ]}
        >
          {renderList({
            ...restProps,
            ref: listRef,
            data,
            initialIndex,
            itemHeight,
            pickerHeight,
            visibleItemCount,
            readOnly,
            keyExtractor,
            renderItem: renderPickerItem,
            scrollOffset: offsetY,
            onTouchStart: touching.setTrue,
            onTouchEnd: touching.setFalse,
            onTouchCancel: touching.setFalse,
            onScrollStart,
            onScrollEnd,
            contentContainerStyle,
          })}
          {renderOverlay &&
            renderOverlay({
              itemHeight,
              pickerWidth: width,
              pickerHeight,
              overlayItemStyle,
            })}
        </View>
      </PickerItemHeightContext.Provider>
    </ScrollContentOffsetContext.Provider>
  );
};
const styles = StyleSheet.create({
  root: {
    justifyContent: 'center',
    alignItems: 'center',
  },
});
export default Picker;
