type ChangeCallback = () => void;

interface WSEvent {
  table: string;
  action: 'INSERT' | 'UPDATE' | 'DELETE';
  company_id: string;
}

type WSRole = 'purchaser' | 'textile' | 'washing';

class WebSocketClient {
  private ws: WebSocket | null = null;
  private listeners = new Map<string, Set<ChangeCallback>>();
  private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
  private role: WSRole | null = null;
  private connected = false;

  connect(role: WSRole) {
    this.role = role;
    this.doConnect();
  }

  disconnect() {
    this.connected = false;
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
  }

  subscribe(table: string, callback: ChangeCallback): () => void {
    if (!this.listeners.has(table)) {
      this.listeners.set(table, new Set());
    }
    this.listeners.get(table)!.add(callback);
    return () => {
      this.listeners.get(table)?.delete(callback);
      if (this.listeners.get(table)?.size === 0) {
        this.listeners.delete(table);
      }
    };
  }

  private doConnect() {
    if (!this.role) return;
    this.connected = true;

    const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
    const url = `${protocol}//${location.host}/ws/v1/${this.role}`;

    try {
      this.ws = new WebSocket(url);

      this.ws.onmessage = (e) => {
        try {
          const event: WSEvent = JSON.parse(e.data);
          this.listeners.get(event.table)?.forEach(cb => cb());
          this.listeners.get('*')?.forEach(cb => cb());
        } catch {
          // ignore malformed messages
        }
      };

      this.ws.onclose = () => {
        if (this.connected) this.scheduleReconnect();
      };

      this.ws.onerror = () => {
        this.ws?.close();
      };
    } catch {
      this.scheduleReconnect();
    }
  }

  private scheduleReconnect() {
    if (this.reconnectTimer) return;
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      if (this.connected) this.doConnect();
    }, 3000);
  }
}

export const wsClient = new WebSocketClient();
