/* oxlint-disable react/rules-of-hooks -- this module deliberately routes hook calls between tap and React at runtime */
/* oxlint-disable react/exhaustive-deps -- dependency arrays are forwarded verbatim from the caller */
import React from "react";
import { isResourceContext, useResourceContext } from "../core/context";
import { peekResourceFiber } from "../core/helpers/execution-context";
import { tapCallback } from "../hooks/tap-callback";
import { tapEffect } from "../hooks/tap-effect";
import { tapEffectEvent } from "../hooks/tap-effect-event";
import { tapMemo } from "../hooks/tap-memo";
import { tapReducer } from "../hooks/tap-reducer";
import { tapRef } from "../hooks/tap-ref";
import { tapState } from "../hooks/tap-state";
import { useReactEffectEvent } from "./useReactEffectEvent";

// @ts-expect-error -- @types/react uses `export =`; this is valid at runtime.
export * from "react";
export { default } from "react";

const inTap = () => peekResourceFiber() !== null;
const ReactRuntime = React as any;

export const useState = (initialState?: any) =>
  inTap() ? tapState(initialState) : ReactRuntime.useState(initialState);

export const useReducer = (reducer: any, initialArg: any, init?: any) =>
  inTap()
    ? tapReducer(reducer, initialArg, init)
    : ReactRuntime.useReducer(reducer, initialArg, init);

export const useRef = (initialValue?: any) =>
  inTap() ? tapRef(initialValue) : ReactRuntime.useRef(initialValue);

export const useMemo = (factory: any, deps: any) =>
  inTap() ? tapMemo(factory, deps) : ReactRuntime.useMemo(factory, deps);

export const useCallback = (callback: any, deps: any) =>
  inTap()
    ? tapCallback(callback, deps)
    : ReactRuntime.useCallback(callback, deps);

export const useEffect = (effect: any, deps?: any) =>
  inTap() ? tapEffect(effect, deps) : ReactRuntime.useEffect(effect, deps);

export const useLayoutEffect = (effect: any, deps?: any) =>
  inTap()
    ? tapEffect(effect, deps)
    : ReactRuntime.useLayoutEffect(effect, deps);

export const useEffectEvent = (callback: any) =>
  inTap() ? tapEffectEvent(callback) : useReactEffectEvent(callback);

export const useSyncExternalStore = (
  subscribe: (onStoreChange: () => void) => () => void,
  getSnapshot: () => any,
  getServerSnapshot: () => any = getSnapshot,
) => {
  if (!inTap()) {
    return ReactRuntime.useSyncExternalStore(
      subscribe,
      getSnapshot,
      getServerSnapshot,
    );
  }

  const isFirstRender = tapRef(true);
  const value = isFirstRender.current ? getServerSnapshot() : getSnapshot();
  isFirstRender.current = false;

  const [, forceUpdate] = tapState(0);
  const onStoreChange = tapEffectEvent(() => {
    if (!Object.is(value, getSnapshot())) forceUpdate((count) => count + 1);
  });

  tapEffect(() => {
    onStoreChange();
    return subscribe(onStoreChange);
  }, [subscribe]);

  return value;
};

export const useDebugValue = (value: any, format?: any) => {
  if (!inTap()) ReactRuntime.useDebugValue(value, format);
};

export const use = (usable: any) =>
  isResourceContext(usable)
    ? useResourceContext(usable)
    : ReactRuntime.use(usable);

export const useContext = (context: any) =>
  isResourceContext(context)
    ? useResourceContext(context)
    : ReactRuntime.useContext(context);
