import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useVisibility } from '../visibility';

export interface BackEventControls {
  addEventListener: (...handlers: Array<() => void>) => void;
  removeEventListener: (...handlers: Array<() => void>) => void;
}

interface PrivateBackEventControls extends BackEventControls {
  handlersRef: Set<() => void>;
  hasBackEvent: boolean;
  onBack: () => void;
}

const BackEventContext = createContext<PrivateBackEventControls | null>(null);

/**
 * @component
 * @name BackEventProvider
 * @description
 * 앱 내에서 뒤로 가기 이벤트를 처리하는 `Context`를 제공하는 컴포넌트예요. 이 컴포넌트를 사용해서 하위 컴포넌트들이 뒤로 가기 이벤트를 구독하고 관리할 수 있어요. 트리의 상위에 `BackEventProvider`를 배치해 모든 뒤로 가기 이벤트를 중앙에서 처리할 수 있게 해줘요.
 *
 * @param {ReactNode} children 뒤로 가기 이벤트를 제어 할 자식컴포넌트예요.
 * @param {BackEventControls} backEvent 뒤로 가기 이벤트를 처리하기 위한 제어 객체에요. `addEventListener` 메서드를 사용해 뒤로 가기 이벤트를 등록할 수 있고, `removeEventListener` 메서드를 사용해 등록된 뒤로 가기 이벤트를 제거 할 수 있어요.
 * @returns {JSX.Element} 뒤로 가기 이벤트를 처리할 수 있는 컴포넌트예요.
 *
 * 다음과 같이 `BackEventProvider`를 추가하면, 자식 컴포넌트가 뒤로 가기 이벤트를 처리할 수 있어요.
 *
 * @example
 * ```jsx
 * const backEventControls = {
 *   addEventListener: (handler) => { ... },
 *   removeEventListener: (handler) => { ... },
 * };
 *
 * <BackEventProvider backEvent={backEventControls}>
 *   <App />
 * </BackEventProvider>
 * ```
 */
export function BackEventProvider({
  children,
  backEvent,
}: {
  children: ReactNode;
  backEvent: PrivateBackEventControls;
}) {
  return <BackEventContext.Provider value={backEvent}>{children}</BackEventContext.Provider>;
}

/**
 * @name useBackEventState
 * @description
 * 현재 `BackEventProvider`에서 관리 중인 뒤로 가기 이벤트 상태와 제어 메서드를 반환하는 훅이에요. 이 훅을 사용해 뒤로 가기 이벤트 핸들러를 추가하거나 제거할 수 있고, 현재 상태를 조회할 수 있어요.
 *
 * @returns {{ handlers: Array<() => void>, backEvent: BackEventControls, routerProps: { canGoBack: boolean, onBack: () => void } }} 뒤로 가기 이벤트 핸들러 배열, 이벤트 제어 메서드인 `backEvent`, 그리고 라우터에서 뒤로 가기 기능을 처리할 때 사용할 수 있는 `routerProps`를 반환해요.
 *
 * 이 Hook을 사용해 뒤로 가기 이벤트 핸들러를 추가하거나 제거하고, 라우터와 연동할 수 있어요.
 *
 * @example
 * ```javascript
 * const { backEvent, routerProps } = useBackEventState();
 * // routerProps를 통해 라우터에서 뒤로 가기 이벤트를 처리
 * backEvent.addEventListener(() => {
 *   console.log('뒤로 가기 이벤트 발생');
 * });
 *
 * return (
 *   <BackEventProvider backEvent={backEvent}>
 *     <BedrockRouter {...routerProps} context={context} prefix={'intoss://testbench'} />
 *   </BackEventProvider>
 * );
 *
 * ```
 */
export function useBackEventState() {
  const handlersRef = useRef<Set<() => void>>(new Set()).current;
  const [hasBackEvent, setHasBackEvent] = useState(false);

  const addEventListener = useCallback(
    (...handlers: Array<() => void>) => {
      for (const handler of handlers) {
        handlersRef.add(handler);
      }

      if (handlersRef.size > 0) {
        setHasBackEvent(true);
      }
    },
    [handlersRef]
  );

  const removeEventListener = useCallback(
    (...handlers: Array<() => void>) => {
      for (const handler of handlers) {
        handlersRef.delete(handler);
      }

      if (handlersRef.size === 0) {
        setHasBackEvent(false);
      }
    },
    [handlersRef]
  );

  const backEvent = useMemo((): PrivateBackEventControls => {
    return {
      addEventListener,
      removeEventListener,
      handlersRef,
      hasBackEvent,
      onBack: () => {
        handlersRef.forEach((handler) => handler());
      },
    };
  }, [addEventListener, handlersRef, hasBackEvent, removeEventListener]);

  return backEvent;
}

/**
 * @public
 * @category 화면 제어
 * @name useBackEvent
 * @description
 * 뒤로 가기 이벤트를 등록하고 제거할 수 있는 컨트롤러 객체를 반환하는 Hook이에요. 이 Hook을 사용하면 특정 컴포넌트가 활성화되었을 때만 뒤로 가기 이벤트를 처리할 수 있어요.
 * `addEventListener` 를 쓰면 뒤로 가기 이벤트를 등록할 수 있고, `removeEventListener` 를 쓰면 뒤로 가기 이벤트를 제거할 수 있어요.
 * 사용자가 화면을 보고 있을 때만 등록된 뒤로 가기 이벤트가 등록돼요. 화면을 보고 있다는 조건은 [useVisibility](/react-native/reference/react-native-bedrock/화면%20제어/useVisibility.md) 을 사용해요.
 *
 * 이 Hook을 사용해 특정 컴포넌트에서 뒤로 가기 이벤트를 처리하는 로직을 정의할 수 있어요.
 *
 * @returns {BackEventControls} 뒤로 가기 이벤트를 제어할 수 있는 객체예요. 이 객체에는 이벤트를 등록하는 `addEventListener` 메서드와와 제거하는 `removeEventListener` 메서드가 포함 되어 있어요.
 *
 * @throws {Error} 이 훅이 `BackEventProvider` 내에서 사용되지 않은 경우 에러를 발생시켜요.
 *
 * @example
 *
 * ### 뒤로 가기 이벤트 등록 및 제거 예제
 *
 * - **"Add BackEvent" 버튼을 누르면 뒤로 가기 이벤트가 등록돼요.** 이후 뒤로 가기 버튼을 누르면 "back"이라는 알림이 뜨고, 실제로 뒤로 가지 않아요.
 * - **"Remove BackEvent" 버튼을 누르면 등록된 이벤트가 제거돼요.** 이후 뒤로 가기 버튼을 누르면 기존 동작대로 정상적으로 뒤로 가요.
 *
 * ```tsx
 * import { useEffect, useState } from 'react';
 * import { Alert, Button, View } from 'react-native';
 * import { useBackEvent } from 'react-native-bedrock';
 *
 * export function UseBackEventExample() {
 *   const backEvent = useBackEvent();
 *
 *   const [handler, setHandler] = useState<{ callback: () => void } | undefined>(undefined);
 *
 *   useEffect(() => {
 *     const callback = handler?.callback;
 *
 *     if (callback != null) {
 *       backEvent.addEventListener(callback);
 *
 *       return () => {
 *         backEvent.removeEventListener(callback);
 *       };
 *     }
 *
 *     return;
 *   }, [backEvent, handler]);
 *
 *   return (
 *     <View>
 *       <Button
 *         title="Add BackEvent"
 *         onPress={() => {
 *           setHandler({ callback: () => Alert.alert('back') });
 *         }}
 *       />
 *       <Button
 *         title="Remove BackEvent"
 *         onPress={() => {
 *           setHandler(undefined);
 *         }}
 *       />
 *     </View>
 *   );
 * }
 * ```
 */
export function useBackEvent() {
  const context = useContext(BackEventContext);
  const handlersRef = useRef<Set<() => void>>(new Set()).current;

  const isVisible = useVisibility();

  if (context == null) {
    throw new Error('useBackEvent must be used within a BackEventProvider');
  }

  const contextAddEventListener = context.addEventListener;
  const contextRemoveEventListener = context.removeEventListener;

  const addEventListener = useCallback(
    (...handlers: Array<() => void>) => {
      for (const handler of handlers) {
        handlersRef.add(handler);
        contextAddEventListener(handler);
      }
    },
    [contextAddEventListener, handlersRef]
  );

  const removeEventListener = useCallback(
    (...handlers: Array<() => void>) => {
      for (const handler of handlers) {
        handlersRef.delete(handler);
        contextRemoveEventListener(handler);
      }
    },
    [contextRemoveEventListener, handlersRef]
  );

  /**
   * 다른페이지로 이동했을 때 이벤트를 제거해야해요.
   * 이벤트를 제거하지 않으면 간섭이 생겨요.
   */
  useEffect(() => {
    if (!isVisible) {
      return;
    }

    /** 로컬에 저장해둔 핸들러를 다시 등록해요. */
    for (const handler of handlersRef) {
      contextAddEventListener(handler);
    }

    return () => {
      for (const handler of handlersRef) {
        contextRemoveEventListener(handler);
      }
    };
  }, [contextAddEventListener, contextRemoveEventListener, handlersRef, isVisible]);

  const backEvent = useMemo((): BackEventControls => {
    return {
      addEventListener,
      removeEventListener,
    };
  }, [addEventListener, removeEventListener]);

  return backEvent;
}

export function useBackEventContext() {
  const context = useContext(BackEventContext);
  if (context == null) {
    throw new Error('useBackEvent must be used within a BackEventProvider');
  }

  return {
    onGoBack: context.onBack,
    hasBackEvent: context.hasBackEvent,
  };
}
