"use client";

import { useRef, useCallback, useEffect, useState } from "react";

// ── Types ──

export interface L2Level {
  px: number;
  sz: number;
  n: number; // number of orders
}

export interface L2Book {
  coin: string;
  levels: [L2Level[], L2Level[]]; // [bids, asks]
  time: number;
}

export interface Trade {
  coin: string;
  side: string; // "A" = ask (sell), "B" = bid (buy)
  px: number;
  sz: number;
  hash: string;
  tid: number;
  time: number;
}

export interface L2Snapshot {
  bids: { px: number; sz: number }[];
  asks: { px: number; sz: number }[];
  mid: number;
  spread: number;
  totalBidVol: number;
  totalAskVol: number;
  imbalance: number;
  time: number;
}

export interface TradeTapeEntry {
  px: number;
  sz: number;
  side: "buy" | "sell";
  time: number;
}

// ── WebSocket Hook ──

interface HyperliquidData {
  l2: L2Snapshot | null;
  trades: TradeTapeEntry[];
  connected: boolean;
  error: string | null;
}

export function useHyperliquidWebSocket(coin: string = "BTC"): HyperliquidData {
  const wsRef = useRef<WebSocket | null>(null);
  const l2Ref = useRef<L2Snapshot | null>(null);
  const tradesRef = useRef<TradeTapeEntry[]>([]);
  const reconnectTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
  const subscribed = useRef(false);

  const [l2, setL2] = useState<L2Snapshot | null>(null);
  const [trades, setTrades] = useState<TradeTapeEntry[]>([]);
  const [connected, setConnected] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const connect = useCallback(() => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      // Already connected — just resubscribe
      wsRef.current.send(JSON.stringify({ type: "subscribe", subscription: { type: "l2Book", coin } }));
      wsRef.current.send(JSON.stringify({ type: "subscribe", subscription: { type: "trades", coin } }));
      return;
    }

    // Close stale connection
    if (wsRef.current) {
      wsRef.current.close();
      wsRef.current = null;
    }

    const ws = new WebSocket("wss://api.hyperliquid.xyz/ws");
    wsRef.current = ws;

    ws.onopen = () => {
      setConnected(true);
      setError(null);
      subscribed.current = false;
      // Subscribe — Hyperliquid WebSocket uses "method" not "type"
      ws.send(JSON.stringify({ method: "subscribe", subscription: { type: "l2Book", coin } }));
      ws.send(JSON.stringify({ method: "subscribe", subscription: { type: "trades", coin } }));
      subscribed.current = true;
    };

    ws.onmessage = (event) => {
      try {
        const msg = JSON.parse(event.data);
        if (msg.channel === "l2Book" && msg.data?.levels) {
          const levels = msg.data.levels as [L2Level[], L2Level[]];
          const bids = (levels[0] || []).map((l) => ({ px: parseFloat(String(l.px)), sz: parseFloat(String(l.sz)) }));
          const asks = (levels[1] || []).map((l) => ({ px: parseFloat(String(l.px)), sz: parseFloat(String(l.sz)) }));

          const bestBid = bids[0]?.px ?? 0;
          const bestAsk = asks[0]?.px ?? 0;
          const mid = (bestBid + bestAsk) / 2;
          const spread = bestAsk - bestBid;

          // Calculate volume totals (top 20 levels)
          const topBids = bids.slice(0, 20);
          const topAsks = asks.slice(0, 20);
          const totalBidVol = topBids.reduce((s, l) => s + l.sz, 0);
          const totalAskVol = topAsks.reduce((s, l) => s + l.sz, 0);
          const imbalance = totalBidVol + totalAskVol > 0
            ? (totalBidVol - totalAskVol) / (totalBidVol + totalAskVol)
            : 0;

          const snapshot: L2Snapshot = {
            bids, asks, mid, spread,
            totalBidVol, totalAskVol, imbalance,
            time: Date.now(),
          };
          l2Ref.current = snapshot;
          setL2(snapshot);
        } else if (msg.channel === "trades" && Array.isArray(msg.data)) {
          const newTrades: TradeTapeEntry[] = msg.data.map((t: Trade) => ({
            px: parseFloat(String(t.px)),
            sz: parseFloat(String(t.sz)),
            side: t.side === "B" ? "buy" : "sell",
            time: t.time || Date.now(),
          }));
          // Append to ring buffer — keep last ~500 trades
          tradesRef.current = [...tradesRef.current, ...newTrades].slice(-500);
          setTrades([...tradesRef.current]);
        }
      } catch {
        // Ignore parse errors
      }
    };

    ws.onerror = () => {
      setError("WebSocket error");
    };

    ws.onclose = () => {
      setConnected(false);
      // Auto-reconnect after 2s
      reconnectTimer.current = setTimeout(connect, 2000);
    };
  }, [coin]);

  useEffect(() => {
    connect();
    return () => {
      if (reconnectTimer.current) clearTimeout(reconnectTimer.current);
      if (wsRef.current) {
        wsRef.current.close();
        wsRef.current = null;
      }
    };
  }, [connect]);

  return { l2, trades, connected, error };
}