import {
  Animated,
  Keyboard,
  Platform,
  ScrollView,
  type ScrollViewProps,
  StatusBar,
  TextInput,
  View,
} from 'react-native';
import {
  forwardRef,
  ReactElement,
  useEffect,
  useImperativeHandle,
  useRef,
} from 'react';

const androidStatusBarOffset = StatusBar.currentHeight ?? 0;

const KeyboardAwareScrollView = forwardRef(
  (
    {
      children,
      BottomComponent,
      isEdgeToEdgeEnabled = Platform.OS === 'android' && Platform.Version >= 35,
      ...rest
    }: ScrollViewProps & {
      BottomComponent?: ReactElement;
      isEdgeToEdgeEnabled?: boolean;
    },
    ref,
  ) => {
    const isHeightBasedSolution = Platform.OS === 'ios' || isEdgeToEdgeEnabled;
    const initialKeyboardHeight = Keyboard.metrics()?.height;

    const scrollViewRef = useRef<ScrollView>(null);
    const scrollPositionRef = useRef<number>(0);
    const keyboardHeightRef = useRef(
      new Animated.Value(initialKeyboardHeight ? initialKeyboardHeight : 0),
    ).current;
    const bottomRef = useRef<View>(null);

    useImperativeHandle(ref, () => scrollViewRef.current);

    useEffect(() => {
      const calculateAndScroll = ({
        keyboardY,
        bottomHeight,
      }: {
        keyboardY: number;
        bottomHeight: number;
      }) => {
        const currentlyFocusedInput = TextInput.State.currentlyFocusedInput();
        currentlyFocusedInput?.measureInWindow((_x, y, _width, height) => {
          const endOfInputY = y + height + androidStatusBarOffset;
          const deltaToScroll = endOfInputY - keyboardY;
          const additionalScroll = 30;

          const scrollPositionTarget =
            scrollPositionRef.current +
            deltaToScroll +
            additionalScroll +
            bottomHeight;

          scrollViewRef.current?.scrollTo({
            y: scrollPositionTarget,
            animated: true,
          });
        });
      };

      const didShowListener = Keyboard.addListener(
        Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
        frames => {
          const keyboardY = frames.endCoordinates.screenY;
          const duration = frames.duration;

          Animated.timing(keyboardHeightRef, {
            toValue: frames.endCoordinates.height,
            duration,
            useNativeDriver: !isHeightBasedSolution,
          }).start(() => {
            bottomRef.current?.measureInWindow(
              (_BottomX, _BottomY, _BottomWidth, bottomHeight) => {
                calculateAndScroll({
                  keyboardY,
                  bottomHeight: bottomHeight ?? 0,
                });
              },
            );
          });
        },
      );

      const didHideListener = Keyboard.addListener(
        Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
        frames => {
          const duration = frames.duration;
          Animated.timing(keyboardHeightRef, {
            toValue: 0,
            duration,
            useNativeDriver: !isHeightBasedSolution,
          }).start();
        },
      );

      return () => {
        didShowListener.remove();
        didHideListener.remove();
      };
    }, []);

    return (
      <>
        <Animated.ScrollView
          ref={scrollViewRef}
          onScroll={event => {
            scrollPositionRef.current = event.nativeEvent.contentOffset.y;
          }}
          {...rest}>
          {children}
        </Animated.ScrollView>
        <View ref={bottomRef} collapsable={false}>
          {BottomComponent}
        </View>
        <Animated.View
          style={[
            isHeightBasedSolution
              ? {height: keyboardHeightRef}
              : {
                  transform: [{translateY: keyboardHeightRef}],
                },
          ]}
        />
      </>
    );
  },
);

export default KeyboardAwareScrollView;
