"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { useAccount } from "wagmi";
import { getRuntimeConfig } from "@/lib/config";
import type {
  PendingRequest,
  OutboundMessage,
} from "@/lib/types";

export function useProxySocket() {
  const { address, isConnected } = useAccount();
  const [pendingRequests, setPendingRequests] = useState<PendingRequest[]>([]);
  const [connected, setConnected] = useState(false);
  const [requiredChainId, setRequiredChainId] = useState<number | null>(null);
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const addressRef = useRef(address);
  addressRef.current = address;

  const send = useCallback((msg: OutboundMessage) => {
    const ws = wsRef.current;
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(msg));
    }
  }, []);

  // Ask for desktop-notification permission once on mount.
  useEffect(() => {
    if (typeof Notification !== "undefined" && Notification.permission === "default") {
      Notification.requestPermission().catch(() => {});
    }
  }, []);

  // WebAudio beep — desktop notifications don't reliably play a sound, so we
  // synthesize a short tone ourselves. Created lazily; unlocked on first gesture.
  const audioCtxRef = useRef<AudioContext | null>(null);

  const getAudioCtx = useCallback((): AudioContext | null => {
    if (typeof window === "undefined") return null;
    const Ctor =
      window.AudioContext ??
      (window as unknown as { webkitAudioContext?: typeof AudioContext })
        .webkitAudioContext;
    if (!Ctor) return null;
    if (!audioCtxRef.current) audioCtxRef.current = new Ctor();
    return audioCtxRef.current;
  }, []);

  // Browsers block audio until the page has user activation. Resume the context
  // on the first interaction so later beeps actually play.
  useEffect(() => {
    const unlock = () => {
      getAudioCtx()?.resume().catch(() => {});
    };
    window.addEventListener("pointerdown", unlock, { once: true });
    window.addEventListener("keydown", unlock, { once: true });
    return () => {
      window.removeEventListener("pointerdown", unlock);
      window.removeEventListener("keydown", unlock);
    };
  }, [getAudioCtx]);

  const beep = useCallback(() => {
    const ctx = getAudioCtx();
    if (!ctx) return;
    ctx.resume().catch(() => {});
    const now = ctx.currentTime;
    // Two quick rising tones.
    [880, 1175].forEach((freq, i) => {
      const osc = ctx.createOscillator();
      const gain = ctx.createGain();
      osc.type = "sine";
      osc.frequency.value = freq;
      const start = now + i * 0.13;
      gain.gain.setValueAtTime(0.0001, start);
      gain.gain.exponentialRampToValueAtTime(0.15, start + 0.02);
      gain.gain.exponentialRampToValueAtTime(0.0001, start + 0.12);
      osc.connect(gain).connect(ctx.destination);
      osc.start(start);
      osc.stop(start + 0.13);
    });
  }, [getAudioCtx]);

  const notify = useCallback((req: PendingRequest) => {
    if (typeof Notification === "undefined" || Notification.permission !== "granted") {
      return;
    }
    const fn = req.decoded_calldata?.function_name;
    const body = fn ? `${req.method} → ${fn}()` : req.method;
    try {
      const n = new Notification("New signing request", {
        body,
        tag: req.id, // dedupe if re-rendered
      });
      n.onclick = () => {
        window.focus();
        n.close();
      };
    } catch {
      // Notification constructor can throw on some platforms — ignore.
    }
  }, []);

  const connect = useCallback(async () => {
    if (wsRef.current?.readyState === WebSocket.OPEN) return;

    const { wsUrl } = await getRuntimeConfig();
    const ws = new WebSocket(wsUrl);

    ws.onopen = () => {
      setConnected(true);
      if (addressRef.current) {
        ws.send(
          JSON.stringify({ type: "account_connected", address: addressRef.current } as OutboundMessage)
        );
      }
    };

    ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        if (data.type === "signing_request") {
          const req = { ...data, receivedAt: Date.now() } as PendingRequest;
          setPendingRequests((prev) => [...prev, req]);
          notify(req);
          beep();
        } else if (data.type === "chain_config") {
          const chainId = parseInt(data.chainId, 16);
          setRequiredChainId(chainId);
        }
      } catch {
        console.warn("Invalid message from proxy:", event.data);
      }
    };

    ws.onclose = () => {
      setConnected(false);
      wsRef.current = null;
      reconnectTimer.current = setTimeout(connect, 2000);
    };

    ws.onerror = () => {
      ws.close();
    };

    wsRef.current = ws;
  }, [notify, beep]);

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

  useEffect(() => {
    if (!connected) return;
    if (isConnected && address) {
      send({ type: "account_connected", address });
    } else {
      send({ type: "account_disconnected" });
    }
  }, [isConnected, address, connected, send]);

  // Reflect the pending count in the tab title.
  useEffect(() => {
    const base = "Cipher Gate — Signing UI";
    document.title =
      pendingRequests.length > 0 ? `(${pendingRequests.length}) ${base}` : base;
  }, [pendingRequests.length]);

  const removeRequest = useCallback((id: string) => {
    setPendingRequests((prev) => prev.filter((r) => r.id !== id));
  }, []);

  const respond = useCallback(
    (id: string, result?: string, error?: { code: number; message: string }) => {
      if (error) {
        send({ id, type: "signing_response", error });
      } else {
        send({ id, type: "signing_response", result: result! });
      }
      removeRequest(id);
    },
    [send, removeRequest]
  );

  return {
    pendingRequests,
    proxyConnected: connected,
    requiredChainId,
    respond,
    removeRequest,
  };
}
