import React, {ReactNode, useCallback, useMemo} from 'react';
import {
  Pressable as RNPressable,
  PressableProps,
  StyleProp,
  StyleSheet,
  ViewStyle,
  PressableStateCallbackType,
  View,
} from 'react-native';
import {Pressable as GHPressable} from 'react-native-gesture-handler';
import * as Haptics from 'expo-haptics';

interface Props extends PressableProps {
  enableHaptics?: boolean;
  backgroundColor?: string;
  children: ReactNode;
  onPress?: () => void;
  pressInColor?: string;
  style?: StyleProp<ViewStyle>;
  containerStyle?: StyleProp<ViewStyle>;
  activeOpacity?: number;
  underlayColor?: string;
  enableShadowOnPress?: boolean;
  ref?: React.Ref<Nullable<View>>;
  useGestureHandler?: boolean;
}

const ACTIVE_OPACITY = 0.85;

export function Pressable({
  enableHaptics = false,
  backgroundColor,
  children,
  onPress,
  style,
  containerStyle,
  activeOpacity = ACTIVE_OPACITY,
  underlayColor,
  ref,
  useGestureHandler,
  ...props
}: Props) {
  const handlePressIn = useCallback(() => {
    if (enableHaptics) {
      Haptics.selectionAsync();
    }
  }, [enableHaptics]);

  const handlePressOut = useCallback(() => {
    if (enableHaptics) {
      Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Rigid);
    }
  }, [enableHaptics]);

  const handlePress = useCallback(() => {
    onPress?.();
  }, [onPress]);

  const pressableStyle: PressableProps['style'] = useMemo(() => {
    const staticStyles = StyleSheet.flatten([containerStyle, style]);
    const hasUnderlay = !!underlayColor;

    return ({pressed}: PressableStateCallbackType) => {
      const bgColor =
        hasUnderlay && pressed
          ? underlayColor
          : backgroundColor || staticStyles?.backgroundColor;
      const opacity = hasUnderlay ? 1 : pressed ? activeOpacity : 1;

      return [staticStyles, {backgroundColor: bgColor, opacity}];
    };
  }, [containerStyle, style, underlayColor, backgroundColor, activeOpacity]);

  const PressableComponent = (
    useGestureHandler ? GHPressable : RNPressable
  ) as typeof RNPressable;

  return (
    <PressableComponent
      ref={ref}
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      onPress={handlePress}
      style={pressableStyle}
      {...props}>
      {children}
    </PressableComponent>
  );
}
