import React, {useEffect, useReducer, useMemo} from 'react';

import {
  ActivityIndicator,
  TouchableOpacity,
  Animated,
  Text,
  Easing,
  View,
  ViewStyle,
  TextStyle,
  ImageStyle,
  LayoutChangeEvent,
  FlatList,
} from 'react-native';

import {styles} from './styles';

import white_chevron from './assets/images/white.png';
import black_chevron from './assets/images/black.png';

export type InnerItem = {
  /**Inner Item id */
  id: string;
  /**Default text for Inner Item */
  name?: string;
  /**Add your custom Inner Item */
  customInnerItem?: JSX.Element;
};

export interface Item {
  /**Item id */
  id: string;
  /**Inner Items */
  subCategory: InnerItem[];
  /**Default text for Item */
  categoryName?: string;
  /**Add your custom Item */
  customItem?: JSX.Element;
}

interface InnerItemClickCallback {
  innerItemIndex: number;
  item: Item;
  itemIndex: number;
}

interface Props {
  /** Data for the expandable listview */
  data: Array<Item>;
  /** Callback for item click */
  onItemClick?: ({index}: {index: number}) => void;
  /** Callback for inner item click */
  onInnerItemClick?: ({
    innerItemIndex,
    item,
    itemIndex,
  }: InnerItemClickCallback) => void;
  /** Add style to whole expandable listview */
  ExpandableListViewStyles?: ViewStyle;
  /** Add style to each inner item container */
  innerItemContainerStyle?: ViewStyle;
  /** Add style to each inner item label */
  innerItemLabelStyle?: TextStyle;
  /** Add style to each item container */
  itemContainerStyle?: ViewStyle;
  /** Add style to each item label */
  itemLabelStyle?: TextStyle;
  /** Add style to the item indicator */
  itemImageIndicatorStyle?: ImageStyle;
  /** Pass the path for your custom indicator */
  customChevron?: string;
  /** Color for default indicator */
  chevronColor?: 'white' | 'black';
  /** Render separator for items */
  renderItemSeparator?: boolean;
  /** Render separator for inner items */
  renderInnerItemSeparator?: boolean;
  /** Add style to the item separator */
  itemSeparatorStyle?: ViewStyle;
  /** Add style to the inner item separator */
  innerItemSeparatorStyle?: ViewStyle;
  /** Set Animation on/off, default on */
  animated?: boolean;
  /** Set your styles to default loader (only for animated={true}) */
  defaultLoaderStyles?: ViewStyle;
  /** Pass your custom loader, while your content is measured and rendered (only for animated={true}) */
  customLoader?: JSX.Element;
}

interface ExpandableListItem {
  item: Item;
  index: number;
}

const initialState = {
  opened: false,
  height: [],
  data: [],
  isMounted: [],
  lastSelectedIndex: -1,
  selectedIndex: -1,
  opacityValues: new Animated.Value(0),
  animatedValues: [],
  rotateValueHolder: [],
};

function reducer(
  state: any,
  action: {
    type: string;
    data?: Item[];
    opened?: boolean;
    height?: [];
    isMounted?: [];
    lastSelectedIndex?: number;
    selectedIndex?: number;
    opacityValues?: Animated.Value;
    animatedValues?: Animated.Value[];
    rotateValueHolder?: Animated.Value[];
  },
) {
  switch (action.type) {
    case 'set':
      return {...state, ...action};
    case 'reset':
      return {
        opened: false,
        height: [],
        data: [],
        isMounted: [],
        lastSelectedIndex: -1,
        selectedIndex: -1,
        opacityValues: new Animated.Value(0),
        animatedValues: [],
        rotateValueHolder: [],
      };
    default:
      return {...state};
  }
}

export const ExpandableListView: React.FC<Props> = ({data,innerItemLabelStyle,renderItemSeparator,renderInnerItemSeparator,onInnerItemClick,onItemClick,defaultLoaderStyles,itemSeparatorStyle,itemLabelStyle,itemImageIndicatorStyle,itemContainerStyle,innerItemSeparatorStyle,innerItemContainerStyle,customLoader,customChevron,animated=true,chevronColor, ExpandableListViewStyles}) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const CustomLoader = customLoader;
  useEffect(() => {
    if (state.selectedIndex >= 0) {
      if (state.animatedValues[state.selectedIndex] !== undefined) {
        if (state.selectedIndex !== state.lastSelectedIndex) {
          if (
            state.lastSelectedIndex >= 0 &&
            state.lastSelectedIndex < state.data.length
          ) {
            Animated.parallel([
              Animated.timing(state.animatedValues[state.lastSelectedIndex], {
                useNativeDriver: false,
                duration: 300,
                easing: Easing.linear,
                toValue: 0,
              }),
              Animated.timing(
                state.rotateValueHolder[state.lastSelectedIndex],
                {
                  toValue: 0,
                  duration: 300,
                  easing: Easing.linear,
                  useNativeDriver: true,
                },
              ),
            ]).start();
          }
          Animated.parallel([
            Animated.timing(state.animatedValues[state.selectedIndex], {
              useNativeDriver: false,
              duration: 300,
              easing: Easing.linear,
              toValue: state.height[state.selectedIndex],
            }),
            Animated.timing(state.rotateValueHolder[state.selectedIndex], {
              toValue: 1,
              duration: 300,
              easing: Easing.linear,
              useNativeDriver: true,
            }),
          ]).start();
        } else {
          Animated.parallel([
            Animated.timing(state.animatedValues[state.selectedIndex], {
              useNativeDriver: false,
              duration: 300,
              easing: Easing.linear,
              toValue:
                state.opened &&
                state.height !== undefined &&
                state.height[state.selectedIndex] !== undefined
                  ? state.height[state.selectedIndex]
                  : 0,
            }),
            Animated.timing(state.rotateValueHolder[state.selectedIndex], {
              toValue: state.opened ? 1 : 0,
              duration: 300,
              easing: Easing.linear,
              useNativeDriver: true,
            }),
          ]).start();
        }
        dispatch({type: 'set', lastSelectedIndex: state.selectedIndex});
      }
    } else {
      if (
        state.isMounted.length === state.data.length &&
        state.data.length > 0
      ) {
        Animated.timing(state.opacityValues, {
          toValue: 1,
          duration: 300,
          easing: Easing.linear,
          useNativeDriver: true,
        }).start();
      }
    }
  }, [
    state.data,
    state.height,
    state.opened,
    state.isMounted,
    state.opacityValues,
    state.animatedValues,
    state.rotateValueHolder,
    state.selectedIndex,
    state.lastSelectedIndex,
  ]);

  useEffect(() => {
    async function reset() {
      await dispatch({type: 'reset'});
      await dispatch({type: 'set', data: data});
    }
    reset();
  }, [data]);

  function handleLayout(evt: LayoutChangeEvent, index: number) {
    if (!state.isMounted[index] && evt.nativeEvent.layout.height !== 0) {
      let h = state.height;
      h[index] = evt.nativeEvent.layout.height;
      let m = state.isMounted;
      m[index] = true;
      let newAnimatedValues: Array<Animated.Value> = [...state.animatedValues];
      let newRotateValueHolder: Array<Animated.Value> = [
        ...state.rotateValueHolder,
      ];
      newAnimatedValues.push(new Animated.Value(0));
      newRotateValueHolder.push(new Animated.Value(0));

      dispatch({
        type: 'set',
        animatedValues: newAnimatedValues,
        rotateValueHolder: newRotateValueHolder,
        height: h,
        isMounted: m,
      });
    }
  }

  function updateLayout(updatedIndex: number) {
    dispatch({
      type: 'set',
      opened: updatedIndex === state.selectedIndex ? !state.opened : true,
      selectedIndex: updatedIndex,
    });

    if (onItemClick) {
      return onItemClick({index: updatedIndex});
    }
    return;
  }

  const List = useMemo(() => Animated.FlatList, []);

  function renderInnerItem(itemO: any, headerItem: Item, headerIndex: number) {
    let {item}: {item: InnerItem} = itemO;
    let {index}: {index: number} = itemO;

    let CustomComponent = item.customInnerItem;

    let container = {
      ...styles.content,
      ...innerItemContainerStyle,
      height: undefined,
    };
    innerItemLabelStyle = {
      ...styles.text,
      ...innerItemLabelStyle,
      height: undefined,
    };

    innerItemSeparatorStyle = {
      ...styles.innerItemSeparator,
      ...innerItemSeparatorStyle,
    };

    return (
      <>
        <TouchableOpacity
          activeOpacity={0.6}
          key={Math.random()}
          style={container}
          onPress={() =>
            onInnerItemClick &&
            onInnerItemClick({
              innerItemIndex: index,
              item: headerItem,
              itemIndex: headerIndex,
            })
          }>
          {CustomComponent !== undefined ? (
            CustomComponent
          ) : (
            <Text style={innerItemLabelStyle}>{item.name}</Text>
          )}
        </TouchableOpacity>
        {renderInnerItemSeparator !== undefined &&
          renderInnerItemSeparator &&
          index < headerItem.subCategory.length - 1 && (
            <View style={innerItemSeparatorStyle} />
          )}
      </>
    );
  }

  function renderItem({item, index}: ExpandableListItem) {

    itemContainerStyle = {
      ...styles.header,
      ...itemContainerStyle,
      height: undefined,
    };
    itemLabelStyle = {
      ...styles.headerText,
      ...itemLabelStyle,
    };
    itemImageIndicatorStyle = {
      height: 15,
      width: 15,
      marginHorizontal: 5,
      ...itemImageIndicatorStyle,
    };

    itemSeparatorStyle = {...styles.headerSeparator, ...itemSeparatorStyle};

    let CustomComponent = item.customItem;
    return (
      <Animated.View
        style={{
          height: undefined,
        }}>
        <TouchableOpacity
          activeOpacity={0.6}
          onPress={() => updateLayout(index)}
          style={itemContainerStyle}>
          {CustomComponent !== undefined ? (
            CustomComponent
          ) : (
            <>
              <Animated.Image
                source={
                  customChevron !== undefined
                    ? customChevron
                    : chevronColor !== undefined &&
                      chevronColor === 'white'
                    ? white_chevron
                    : black_chevron
                }
                resizeMethod="scale"
                resizeMode="contain"
                style={[
                  itemImageIndicatorStyle,
                  animated === undefined ||
                  (animated !== undefined && animated)
                    ? state.rotateValueHolder[index] !== undefined && {
                        transform: [
                          {
                            rotate: state.rotateValueHolder[index].interpolate({
                              inputRange: [0, 1],
                              outputRange: ['0deg', '90deg'],
                            }),
                          },
                        ],
                      }
                    : {
                        transform: [
                          {
                            rotate:
                              state.opened && index === state.selectedIndex
                                ? '90deg'
                                : '0deg',
                          },
                        ],
                      },
                ]}
              />

              <Text style={itemLabelStyle}>{item.categoryName}</Text>
            </>
          )}
        </TouchableOpacity>

        <Animated.View
          style={[
            animated === undefined ||
            (animated !== undefined && animated)
              ? // eslint-disable-next-line react-native/no-inline-styles
                {
                  height: !state.isMounted[index]
                    ? undefined
                    : state.animatedValues[index],
                  overflow: 'hidden',
                }
              : // eslint-disable-next-line react-native/no-inline-styles
                {
                  display:
                    state.opened && index === state.selectedIndex
                      ? 'flex'
                      : 'none',
                  overflow: 'hidden',
                },
          ]}
          onLayout={(evt: any) => handleLayout(evt, index)}>
          <FlatList
            style={{height: undefined}}
            contentContainerStyle={{height: undefined}}
            updateCellsBatchingPeriod={50}
            initialNumToRender={50}
            windowSize={50}
            maxToRenderPerBatch={50}
            keyExtractor={() => Math.random().toString()}
            listKey={String(Math.random())}
            data={item.subCategory}
            renderItem={(innerItem: any) =>
              renderInnerItem(innerItem, item, index)
            }
          />
        </Animated.View>

        {renderItemSeparator !== undefined &&
          renderItemSeparator &&
          (!state.opened || state.selectedIndex !== index) &&
          index < state.data.length - 1 && <View style={itemSeparatorStyle} />}
      </Animated.View>
    );
  }

  return (
    <>
    {animated && data.length >0 && state.isMounted[data.length -1] === undefined && (CustomLoader !== undefined ? CustomLoader : <ActivityIndicator style={defaultLoaderStyles} color="#94bfda" size="large" />)}

    <Animated.View
      style={[
        // eslint-disable-next-line react-native/no-inline-styles
        {
          opacity:
            animated === undefined ||
            (animated !== undefined && animated)
              ? state.isMounted.length === state.data.length &&
                data.length > 0
                ? state.opacityValues
                : 0
              : 1,
        },
        {...ExpandableListViewStyles},
        {height: animated && data.length >0 && state.isMounted[data.length -1] === undefined ? 0 : ExpandableListViewStyles?.height !== undefined ? ExpandableListViewStyles?.height : 'auto'},
      ]}>


      <List
        updateCellsBatchingPeriod={50}
        initialNumToRender={50}
        windowSize={50}
        maxToRenderPerBatch={50}
        keyExtractor={(_: any, itemIndex: number) => itemIndex.toString()}
        data={state.data}
        renderItem={(item: ExpandableListItem) => renderItem(item)}
      />
    </Animated.View>
    </>
  );
};