/**
 * DataPortBus — INVYONE 컴포넌트 간 DataPort 통신용 경량 pub/sub 버스.
 *
 * 존재 이유:
 *   v2-core 의 V2EventBus 는 V2EventName 이 사전 정의된 타입 안전 이벤트 버스라,
 *   DataPort 처럼 런타임에 "componentId.portName" 으로 동적 채널명을 다루기에
 *   적합하지 않다. 그래서 DataPort 전용으로 독립된 경량 버스를 둔다.
 *
 * 관계:
 *   v2EventBus     — 테이블 refresh 등 사전 정의된 시스템 이벤트
 *   DataPortBus    — 화면 런타임의 DataPort 브리지 (이 파일)
 *   둘은 서로 독립. 공존.
 *
 * 채널 규약:
 *   channel = `${componentId}.${portName}`
 *   예: "cmp_table_1.selectedRow", "cmp_search_1.searchParams"
 */

type Handler = (value: unknown) => void;

/**
 * 화면(ScreenDesigner 인스턴스) 단위로 독립적인 DataPortBus.
 *
 * 같은 사용자가 여러 화면을 동시에 띄워도 서로 간섭하지 않도록 화면 마운트 시
 * new 로 만들고 언마운트 시 destroy 한다.
 */
export class DataPortBus {
  private channels = new Map<string, Set<Handler>>();

  /** componentId + portName → 내부 채널 문자열 */
  static channelOf(componentId: string, portName: string): string {
    return `${componentId}.${portName}`;
  }

  /**
   * 구독 등록. 반환값은 cleanup 함수 (호출 시 해당 구독만 해제).
   */
  subscribe(channel: string, handler: Handler): () => void {
    if (!this.channels.has(channel)) {
      this.channels.set(channel, new Set());
    }
    this.channels.get(channel)!.add(handler);
    return () => {
      this.channels.get(channel)?.delete(handler);
    };
  }

  /**
   * 값 발행. 해당 채널에 구독 중인 모든 핸들러를 동기 호출한다.
   * 하나의 핸들러가 throw 해도 나머지는 계속 실행한다.
   */
  publish(channel: string, value: unknown): void {
    const handlers = this.channels.get(channel);
    if (!handlers || handlers.size === 0) return;
    handlers.forEach((h) => {
      try {
        h(value);
      } catch (err) {
        console.error(`[DataPortBus] handler error on "${channel}":`, err);
      }
    });
  }

  /** 모든 구독 제거. 화면 언마운트 시 호출. */
  destroy(): void {
    this.channels.clear();
  }

  /** 디버깅용: 현재 활성 채널 목록 */
  getChannels(): string[] {
    return Array.from(this.channels.keys());
  }

  /** 디버깅용: 특정 채널의 구독자 수 */
  getSubscriberCount(channel: string): number {
    return this.channels.get(channel)?.size ?? 0;
  }
}

/**
 * 단일 화면만 쓰는 경우를 위한 전역 기본 인스턴스.
 *
 * 여러 화면을 동시에 띄우는 시나리오에서는 화면마다 new DataPortBus() 를 만들어
 * React Context 로 내려주는 게 권장.
 */
export const defaultDataPortBus = new DataPortBus();
