import React, {
  createContext,
  ReactNode,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import {ViewStyle} from 'react-native';
import {Gesture} from 'react-native-gesture-handler';
import {
  clamp,
  interpolate,
  runOnJS,
  SharedValue,
  useAnimatedReaction,
  useAnimatedStyle,
  useDerivedValue,
  useSharedValue,
  withDelay,
  withTiming,
} from 'react-native-reanimated';
import {windowDimensions} from '@shared/constants';
import {useIsAudioSessionBusy} from '@stores/audioSession';

import {
  MIN_AUDIO_RECORDING_DURATION_MS,
  AUDIO_ANIMATED_VALUES,
} from '../../constants';
import {useAudioRecording} from './AudioRecordingContext';

interface AudioRecordingAnimatedContextValue {
  isActive: SharedValue<number>;
  isActiveAnimated: SharedValue<number>;
  isLocked: SharedValue<number>;
  isPressing: SharedValue<number>;
  isCanceling: SharedValue<number>;
  translationValueY: SharedValue<number>;
  translationValueX: SharedValue<number>;
  isHintVisible: boolean;
  inputAnimatedStyle: ReturnType<typeof useAnimatedStyle<ViewStyle>>;
  gesture: ReturnType<typeof Gesture.Exclusive>;
  reset: () => void;
  hideHint: () => void;
  listenLongPressEnd: (callback: () => void) => void;
}

interface Props {
  children: ReactNode;
}

const AudioRecordingAnimatedContext = createContext<
  AudioRecordingAnimatedContextValue | undefined
>(undefined);

export const AudioRecordingAnimatedProvider = ({children}: Props) => {
  const isActive = useSharedValue(0);
  const isActiveAnimated = useSharedValue(0);
  const isLocked = useSharedValue(0);
  const isPressing = useSharedValue(0);
  const isCanceling = useSharedValue(0);
  const translationValueY = useSharedValue(0);
  const translationValueX = useSharedValue(0);
  const startPressingTs = useRef(0);
  const timerRef = useRef<NodeJS.Timeout | undefined>(undefined);
  const [isHintVisible, setIsHintVisible] = useState(false);
  const longPressEndCallback = useRef<(() => void) | null>(null);
  const isAudioSessionBusy = useIsAudioSessionBusy();

  const {
    isPermissionGranted,
    startRecording,
    requestPermission,
    cancelRecording,
  } = useAudioRecording();

  const reset = useCallback(() => {
    isActive.value = 0;
    isLocked.value = 0;
    translationValueX.value = 0;
    translationValueY.value = 0;
    isCanceling.value = 0;
    isPressing.value = 0;
    isActiveAnimated.value = withTiming(0, {
      duration: AUDIO_ANIMATED_VALUES.isActiveAnimationDuration,
    });
  }, [
    isActive,
    isLocked,
    translationValueX,
    translationValueY,
    isCanceling,
    isPressing,
    isActiveAnimated,
  ]);

  const updateStartPressing = () => {
    startPressingTs.current = Date.now();
  };

  const comparePressingTs = () => {
    const currentTs = Date.now();
    const diff = currentTs - startPressingTs.current;

    const showHint =
      diff <=
      MIN_AUDIO_RECORDING_DURATION_MS +
        AUDIO_ANIMATED_VALUES.isActiveAnimationDuration;

    if (showHint && !isLocked.value) {
      clearTimeout(timerRef.current);
      setIsHintVisible(true);

      timerRef.current = setTimeout(() => {
        setIsHintVisible(false);
        clearTimeout(timerRef.current);
      }, AUDIO_ANIMATED_VALUES.hintVisibilityDuration);
    }
  };

  const hideHint = useCallback(() => {
    setIsHintVisible(false);
    clearTimeout(timerRef.current);
  }, []);

  useAnimatedReaction(
    () => isPressing.value,
    val => {
      if (val) {
        runOnJS(updateStartPressing)();
      } else {
        runOnJS(comparePressingTs)();
      }
    },
    [],
  );

  useAnimatedReaction(
    () => isLocked.value,
    val => {
      if (val && setIsHintVisible) {
        runOnJS(setIsHintVisible)(false);
      }
    },
    [setIsHintVisible],
  );

  const gesturePan = Gesture.Pan()
    .minDistance(0)
    .onUpdate(event => {
      if (isLocked.value) {
        return;
      }
      translationValueY.value = clamp(
        event.translationY,
        -AUDIO_ANIMATED_VALUES.lockThreshold,
        0,
      );
      translationValueX.value = clamp(
        event.translationX,
        -AUDIO_ANIMATED_VALUES.cancelThreshold,
        0,
      );

      if (
        event.translationY < -AUDIO_ANIMATED_VALUES.lockThreshold &&
        !isCanceling.value
      ) {
        isLocked.value = 1;
      } else {
        isLocked.value = 0;
      }

      if (
        event.translationX < -AUDIO_ANIMATED_VALUES.cancelThreshold &&
        !isCanceling.value
      ) {
        isCanceling.value = 1;
        isActive.value = 0;
        isActiveAnimated.value = withDelay(
          AUDIO_ANIMATED_VALUES.cancelingAnimationDurationMs,
          withTiming(
            0,
            {
              duration: AUDIO_ANIMATED_VALUES.isActiveAnimationDuration,
            },
            () => {
              isCanceling.value = 0;
              translationValueX.value = 0;
              translationValueY.value = 0;
            },
          ),
        );
        runOnJS(cancelRecording)();
      }
    })
    .onEnd(() => {
      isPressing.value = 0;
      if (!isLocked.value && !isCanceling.value) {
        translationValueY.value = 0;
        translationValueX.value = 0;

        isActive.value = 0;
        isActiveAnimated.value = withTiming(0, {
          duration: AUDIO_ANIMATED_VALUES.isActiveAnimationDuration,
        });
      }
    })
    .enabled(!isAudioSessionBusy);

  function handlerTap() {
    isLocked.value = 1;
    isActive.value = 1;
    isActiveAnimated.value = withTiming(
      1,
      {duration: AUDIO_ANIMATED_VALUES.isActiveAnimationDuration},
      () => {
        runOnJS(startRecording)();
      },
    );
  }

  function handlerTapWorklet() {
    'worklet';
    isLocked.value = 1;
    isActive.value = 1;
    isActiveAnimated.value = withTiming(
      1,
      {duration: AUDIO_ANIMATED_VALUES.isActiveAnimationDuration},
      () => {
        runOnJS(startRecording)();
      },
    );
  }

  const gestureTap = Gesture.Tap()
    .maxDuration(200)
    .maxDistance(10)
    .onEnd(() => {
      if (!isPermissionGranted.value) {
        runOnJS(requestPermission)(handlerTap);
        return;
      }

      handlerTapWorklet();
    })
    .enabled(!isAudioSessionBusy);

  function handlerLongPress() {
    isPressing.value = 1;
    isActive.value = 1;
    isActiveAnimated.value = withTiming(
      1,
      {duration: AUDIO_ANIMATED_VALUES.isActiveAnimationDuration},
      () => {
        runOnJS(startRecording)();
      },
    );
  }

  function handlerLongPressWorklet() {
    'worklet';
    isPressing.value = 1;
    isActive.value = 1;
    isActiveAnimated.value = withTiming(
      1,
      {duration: AUDIO_ANIMATED_VALUES.isActiveAnimationDuration},
      () => {
        runOnJS(startRecording)();
      },
    );
  }

  const listenLongPressEnd = useCallback((callback: () => void) => {
    longPressEndCallback.current = callback;
  }, []);

  const longPressEnd = () => {
    longPressEndCallback.current?.();
  };

  const gestureLongPress = Gesture.LongPress()
    .minDuration(20)
    .maxDistance(
      Math.max(
        AUDIO_ANIMATED_VALUES.lockThreshold,
        AUDIO_ANIMATED_VALUES.cancelThreshold,
      ) + 50,
    )
    .shouldCancelWhenOutside(false)
    .onStart(() => {
      if (!isPermissionGranted.value) {
        runOnJS(requestPermission)(handlerLongPress);
        return;
      }

      handlerLongPressWorklet();
    })
    .onEnd(() => {
      isPressing.value = 0;
      if (!isLocked.value && !isCanceling.value) {
        isActive.value = 0;
        isActiveAnimated.value = withTiming(0, {
          duration: AUDIO_ANIMATED_VALUES.isActiveAnimationDuration,
        });
        runOnJS(longPressEnd)();
      }
    })
    .enabled(!isAudioSessionBusy);

  const gesture = Gesture.Exclusive(
    gestureTap,
    Gesture.Simultaneous(gesturePan, gestureLongPress),
  );

  const inputMarginTop = useDerivedValue(() => {
    return withTiming(isLocked.value ? 25 : 0, {duration: 100});
  }, [isLocked]);

  const inputAnimatedStyle = useAnimatedStyle<ViewStyle>(() => {
    if (!_WORKLET) {
      return {
        marginTop: 0,
        opacity: 1,
        transform: [{translateX: 0}],
      };
    }

    return {
      marginTop: inputMarginTop.value,
      opacity:
        interpolate(isActiveAnimated.value, [0, 0.5, 1], [1, 0, 0]) -
        isLocked.value,
      transform: [
        {
          translateX: interpolate(
            isActiveAnimated.value,
            [0, 1],
            [0, -windowDimensions.width],
          ),
        },
      ],
    };
  }, [inputMarginTop, isActiveAnimated, isLocked]);

  // TODO: how to handle this when it's a ai summary recording
  // const isStopped = status === 'stopped';
  // useEffect(() => {
  //   if (isStopped) {
  //     isLocked.value = 1;
  //   }
  //   // eslint-disable-next-line react-hooks/exhaustive-deps
  // }, [isStopped]);

  const contextValue = useMemo(
    (): AudioRecordingAnimatedContextValue => ({
      isActive,
      isActiveAnimated,
      isLocked,
      isPressing,
      isCanceling,
      translationValueY,
      translationValueX,
      isHintVisible,
      gesture,
      reset,
      hideHint,
      inputAnimatedStyle,
      listenLongPressEnd,
    }),
    [
      isActive,
      isActiveAnimated,
      isLocked,
      isPressing,
      isCanceling,
      translationValueY,
      translationValueX,
      isHintVisible,
      gesture,
      reset,
      hideHint,
      inputAnimatedStyle,
      listenLongPressEnd,
    ],
  );

  return (
    <AudioRecordingAnimatedContext.Provider value={contextValue}>
      {children}
    </AudioRecordingAnimatedContext.Provider>
  );
};

export const useAudioRecordingAnimated =
  (): AudioRecordingAnimatedContextValue => {
    const context = useContext(AudioRecordingAnimatedContext);
    if (!context) {
      throw new Error(
        'useAudioRecordingAnimated must be used within an AudioRecordingAnimatedProvider',
      );
    }
    return context;
  };

/**
 * Register a callback to be called when the long press ends
 * @param cb - The callback to register
 */
export const useAudioRecordingLongPressListener = (cb: () => void) => {
  const isRegistered = useRef(false);
  const {listenLongPressEnd} = useAudioRecordingAnimated();

  /**
   * Register the callback only once
   */
  useEffect(() => {
    if (isRegistered.current) {
      return;
    }
    listenLongPressEnd(cb);
    isRegistered.current = true;
  }, [listenLongPressEnd, cb]);
};
