import {
  createContext,
  type FC,
  type MutableRefObject,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
} from 'react';

import { useStableCallback } from 'src/hooks/useStableCallback';

const MAX_INACTIVE_TIME_MS = 6 * 60 * 60 * 1000;

type PageVisibilityListener = (isVisible: boolean) => void;

export type PageVisibilityContextValue = {
  isVisibleRef: MutableRefObject<boolean>;
  onVisibilityChange: (listener: PageVisibilityListener) => () => void;
};

const PageVisibilityContext = createContext<PageVisibilityContextValue>({
  isVisibleRef: { current: true },
  onVisibilityChange: () => () => undefined,
});

export const PageVisibilityProvider: FC<
  React.PropsWithChildren<unknown>
> = props => {
  const { children } = props;
  const isVisibleRef = useRef<boolean>(true);
  const visibilityListenersRef = useRef(new Set<PageVisibilityListener>());
  const lastBlurTimeRef = useRef<number | null>(null);

  const setPageVisibility = useCallback((isVisible: boolean) => {
    if (isVisibleRef.current !== isVisible) {
      isVisibleRef.current = isVisible;
      visibilityListenersRef.current.forEach(listener => {
        listener(isVisible);
      });
    }
  }, []);

  const onVisibilityChange = useCallback((listener: PageVisibilityListener) => {
    visibilityListenersRef.current.add(listener);
    return () => {
      visibilityListenersRef.current.delete(listener);
    };
  }, []);

  const contextValue = useMemo<PageVisibilityContextValue>(
    () => ({
      isVisibleRef,
      onVisibilityChange,
    }),
    [onVisibilityChange],
  );

  useEffect(() => {
    const handleFocus = () => {
      setPageVisibility(true);

      if (lastBlurTimeRef.current !== null) {
        const elapsedTime = Date.now() - lastBlurTimeRef.current;
        const isConversationsRoute =
          window.location.pathname.startsWith('/conversations');
        if (isConversationsRoute && elapsedTime > MAX_INACTIVE_TIME_MS) {
          window.location.reload();
        }
        lastBlurTimeRef.current = null;
      }
    };

    const handleBlur = () => {
      setPageVisibility(false);
      lastBlurTimeRef.current = Date.now();
    };

    const handleVisibilityChange = () => {
      if (document.hidden) {
        setPageVisibility(false);
        lastBlurTimeRef.current = Date.now();
      } else {
        setPageVisibility(true);
      }
    };

    window.addEventListener('focus', handleFocus);
    window.addEventListener('blur', handleBlur);
    window.addEventListener('visibilitychange', handleVisibilityChange);

    return () => {
      window.removeEventListener('focus', handleFocus);
      window.removeEventListener('blur', handleBlur);
      window.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, [setPageVisibility]);

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

export const usePageVisibilityContext = (onChange?: PageVisibilityListener) => {
  const context = useContext(PageVisibilityContext);
  const hasOnChange = !!onChange;
  const stableOnChange = useStableCallback((isVisible: boolean) =>
    onChange?.(isVisible),
  );

  // Set up subscription to onVisibilityChange
  useEffect(() => {
    let unsubscribe: (() => void) | undefined;

    if (hasOnChange) {
      unsubscribe = context.onVisibilityChange(stableOnChange);
    }

    return () => {
      unsubscribe?.();
    };
  }, [context, hasOnChange, stableOnChange]);

  return {
    isVisibleRef: context.isVisibleRef,
  };
};

export default PageVisibilityContext;
