import { useCallback, useRef } from 'react';
import { Platform } from 'react-native';
import {
  GestureHandlerGestureEvent,
  State,
} from 'react-native-gesture-handler';
import {
  // @ts-expect-error
  useEvent,
  useWorkletCallback,
} from 'react-native-reanimated';
import { makeRemote } from 'react-native-reanimated/src/reanimated2/core';

function useRemoteContext<T extends object>(initialValue: T) {
  const initRef = useRef<{ context: T } | null>(null);
  if (initRef.current === null) {
    initRef.current = {
      context: makeRemote(initialValue ?? {}),
    };
  }
  const { context } = initRef.current;

  return context;
}

function diff(context: any, name: string, value: any) {
  'worklet';

  if (!context.___diffs) {
    context.___diffs = {};
  }

  if (!context.___diffs[name]) {
    context.___diffs[name] = {
      stash: 0,
      prev: null,
    };
  }

  const d = context.___diffs[name];

  d.stash = d.prev !== null ? value - d.prev : 0;
  d.prev = value;

  return d.stash;
}

type Context = { [key: string]: any };
type Handler<T, TContext extends Context> = (
  event: T,
  context: TContext,
) => void;
type onEndHandler<T, TContext extends Context> = (
  event: T,
  context: TContext,
  isCanceled: boolean,
) => void;
type ReturnHandler<T, TContext extends Context, R> = (
  event: T,
  context: TContext,
) => R;

interface GestureHandlers<T, TContext extends Context> {
  onInit?: Handler<T, TContext>;
  onEvent?: Handler<T, TContext>;
  shouldHandleEvent?: ReturnHandler<T, TContext, boolean>;
  shouldCancel?: ReturnHandler<T, TContext, boolean>;
  onGesture?: Handler<T, TContext>;
  beforeEach?: Handler<T, TContext>;
  afterEach?: Handler<T, TContext>;
  onStart?: Handler<T, TContext>;
  onActive?: Handler<T, TContext>;
  onEnd?: onEndHandler<T, TContext>;
  onFail?: Handler<T, TContext>;
  onCancel?: Handler<T, TContext>;
  onFinish?: (
    event: T,
    context: TContext,
    isCanceledOrFailed: boolean,
  ) => void;
}

type OnGestureEvent<T extends GestureHandlerGestureEvent> = (
  event: T,
) => void;

export function createAnimatedGestureHandler<
  T extends GestureHandlerGestureEvent,
  TContext extends Context,
>(handlers: GestureHandlers<T['nativeEvent'], TContext>) {
  const context = useRemoteContext<any>({
    __initialized: false,
  });
  const isAndroid = Platform.OS === 'android';

  const handler = useWorkletCallback((event: T['nativeEvent']) => {
    'worklet';

    if (handlers.onInit && !context.__initialized) {
      context.__initialized = true;
      handlers.onInit(event, context);
    }

    if (handlers.onGesture) {
      handlers.onGesture(event, context);
    }

    const stateDiff = diff(context, 'pinchState', event.state);

    const pinchBeganAndroid =
      stateDiff === State.ACTIVE - State.BEGAN
        ? event.state === State.ACTIVE
        : false;

    const isBegan = isAndroid
      ? pinchBeganAndroid
      : event.state === State.BEGAN;

    if (isBegan) {
      if (handlers.shouldHandleEvent) {
        context._shouldSkip = !handlers.shouldHandleEvent(
          event,
          context,
        );
      } else {
        context._shouldSkip = false;
      }
    } else if (typeof context._shouldSkip === 'undefined') {
      return;
    }

    if (!context._shouldSkip && !context._shouldCancel) {
      if (handlers.onEvent) {
        handlers.onEvent(event, context);
      }

      if (handlers.shouldCancel) {
        context._shouldCancel = handlers.shouldCancel(event, context);

        if (context._shouldCancel) {
          if (handlers.onEnd) handlers.onEnd(event, context, true);
          return;
        }
      }

      if (handlers.beforeEach) {
        handlers.beforeEach(event, context);
      }

      if (isBegan && handlers.onStart) {
        handlers.onStart(event, context);
      }

      if (event.state === State.ACTIVE && handlers.onActive) {
        handlers.onActive(event, context);
      }
      if (
        event.oldState === State.ACTIVE &&
        event.state === State.END &&
        handlers.onEnd
      ) {
        handlers.onEnd(event, context, false);
      }
      if (
        event.oldState === State.ACTIVE &&
        event.state === State.FAILED &&
        handlers.onFail
      ) {
        handlers.onFail(event, context);
      }
      if (
        event.oldState === State.ACTIVE &&
        event.state === State.CANCELLED &&
        handlers.onCancel
      ) {
        handlers.onCancel(event, context);
      }

      if (event.oldState === State.ACTIVE) {
        if (handlers.onFinish) {
          handlers.onFinish(
            event,
            context,
            event.state === State.CANCELLED ||
              event.state === State.FAILED,
          );
        }
      }

      if (handlers.afterEach) {
        handlers.afterEach(event, context);
      }
    }
    // clean up context
    if (event.oldState === State.ACTIVE) {
      context._shouldSkip = undefined;
      context._shouldCancel = undefined;
    }
  }, []);

  return handler;
}

export function useAnimatedGestureHandler<
  T extends GestureHandlerGestureEvent,
  TContext extends Context,
>(
  handlers: GestureHandlers<T['nativeEvent'], TContext>,
): OnGestureEvent<T> {
  const handler = useCallback(
    createAnimatedGestureHandler<T, TContext>(handlers),
    [],
  );

  // @ts-ignore
  return useEvent(
    handler,
    // @ts-ignore
    ['onGestureHandlerStateChange', 'onGestureHandlerEvent'],
    false,
  );
}