// ============================================================
// WebSocket 连接管理
// 职责：心跳保活、指数退避重连、类型安全的消息收发
// 来源：docs/03-接口文档.md §六 连接管理
// ============================================================

import type { ClientMessage, ServerMessage, WsMessage } from "../types";

// WebSocket 地址：优先使用环境变量，否则基于当前页面地址自动推导
const WS_URL =
  import.meta.env.VITE_WS_URL ||
  `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/ws`;
const PING_INTERVAL = 30_000;   // 30 秒心跳
const MAX_RECONNECT_DELAY = 30_000; // 最大重连延迟 30 秒

type MessageHandler = (msg: ServerMessage) => void;
type StatusHandler = (status: ConnectionStatus) => void;

export type ConnectionStatus = "connecting" | "connected" | "disconnected";

export class CamTalkWebSocket {
  private ws: WebSocket | null = null;
  private pingTimer: ReturnType<typeof setInterval> | null = null;
  private reconnectAttempt = 0;
  private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
  private shouldReconnect = true;
  private token: string | undefined;
  private conversationId: string | undefined;

  private messageHandlers = new Set<MessageHandler>();
  private statusHandlers = new Set<StatusHandler>();
  private _status: ConnectionStatus = "disconnected";

  get status(): ConnectionStatus {
    return this._status;
  }

  /** 注册消息回调 */
  onMessage(handler: MessageHandler): () => void {
    this.messageHandlers.add(handler);
    return () => this.messageHandlers.delete(handler);
  }

  /** 注册连接状态回调 */
  onStatusChange(handler: StatusHandler): () => void {
    this.statusHandlers.add(handler);
    return () => this.statusHandlers.delete(handler);
  }

  /** 建立连接，可选传入 JWT token 和 conversation_id 用于认证和会话恢复 */
  connect(token?: string, conversationId?: string): void {
    if (this.ws?.readyState === WebSocket.OPEN) return;

    this.token = token;
    this.conversationId = conversationId;
    this.shouldReconnect = true;
    this.setStatus("connecting");

    const params = new URLSearchParams();
    if (token) params.set("token", token);
    if (conversationId) params.set("conversation_id", conversationId);
    const queryString = params.toString();
    const url = queryString ? `${WS_URL}?${queryString}` : WS_URL;
    const ws = new WebSocket(url);

    ws.onopen = () => {
      this.reconnectAttempt = 0;
      this.setStatus("connected");
      this.startPing();
    };

    ws.onmessage = (event) => {
      try {
        const msg = JSON.parse(event.data) as WsMessage;
        // 忽略 pong，心跳由服务端自动回复
        if (msg.type === "pong") return;
        this.messageHandlers.forEach((h) => h(msg as ServerMessage));
      } catch {
        console.error("[WS] 无法解析消息:", event.data);
      }
    };

    ws.onclose = () => {
      this.stopPing();
      this.setStatus("disconnected");
      if (this.shouldReconnect) {
        this.scheduleReconnect();
      }
    };

    ws.onerror = (err) => {
      console.error("[WS] 连接错误:", err);
      ws.close();
    };

    this.ws = ws;
  }

  /** 断开连接，不再自动重连 */
  disconnect(): void {
    this.shouldReconnect = false;
    this.clearTimers();
    this.ws?.close();
    this.ws = null;
    this.setStatus("disconnected");
  }

  /** 发送客户端消息 */
  send(msg: ClientMessage): void {
    if (this.ws?.readyState !== WebSocket.OPEN) {
      console.warn("[WS] 连接未就绪，消息丢弃:", msg.type);
      return;
    }
    this.ws.send(JSON.stringify(msg));
  }

  /** 发送 ping 心跳 */
  private startPing(): void {
    this.stopPing();
    this.pingTimer = setInterval(() => {
      this.send({ type: "ping" });
    }, PING_INTERVAL);
  }

  private stopPing(): void {
    if (this.pingTimer) {
      clearInterval(this.pingTimer);
      this.pingTimer = null;
    }
  }

  /** 指数退避 + 抖动重连 */
  private scheduleReconnect(): void {
    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempt), MAX_RECONNECT_DELAY);
    const jitter = Math.random() * 1000;
    const totalDelay = delay + jitter;

    console.log(`[WS] ${totalDelay.toFixed(0)}ms 后重连 (attempt ${this.reconnectAttempt})`);

    this.reconnectTimer = setTimeout(() => {
      this.reconnectAttempt++;
      this.connect(this.token, this.conversationId);
    }, totalDelay);
  }

  private setStatus(status: ConnectionStatus): void {
    this._status = status;
    this.statusHandlers.forEach((h) => h(status));
  }

  private clearTimers(): void {
    this.stopPing();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }
}

/** 创建单例实例 */
export const wsClient = new CamTalkWebSocket();
