import type { ComponentProps, ReactNode } from 'react';
import React, { useCallback, useEffect, useRef } from 'react';
import { Animated, useWindowDimensions } from 'react-native';
import {
  GestureHandlerRootView,
  RectButton,
  Swipeable as RnghSwipeable,
} from 'react-native-gesture-handler';
import SwipeableAction from './SwipeableAction';
import { useTheme } from '../../theme';
import Box from '../Box';

type State = 'closed' | 'leftOpen' | 'rightOpen';
// We are supporting both v1 and v2 of RNGH at the same time.
// SwipeableProps is only exported in v2, so we have to use ComponentProps.
type RnghSwipeableProps = ComponentProps<typeof RnghSwipeable>;
export interface SwipeableProps
  extends Pick<
    RnghSwipeableProps,
    | 'enableTrackpadTwoFingerGesture'
    | 'friction'
    | 'leftThreshold'
    | 'rightThreshold'
    | 'overshootLeft'
    | 'overshootRight'
    | 'overshootFriction'
    | 'useNativeAnimations'
    | 'containerStyle'
    | 'childrenContainerStyle'
  > {
  /**
   * React node that is swipeable.
   */
  children: ReactNode;
  /**
   * State of the component.
   */
  state?: 'closed' | 'leftOpen' | 'rightOpen';
  /**
   * Callback when the state of the component changes.
   */
  onStateChange?: (state: State) => void;
  /**
   * Action panel that is going to be revealed from the left side when user swipes right.
   */
  leftActions?: ReactNode;
  /**
   * Width of the left action panel.
   * By default, it will take up the whole width of the device.
   */
  leftActionsWidth?: number;
  /**
   * Action panel that is going to be revealed from the right side when user swipes left.
   */
  rightActions?: ReactNode;
  /**
   * Width of the right action panel.
   * By default, it will take up the whole width of the device.
   */
  rightActionsWidth?: number;
  /**
   * Testing ID of the component
   */
  testID?: string;
  /**
   * Variant of the component.
   */
  variant?: 'card' | 'full-width';
}

const renderActions = (
  actions: ReactNode,
  width: number,
  progress: Animated.AnimatedInterpolation<string | number>,
  direction: 'left' | 'right'
) => {
  const trans = progress.interpolate({
    inputRange: [0, 1],
    outputRange: direction === 'left' ? [-width, 0] : [width, 0],
    extrapolate: 'clamp',
  });

  return (
    <Animated.View
      style={{
        width,
        flexDirection: 'row',
        transform: [{ translateX: trans }],
        zIndex: -99,
        position: 'relative',
      }}
    >
      {actions}
    </Animated.View>
  );
};

const Swipeable = ({
  children,
  state,
  onStateChange,
  leftActions,
  leftActionsWidth,
  rightActions,
  rightActionsWidth,
  variant = 'card',
  ...swipeableProps
}: SwipeableProps) => {
  const theme = useTheme();
  const { width } = useWindowDimensions();
  const swipeableRef = useRef<RnghSwipeable>(null);
  const [containerWidth, setContainerWidth] = React.useState(0);

  const renderLeftActions = useCallback(
    (progress: Animated.AnimatedInterpolation<string | number>) =>
      renderActions(leftActions, leftActionsWidth || width, progress, 'left'),
    [leftActions, leftActionsWidth, width]
  );
  const renderRightActions = useCallback(
    (progress: Animated.AnimatedInterpolation<string | number>) =>
      renderActions(
        rightActions,
        rightActionsWidth || width,
        progress,
        'right'
      ),
    [rightActions, rightActionsWidth, width]
  );

  useEffect(() => {
    if (swipeableRef.current === null) return;

    switch (state) {
      case 'leftOpen':
        swipeableRef.current.openLeft();
        break;
      case 'rightOpen':
        swipeableRef.current.openRight();
        break;
      case 'closed':
        swipeableRef.current.close();
        break;
    }
  }, [state]);

  return (
    <GestureHandlerRootView>
      <Box onLayout={(e) => setContainerWidth(e.nativeEvent.layout.width)}>
        <RnghSwipeable
          {...swipeableProps}
          ref={swipeableRef}
          {...(leftActions !== undefined && { renderLeftActions })}
          {...(rightActions !== undefined && { renderRightActions })}
          onSwipeableLeftOpen={() => onStateChange?.('leftOpen')}
          onSwipeableRightOpen={() => onStateChange?.('rightOpen')}
          onSwipeableClose={() => onStateChange?.('closed')}
          containerStyle={{
            borderRadius:
              variant === 'card'
                ? theme.__hd__.swipeable.radii.swipeableContainer
                : 0,
          }}
          childrenContainerStyle={{
            backgroundColor:
              theme.__hd__.swipeable.colors.defaultContainerBackground,
            position: 'relative',
            width:
              containerWidth + theme.__hd__.swipeable.space.containerExtraWidth,
            borderRadius: theme.__hd__.swipeable.radii.swipeableContainer,
          }}
        >
          {children}
        </RnghSwipeable>
      </Box>
    </GestureHandlerRootView>
  );
};

export default Object.assign(Swipeable, {
  Action: SwipeableAction,
  Content: RectButton,
});
