/**
 * usePopEvent - POP 컴포넌트 간 이벤트 통신 훅
 *
 * 같은 화면(screenId) 안에서만 동작하는 이벤트 버스.
 * 다른 screenId 간에는 완전히 격리됨.
 *
 * 주요 기능:
 * - publish/subscribe: 일회성 이벤트 (거래처 선택됨, 저장 완료 등)
 * - getSharedData/setSharedData: 지속성 상태 (버튼 클릭 시 다른 컴포넌트 값 수집용)
 *
 * 사용 패턴:
 * ```typescript
 * const { publish, subscribe, getSharedData, setSharedData } = usePopEvent("S001");
 *
 * // 이벤트 구독 (반드시 useEffect 안에서, cleanup 필수)
 * useEffect(() => {
 *   const unsub = subscribe("supplier-selected", (payload) => {
 *     console.log(payload.supplierId);
 *   });
 *   return unsub;
 * }, []);
 *
 * // 이벤트 발행
 * publish("supplier-selected", { supplierId: "SUP-001" });
 *
 * // 공유 데이터 저장/조회
 * setSharedData("selectedSupplier", { id: "SUP-001" });
 * const supplier = getSharedData("selectedSupplier");
 * ```
 */

import { useCallback, useRef } from "react";

// ===== 타입 정의 =====

/** 이벤트 콜백 함수 타입 */
type EventCallback = (payload: unknown) => void;

/** 화면별 이벤트 리스너 맵: eventName -> Set<callback> */
type ListenerMap = Map<string, Set<EventCallback>>;

/** 화면별 공유 데이터 맵: key -> value */
type SharedDataMap = Map<string, unknown>;

// ===== 전역 저장소 (React 외부, 모듈 스코프) =====
// SSR 환경에서 서버/클라이언트 간 공유 방지

/** screenId별 이벤트 리스너 저장소 */
const screenBuses: Map<string, ListenerMap> =
  typeof window !== "undefined" ? new Map() : new Map();

/** screenId별 공유 데이터 저장소 */
const sharedDataStore: Map<string, SharedDataMap> =
  typeof window !== "undefined" ? new Map() : new Map();

// ===== 내부 헬퍼 =====

/** 해당 screenId의 리스너 맵 가져오기 (없으면 생성) */
function getListenerMap(screenId: string): ListenerMap {
  let map = screenBuses.get(screenId);
  if (!map) {
    map = new Map();
    screenBuses.set(screenId, map);
  }
  return map;
}

/** 해당 screenId의 공유 데이터 맵 가져오기 (없으면 생성) */
function getSharedMap(screenId: string): SharedDataMap {
  let map = sharedDataStore.get(screenId);
  if (!map) {
    map = new Map();
    sharedDataStore.set(screenId, map);
  }
  return map;
}

// ===== 외부 API: 화면 정리 =====

/**
 * 화면 언마운트 시 해당 screenId의 모든 리스너 + 공유 데이터 정리
 * 메모리 누수 방지용. 뷰어 또는 PopRenderer에서 화면 전환 시 호출.
 */
export function cleanupScreen(screenId: string): void {
  screenBuses.delete(screenId);
  sharedDataStore.delete(screenId);
}

// ===== 메인 훅 =====

/**
 * POP 컴포넌트 간 이벤트 통신 훅
 *
 * @param screenId - 화면 ID (같은 screenId 안에서만 통신)
 * @returns publish, subscribe, getSharedData, setSharedData
 */
export function usePopEvent(screenId: string) {
  // screenId를 ref로 저장 (콜백 안정성)
  const screenIdRef = useRef(screenId);
  screenIdRef.current = screenId;

  /**
   * 이벤트 발행
   * 해당 screenId + eventName에 등록된 모든 콜백에 payload 전달
   */
  const publish = useCallback(
    (eventName: string, payload?: unknown): void => {
      const listeners = getListenerMap(screenIdRef.current);
      const callbacks = listeners.get(eventName);
      if (!callbacks || callbacks.size === 0) return;

      // Set을 배열로 복사 후 순회 (순회 중 unsubscribe 안전)
      const callbackArray = Array.from(callbacks);
      for (const cb of callbackArray) {
        try {
          cb(payload);
        } catch (err) {
          // 개별 콜백 에러가 다른 콜백 실행을 막지 않음
          console.error(
            `[usePopEvent] 콜백 에러 (screen: ${screenIdRef.current}, event: ${eventName}):`,
            err
          );
        }
      }
    },
    []
  );

  /**
   * 이벤트 구독
   *
   * 주의: 반드시 useEffect 안에서 호출하고, 반환값(unsubscribe)을 cleanup에서 호출할 것.
   *
   * @returns unsubscribe 함수
   */
  const subscribe = useCallback(
    (eventName: string, callback: EventCallback): (() => void) => {
      const listeners = getListenerMap(screenIdRef.current);

      let callbacks = listeners.get(eventName);
      if (!callbacks) {
        callbacks = new Set();
        listeners.set(eventName, callbacks);
      }
      callbacks.add(callback);

      // unsubscribe 함수 반환
      const capturedScreenId = screenIdRef.current;
      return () => {
        const map = screenBuses.get(capturedScreenId);
        if (!map) return;
        const cbs = map.get(eventName);
        if (!cbs) return;
        cbs.delete(callback);
        // 빈 Set 정리
        if (cbs.size === 0) {
          map.delete(eventName);
        }
      };
    },
    []
  );

  /**
   * 공유 데이터 조회
   * 다른 컴포넌트가 setSharedData로 저장한 값을 가져옴
   */
  const getSharedData = useCallback(
    <T = unknown>(key: string): T | undefined => {
      const shared = sharedDataStore.get(screenIdRef.current);
      if (!shared) return undefined;
      return shared.get(key) as T | undefined;
    },
    []
  );

  /**
   * 공유 데이터 저장
   * 같은 screenId의 다른 컴포넌트가 getSharedData로 읽을 수 있음
   */
  const setSharedData = useCallback(
    (key: string, value: unknown): void => {
      const shared = getSharedMap(screenIdRef.current);
      shared.set(key, value);
    },
    []
  );

  return { publish, subscribe, getSharedData, setSharedData } as const;
}
