/*
 * WebSocket(STOMP over SockJS) — 공용 실시간 구독 클라이언트.
 * 근거: 계약 §6 — 핸드셰이크 GET /ws(SockJS), 브로드캐스트 prefix /topic.
 *   - RenderJob 완료:  /topic/render/{jobId}                (design.md §1-4)
 *   - 옥션 실시간 순위: /topic/auctions/{auctionId}/ranking  (M15 폴링→푸시 전환)
 * 단일 STOMP 연결을 refCount 로 공유하고, 연결/재연결/절단을 리스너로 브로드캐스트한다.
 */
import { Client, type IMessage, type StompSubscription } from '@stomp/stompjs';
import SockJS from 'sockjs-client';
import type { RenderJobDto } from './types';

/** 구독자에게 알리는 연결 상태(옥션 순위 폴백 판단용). */
export type WsStatus = 'connecting' | 'connected' | 'disconnected';

interface ConnListener {
  onConnect: () => void;
  onDisconnect: () => void;
}

let client: Client | null = null;
let refCount = 0;
const connListeners = new Set<ConnListener>();

const WS_URL = `${(import.meta.env.VITE_API_BASE as string | undefined) ?? ''}/ws`;

function ensureClient(): Client {
  if (client) return client;
  client = new Client({
    // SockJS 팩토리 (계약이 SockJS 핸드셰이크 명시).
    webSocketFactory: () => new SockJS(WS_URL) as unknown as WebSocket,
    reconnectDelay: 4000,
    heartbeatIncoming: 10000,
    heartbeatOutgoing: 10000,
    debug: () => {},
    onConnect: () => connListeners.forEach((l) => l.onConnect()),
    onWebSocketClose: () => connListeners.forEach((l) => l.onDisconnect()),
    onStompError: () => connListeners.forEach((l) => l.onDisconnect()),
  });
  client.activate();
  return client;
}

function acquire(): Client {
  refCount += 1;
  return ensureClient();
}

function release(): void {
  refCount -= 1;
  if (refCount <= 0 && client) {
    void client.deactivate();
    client = null;
    refCount = 0;
    connListeners.clear();
  }
}

/**
 * 특정 RenderJob 완료/실패 이벤트를 구독한다.
 * @returns 해제 함수 — 언마운트 시 호출.
 */
export function subscribeRenderJob(
  jobId: string,
  onUpdate: (job: RenderJobDto) => void,
): () => void {
  const c = acquire();
  const topic = `/topic/render/${jobId}`;
  let sub: StompSubscription | null = null;

  const doSubscribe = () => {
    sub = c.subscribe(topic, (msg: IMessage) => {
      try {
        onUpdate(JSON.parse(msg.body) as RenderJobDto);
      } catch {
        /* 잘못된 페이로드 무시 */
      }
    });
  };

  const listener: ConnListener = {
    onConnect: () => {
      sub?.unsubscribe();
      doSubscribe(); // 최초 연결·재연결 모두에서 재구독
    },
    onDisconnect: () => {
      sub = null;
    },
  };
  connListeners.add(listener);
  if (c.connected) doSubscribe();

  return () => {
    connListeners.delete(listener);
    sub?.unsubscribe();
    release();
  };
}

/**
 * 옥션 실시간 순위 스냅샷을 구독한다(M15). 서버는 순위 변동(응찰·마감·낙찰) 시 커밋 후 푸시한다.
 * 페이로드는 봉인 안전한 공개 순위만 담으므로, 구독자는 이를 <b>재조회 트리거</b>로 쓰고
 * 개인화 순위는 인증 상세 API로 다시 받는 것을 권장한다.
 *
 * @param onStatus 연결 상태 콜백 — 'disconnected' 시 호출부가 폴링으로 폴백한다.
 * @returns 해제 함수.
 */
export function subscribeAuctionRanking<T>(
  auctionId: string,
  onUpdate: (snapshot: T) => void,
  onStatus?: (status: WsStatus) => void,
): () => void {
  const c = acquire();
  const topic = `/topic/auctions/${auctionId}/ranking`;
  let sub: StompSubscription | null = null;

  const doSubscribe = () => {
    sub = c.subscribe(topic, (msg: IMessage) => {
      try {
        onUpdate(JSON.parse(msg.body) as T);
      } catch {
        /* 잘못된 페이로드 무시 */
      }
    });
  };

  const listener: ConnListener = {
    onConnect: () => {
      sub?.unsubscribe();
      doSubscribe();
      onStatus?.('connected');
    },
    onDisconnect: () => {
      sub = null;
      onStatus?.('disconnected');
    },
  };
  connListeners.add(listener);
  if (c.connected) {
    doSubscribe();
    onStatus?.('connected');
  } else {
    onStatus?.('connecting');
  }

  return () => {
    connListeners.delete(listener);
    sub?.unsubscribe();
    release();
  };
}
