/**
 * WebSocket hook for real-time event subscriptions.
 *
 * Features:
 * - Single shared connection per browser tab
 * - Exponential backoff reconnection (1s -> 2s -> 4s -> ... -> 30s max)
 * - Component-level event subscriptions
 * - Connection state management
 */
import { useEffect, useRef, useState } from "react";

// Connection states
export type ConnectionState = "connecting" | "connected" | "disconnected";

// Event message format from backend
interface WebSocketMessage {
  type: "event";
  event: string;
  data: Record<string, unknown>;
  timestamp: string;
}

// Event handler type
type EventHandler = (data: Record<string, unknown>) => void;

// Singleton WebSocket manager
class WebSocketManager {
  private static instance: WebSocketManager | null = null;
  private ws: WebSocket | null = null;
  private subscribers: Map<string, Set<EventHandler>> = new Map();
  private stateListeners: Set<(state: ConnectionState) => void> = new Set();
  private reconnectAttempts = 0;
  private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
  private state: ConnectionState = "disconnected";

  // Backoff configuration
  private readonly INITIAL_DELAY = 1000; // 1 second
  private readonly MAX_DELAY = 30000; // 30 seconds
  private readonly BACKOFF_MULTIPLIER = 2;

  private constructor() {}

  static getInstance(): WebSocketManager {
    if (!WebSocketManager.instance) {
      WebSocketManager.instance = new WebSocketManager();
    }
    return WebSocketManager.instance;
  }

  private getWebSocketUrl(): string {
    if (typeof window === "undefined") {
      return "ws://localhost:8000/ws/events";
    }

    const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
    const host = window.location.hostname;
    const port = window.location.port || (protocol === "wss:" ? "443" : "80");

    return `${protocol}//${host}:${port}/ws/events`;
  }

  connect(): void {
    if (
      this.ws?.readyState === WebSocket.OPEN ||
      this.ws?.readyState === WebSocket.CONNECTING
    ) {
      return;
    }

    this.setState("connecting");
    const url = this.getWebSocketUrl();

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

      this.ws.onopen = () => {
        console.log("[WS] Connected to", url);
        this.reconnectAttempts = 0;
        this.setState("connected");
      };

      this.ws.onmessage = (event) => {
        try {
          const message: WebSocketMessage = JSON.parse(event.data);
          if (message.type === "event") {
            this.notifySubscribers(message.event, message.data);
          }
        } catch (e) {
          console.error("[WS] Failed to parse message:", e);
        }
      };

      this.ws.onclose = (event) => {
        console.log(`[WS] Disconnected (code: ${event.code})`);
        this.ws = null;
        this.setState("disconnected");
        this.scheduleReconnect();
      };

      this.ws.onerror = (error) => {
        console.error("[WS] Error:", error);
      };
    } catch (e) {
      console.error("[WS] Connection failed:", e);
      this.scheduleReconnect();
    }
  }

  private scheduleReconnect(): void {
    // Only reconnect if there are still subscribers
    if (this.getTotalSubscriberCount() === 0) {
      return;
    }

    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout);
    }

    const delay = Math.min(
      this.INITIAL_DELAY *
        Math.pow(this.BACKOFF_MULTIPLIER, this.reconnectAttempts),
      this.MAX_DELAY
    );

    console.log(
      `[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1})`
    );

    this.reconnectTimeout = setTimeout(() => {
      this.reconnectAttempts++;
      this.connect();
    }, delay);
  }

  disconnect(): void {
    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout);
      this.reconnectTimeout = null;
    }

    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }

    this.setState("disconnected");
  }

  subscribe(eventType: string, handler: EventHandler): () => void {
    if (!this.subscribers.has(eventType)) {
      this.subscribers.set(eventType, new Set());
    }
    this.subscribers.get(eventType)!.add(handler);

    // Start connection if this is first subscriber
    if (this.getTotalSubscriberCount() === 1) {
      this.connect();
    }

    // Return unsubscribe function
    return () => {
      const handlers = this.subscribers.get(eventType);
      if (handlers) {
        handlers.delete(handler);
        if (handlers.size === 0) {
          this.subscribers.delete(eventType);
        }
      }

      // Disconnect if no subscribers left
      if (this.getTotalSubscriberCount() === 0) {
        this.disconnect();
      }
    };
  }

  subscribeToState(listener: (state: ConnectionState) => void): () => void {
    this.stateListeners.add(listener);
    // Immediately notify of current state
    listener(this.state);

    return () => {
      this.stateListeners.delete(listener);
    };
  }

  private notifySubscribers(
    eventType: string,
    data: Record<string, unknown>
  ): void {
    const handlers = this.subscribers.get(eventType);
    if (handlers) {
      handlers.forEach((handler) => {
        try {
          handler(data);
        } catch (e) {
          console.error(`[WS] Handler error for ${eventType}:`, e);
        }
      });
    }
  }

  private setState(newState: ConnectionState): void {
    this.state = newState;
    this.stateListeners.forEach((listener) => listener(newState));
  }

  private getTotalSubscriberCount(): number {
    let count = 0;
    this.subscribers.forEach((handlers) => {
      count += handlers.size;
    });
    return count;
  }

  getState(): ConnectionState {
    return this.state;
  }
}

/**
 * Hook to subscribe to WebSocket events.
 *
 * @param eventType - The event type to subscribe to (e.g., "system_stats", "ups_battery")
 * @param handler - Callback function called when event is received
 *
 * @example
 * ```tsx
 * function MyComponent() {
 *   useWebSocketEvent("system_stats", (data) => {
 *     console.log("System stats:", data);
 *   });
 * }
 * ```
 */
export function useWebSocketEvent(
  eventType: string,
  handler: EventHandler
): void {
  const handlerRef = useRef(handler);
  handlerRef.current = handler;

  useEffect(() => {
    const wsManager = WebSocketManager.getInstance();

    const wrappedHandler: EventHandler = (data) => {
      handlerRef.current(data);
    };

    const unsubscribe = wsManager.subscribe(eventType, wrappedHandler);

    return unsubscribe;
  }, [eventType]);
}

/**
 * Hook to get current WebSocket connection state.
 *
 * @returns Current connection state: "connecting" | "connected" | "disconnected"
 *
 * @example
 * ```tsx
 * function StatusIndicator() {
 *   const connectionState = useWebSocketState();
 *   return <span>{connectionState}</span>;
 * }
 * ```
 */
export function useWebSocketState(): ConnectionState {
  const [state, setState] = useState<ConnectionState>("disconnected");

  useEffect(() => {
    const wsManager = WebSocketManager.getInstance();
    const unsubscribe = wsManager.subscribeToState(setState);
    return unsubscribe;
  }, []);

  return state;
}
