/**
 * V2 EventBus - 타입 안전한 이벤트 버스
 *
 * 특징:
 * - 타입 안전한 이벤트 발행/구독
 * - 에러 격리 (하나의 핸들러 실패가 다른 핸들러에 영향 없음)
 * - 병렬/순차 실행 지원
 * - 디버그 모드 지원
 * - 구독 자동 정리 (컴포넌트 언마운트 시)
 */

import {
  V2_EVENTS,
  V2EventName,
  V2EventPayloadMap,
  V2EventHandler,
  V2Unsubscribe,
} from "./types";

interface SubscriberInfo<T extends V2EventName> {
  id: string;
  handler: V2EventHandler<T>;
  componentId?: string;
  once: boolean;
}

interface EmitOptions {
  /** 병렬 실행 여부 (기본값: true) */
  parallel?: boolean;
  /** 타임아웃 (ms, 기본값: 5000) */
  timeout?: number;
  /** 실패 시 재시도 횟수 (기본값: 0) */
  retryCount?: number;
}

interface EmitResult {
  success: boolean;
  handlerCount: number;
  errors: Array<{ subscriberId: string; error: Error }>;
}

class V2EventBus {
  private subscribers: Map<V2EventName, Map<string, SubscriberInfo<any>>> =
    new Map();
  private subscriberIdCounter = 0;

  /** 디버그 모드 활성화 시 모든 이벤트 로깅 */
  public debug = false;

  /** 디버그용 로거 */
  private log(message: string, ...args: any[]) {
    if (this.debug) {
      console.log(`[V2EventBus] ${message}`, ...args);
    }
  }

  /** 에러 로거 */
  private logError(message: string, error: any) {
    console.error(`[V2EventBus] ${message}`, error);
  }

  /**
   * 이벤트 구독
   *
   * @param eventName - 이벤트 이름
   * @param handler - 이벤트 핸들러
   * @param options - 구독 옵션
   * @returns 구독 해제 함수
   *
   * @example
   * ```typescript
   * const unsubscribe = v2EventBus.subscribe(
   *   V2_EVENTS.TABLE_REFRESH,
   *   (payload) => {
   *     console.log("테이블 새로고침:", payload.tableName);
   *   },
   *   { componentId: "my-table" }
   * );
   *
   * // 컴포넌트 언마운트 시
   * unsubscribe();
   * ```
   */
  subscribe<T extends V2EventName>(
    eventName: T,
    handler: V2EventHandler<T>,
    options?: { componentId?: string; once?: boolean }
  ): V2Unsubscribe {
    const subscriberId = `sub_${++this.subscriberIdCounter}`;

    if (!this.subscribers.has(eventName)) {
      this.subscribers.set(eventName, new Map());
    }

    const eventSubscribers = this.subscribers.get(eventName)!;
    eventSubscribers.set(subscriberId, {
      id: subscriberId,
      handler,
      componentId: options?.componentId,
      once: options?.once ?? false,
    });

    this.log(
      `구독 등록: ${eventName} (${subscriberId})`,
      options?.componentId ? `컴포넌트: ${options.componentId}` : ""
    );

    // 구독 해제 함수 반환
    return () => {
      this.unsubscribe(eventName, subscriberId);
    };
  }

  /**
   * 일회성 이벤트 구독 (한 번 실행 후 자동 해제)
   */
  once<T extends V2EventName>(
    eventName: T,
    handler: V2EventHandler<T>,
    options?: { componentId?: string }
  ): V2Unsubscribe {
    return this.subscribe(eventName, handler, { ...options, once: true });
  }

  /**
   * 이벤트 구독 해제
   */
  private unsubscribe(eventName: V2EventName, subscriberId: string): void {
    const eventSubscribers = this.subscribers.get(eventName);
    if (eventSubscribers) {
      eventSubscribers.delete(subscriberId);
      this.log(`구독 해제: ${eventName} (${subscriberId})`);

      // 구독자가 없으면 Map 정리
      if (eventSubscribers.size === 0) {
        this.subscribers.delete(eventName);
      }
    }
  }

  /**
   * 특정 컴포넌트의 모든 구독 해제
   *
   * @param componentId - 컴포넌트 ID
   *
   * @example
   * ```typescript
   * // useEffect cleanup에서 사용
   * useEffect(() => {
   *   return () => {
   *     v2EventBus.unsubscribeByComponent("my-table-component");
   *   };
   * }, []);
   * ```
   */
  unsubscribeByComponent(componentId: string): void {
    let unsubscribedCount = 0;

    this.subscribers.forEach((eventSubscribers, eventName) => {
      eventSubscribers.forEach((subscriber, subscriberId) => {
        if (subscriber.componentId === componentId) {
          eventSubscribers.delete(subscriberId);
          unsubscribedCount++;
        }
      });

      // 구독자가 없으면 Map 정리
      if (eventSubscribers.size === 0) {
        this.subscribers.delete(eventName);
      }
    });

    this.log(
      `컴포넌트 구독 해제: ${componentId} (${unsubscribedCount}개 해제)`
    );
  }

  /**
   * 이벤트 발행
   *
   * @param eventName - 이벤트 이름
   * @param payload - 이벤트 데이터
   * @param options - 발행 옵션
   * @returns 발행 결과
   *
   * @example
   * ```typescript
   * await v2EventBus.emit(V2_EVENTS.TABLE_REFRESH, {
   *   tableName: "item_info",
   *   target: "single",
   * });
   * ```
   */
  async emit<T extends V2EventName>(
    eventName: T,
    payload: V2EventPayloadMap[T],
    options: EmitOptions = {}
  ): Promise<EmitResult> {
    const { parallel = true, timeout = 5000, retryCount = 0 } = options;

    const eventSubscribers = this.subscribers.get(eventName);

    if (!eventSubscribers || eventSubscribers.size === 0) {
      this.log(`이벤트 발행 (구독자 없음): ${eventName}`);
      return { success: true, handlerCount: 0, errors: [] };
    }

    this.log(`이벤트 발행: ${eventName} → ${eventSubscribers.size}개 구독자`);

    const errors: Array<{ subscriberId: string; error: Error }> = [];
    const subscribersToRemove: string[] = [];

    // 핸들러 실행 함수
    const executeHandler = async (
      subscriber: SubscriberInfo<T>
    ): Promise<void> => {
      const executeWithRetry = async (retriesLeft: number): Promise<void> => {
        try {
          // 타임아웃 적용
          const timeoutPromise = new Promise<never>((_, reject) => {
            setTimeout(
              () => reject(new Error(`Handler timeout after ${timeout}ms`)),
              timeout
            );
          });

          const handlerPromise = Promise.resolve(subscriber.handler(payload));
          await Promise.race([handlerPromise, timeoutPromise]);

          if (subscriber.once) {
            subscribersToRemove.push(subscriber.id);
          }
        } catch (error) {
          if (retriesLeft > 0) {
            this.log(
              `핸들러 재시도: ${subscriber.id} (남은 횟수: ${retriesLeft})`
            );
            await executeWithRetry(retriesLeft - 1);
          } else {
            const err =
              error instanceof Error ? error : new Error(String(error));
            this.logError(`핸들러 실행 실패: ${subscriber.id}`, err);
            errors.push({ subscriberId: subscriber.id, error: err });
          }
        }
      };

      await executeWithRetry(retryCount);
    };

    // 병렬 또는 순차 실행
    const subscriberArray = Array.from(eventSubscribers.values());

    if (parallel) {
      // 병렬 실행 (Promise.allSettled로 에러 격리)
      await Promise.allSettled(
        subscriberArray.map((subscriber) => executeHandler(subscriber))
      );
    } else {
      // 순차 실행
      for (const subscriber of subscriberArray) {
        await executeHandler(subscriber);
      }
    }

    // 일회성 구독자 정리
    subscribersToRemove.forEach((id) => {
      eventSubscribers.delete(id);
    });

    if (eventSubscribers.size === 0) {
      this.subscribers.delete(eventName);
    }

    const success = errors.length === 0;

    if (errors.length > 0) {
      this.log(
        `이벤트 완료: ${eventName} (성공: ${subscriberArray.length - errors.length}, 실패: ${errors.length})`
      );
    }

    return {
      success,
      handlerCount: subscriberArray.length,
      errors,
    };
  }

  /**
   * 동기적 이벤트 발행 (결과 대기 없음)
   *
   * 빠른 발행이 필요하고 결과를 기다릴 필요 없을 때 사용
   */
  emitSync<T extends V2EventName>(
    eventName: T,
    payload: V2EventPayloadMap[T]
  ): void {
    this.emit(eventName, payload).catch((error) => {
      this.logError(`동기 이벤트 발행 실패: ${eventName}`, error);
    });
  }

  /**
   * 이벤트 구독자 수 조회
   */
  getSubscriberCount(eventName: V2EventName): number {
    return this.subscribers.get(eventName)?.size ?? 0;
  }

  /**
   * 모든 구독 해제 (테스트용)
   */
  clear(): void {
    this.subscribers.clear();
    this.log("모든 구독 해제됨");
  }

  /**
   * 현재 구독 상태 출력 (디버그용)
   */
  printState(): void {
    console.log("=== V2EventBus 상태 ===");
    this.subscribers.forEach((subscribers, eventName) => {
      console.log(`${eventName}: ${subscribers.size}개 구독자`);
      subscribers.forEach((sub) => {
        console.log(`  - ${sub.id} (컴포넌트: ${sub.componentId ?? "없음"})`);
      });
    });
    console.log("======================");
  }
}

// 싱글톤 인스턴스 생성 및 내보내기
export const v2EventBus = new V2EventBus();

// 개발 환경에서 window에 노출 (디버깅용)
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
  (window as any).__v2EventBus = v2EventBus;
}

// 클래스도 내보내기 (테스트용)
export { V2EventBus };

