import { useState, useEffect, useRef, useCallback } from 'react';
import { WebSocketClient } from '../../infrastructure/ws/websocket-client';

/* -------------------------------------------------------------------------- */
/*  useWebSocket                                                              */
/*  React hook that wraps the IT0 WebSocketClient infrastructure class.       */
/*  Provides: connected state, message buffer, send fn, disconnect fn.        */
/*  Auto-connects on mount and auto-reconnects via the underlying client.     */
/* -------------------------------------------------------------------------- */

export interface WebSocketMessage {
  type: string;
  [key: string]: unknown;
}

export interface UseWebSocketOptions {
  /** WebSocket server URL. Defaults to NEXT_PUBLIC_WS_URL or ws://localhost:3001 */
  url?: string;
  /** Event types to subscribe to. If omitted, all messages are captured. */
  eventTypes?: string[];
  /** Maximum number of messages to keep in the buffer (default 100) */
  bufferSize?: number;
  /** If false, the connection is deferred until you call connect manually (default true) */
  autoConnect?: boolean;
}

export interface UseWebSocketReturn {
  /** Whether the WebSocket is currently connected */
  connected: boolean;
  /** Buffered messages (most recent last) */
  messages: WebSocketMessage[];
  /** Send a JSON-serialisable payload to the server */
  send: (data: unknown) => void;
  /** Tear down the connection (will not auto-reconnect) */
  disconnect: () => void;
}

const DEFAULT_URL =
  (typeof window !== 'undefined' &&
    (window as any).__NEXT_PUBLIC_WS_URL) ||
  process.env.NEXT_PUBLIC_WS_URL ||
  'ws://localhost:3001';

export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketReturn {
  const {
    url = DEFAULT_URL,
    eventTypes,
    bufferSize = 100,
    autoConnect = true,
  } = options;

  const [connected, setConnected] = useState(false);
  const [messages, setMessages] = useState<WebSocketMessage[]>([]);
  const clientRef = useRef<WebSocketClient | null>(null);
  const cleanupFns = useRef<Array<() => void>>([]);

  /* -- Append a message to the buffer -- */
  const pushMessage = useCallback(
    (msg: WebSocketMessage) => {
      setMessages((prev) => {
        const next = [...prev, msg];
        return next.length > bufferSize ? next.slice(-bufferSize) : next;
      });
    },
    [bufferSize],
  );

  /* -- Initialise / tear down -- */
  useEffect(() => {
    if (!autoConnect) return;

    const client = new WebSocketClient(url);
    clientRef.current = client;

    // Listen for connection state by subscribing to a synthetic 'open' event.
    // Since the underlying WebSocketClient does not expose an onOpen callback
    // directly, we poll the readyState via a tiny interval until connected,
    // and use the 'close' event callback already available through the on() API
    // if the client emits typed events. As a pragmatic alternative, we subscribe
    // to all incoming messages and flip connected on first receipt.

    // Strategy: subscribe to every event the caller cares about (or '*' for all).
    if (eventTypes && eventTypes.length > 0) {
      for (const type of eventTypes) {
        const unsub = client.on(type, (data: any) => {
          setConnected(true);
          pushMessage(data as WebSocketMessage);
        });
        cleanupFns.current.push(unsub);
      }
    } else {
      // Subscribe to a catch-all by listening to every message type.
      // The WebSocketClient dispatches by `data.type`, so we use a Proxy-style
      // approach: wrap the original onmessage after connect().
      // We store a patching function that runs after client.connect().
      const origConnect = client.connect.bind(client);
      client.connect = function patchedConnect() {
        origConnect();
        // Access the raw WS after connection
        const ws = (client as any).ws as WebSocket | null;
        if (ws) {
          const origOnOpen = ws.onopen;
          ws.onopen = (ev) => {
            setConnected(true);
            if (origOnOpen) (origOnOpen as any).call(ws, ev);
          };
          const origOnMessage = ws.onmessage;
          ws.onmessage = (ev) => {
            try {
              const parsed = JSON.parse(ev.data);
              pushMessage(parsed as WebSocketMessage);
            } catch {
              // non-JSON messages — still pass through
            }
            if (origOnMessage) (origOnMessage as any).call(ws, ev);
          };
          const origOnClose = ws.onclose;
          ws.onclose = (ev) => {
            setConnected(false);
            if (origOnClose) (origOnClose as any).call(ws, ev);
          };
        }
      };
    }

    client.connect();

    return () => {
      cleanupFns.current.forEach((fn) => fn());
      cleanupFns.current = [];
      client.disconnect();
      clientRef.current = null;
      setConnected(false);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [url, autoConnect]);

  /* -- Send -- */
  const send = useCallback((data: unknown) => {
    clientRef.current?.send(data);
  }, []);

  /* -- Disconnect -- */
  const disconnect = useCallback(() => {
    clientRef.current?.disconnect();
    clientRef.current = null;
    setConnected(false);
  }, []);

  return { connected, messages, send, disconnect };
}
