"use client";

// ═══════════════════════════════════════════════════════════════════
//  useMeeting — the WebRTC + Socket.IO brain for the meeting room.
//  Matches the backend /meeting gateway exactly:
//   emit  'join'   { roomId, auditId?, user? }  → { ok, you, participants[] }
//   emit  'signal' { to, data }                 (offer / answer / ICE)
//   emit  'leave'  { }
//   emit  'mute:state' { muted }
//   emit  'chat:send'  { text }
//   on    'participant:joined' | 'participant:left' | 'participant:muted'
//   on    'signal' { from, data }
//   on    'chat:message'
//
//  Auth: the socket handshake sends { auth: { token } }. Pass the right
//  token — staff access_token, client clientPortalToken, or a guest token.
// ═══════════════════════════════════════════════════════════════════

import { useCallback, useEffect, useRef, useState } from "react";
import { io, Socket } from "socket.io-client";

export interface RemotePeer {
  id: string;
  name: string;
  initials: string;
  role: string;
  muted?: boolean;
  stream?: MediaStream;
  connected: boolean;
}

export interface ChatMessage {
  id: string;
  from: string;
  fromName: string;
  text: string;
  at: number;
  mine?: boolean;
}

export interface UseMeetingOpts {
  socketUrl: string;      // e.g. https://web.qrsyst.com  (namespace /meeting appended)
  roomId: string;         // the meeting room_code
  token: string;          // JWT for the socket handshake
  displayName: string;
  role?: string;
  auditId?: number | null;
  iceServers?: RTCIceServer[];
  audioDeviceId?: string;
  videoDeviceId?: string;
  startMuted?: boolean;
  startCamOff?: boolean;
}

const DEFAULT_ICE: RTCIceServer[] = [{ urls: "stun:stun.l.google.com:19302" }];

export function useMeeting(opts: UseMeetingOpts) {
  const { socketUrl, roomId, token, displayName, role = "client", auditId = null } = opts;
  const iceServers = opts.iceServers && opts.iceServers.length ? opts.iceServers : DEFAULT_ICE;

  const [localStream, setLocalStream] = useState<MediaStream | null>(null);
  const [peers, setPeers] = useState<Record<string, RemotePeer>>({});
  const [chat, setChat] = useState<ChatMessage[]>([]);
  const [status, setStatus] = useState<"idle" | "connecting" | "connected" | "error" | "left">("idle");
  const [error, setError] = useState<string | null>(null);
  const [micOn, setMicOn] = useState(true);
  const [camOn, setCamOn] = useState(true);
  const [selfId, setSelfId] = useState<string>("");

  const socketRef = useRef<Socket | null>(null);
  const pcsRef = useRef<Record<string, RTCPeerConnection>>({});
  const localStreamRef = useRef<MediaStream | null>(null);
  const roomJoinedRef = useRef(false);

  // ── create a peer connection to one remote id ──
  const makePc = useCallback((remoteId: string, isInitiator: boolean) => {
    if (pcsRef.current[remoteId]) return pcsRef.current[remoteId];
    const pc = new RTCPeerConnection({ iceServers });
    pcsRef.current[remoteId] = pc;

    // add our local tracks (if we have any — viewer mode has none)
    const myTracks = localStreamRef.current?.getTracks() || [];
    console.log("[MEETING] adding", myTracks.length, "local tracks to pc for", remoteId, myTracks.map(t=>t.kind));
    myTracks.forEach((t) => {
      try { pc.addTrack(t, localStreamRef.current!); } catch (err) { console.log("[MEETING] addTrack error", err); }
    });
    // viewer with no media still needs to receive
    if (!localStreamRef.current) {
      try { pc.addTransceiver("video", { direction: "recvonly" }); pc.addTransceiver("audio", { direction: "recvonly" }); } catch { /* noop */ }
    }

    pc.onicecandidate = (e) => {
      if (e.candidate) {
        socketRef.current?.emit("signal", { to: remoteId, data: { candidate: e.candidate } });
      }
    };

    pc.ontrack = (e) => {
      const [stream] = e.streams;
      console.log("[MEETING] ontrack from", remoteId, "kind:", e.track.kind, "streams:", e.streams.length, "audioTracks:", stream?.getAudioTracks().length);
      setPeers((prev) => ({
        ...prev,
        [remoteId]: { ...(prev[remoteId] || { id: remoteId, name: "", initials: "?", role: "client", connected: true }), stream },
      }));
    };

    pc.oniceconnectionstatechange = () => {
      console.log("[MEETING] ICE state for", remoteId, "=", pc.iceConnectionState);
    };
    pc.onconnectionstatechange = () => {
      console.log("[MEETING] connection state for", remoteId, "=", pc.connectionState);
      if (pc.connectionState === "failed") {
        try { pc.restartIce(); } catch { /* noop */ }
      }
    };

    if (isInitiator) {
      pc.onnegotiationneeded = async () => {
        try {
          const offer = await pc.createOffer();
          await pc.setLocalDescription(offer);
          socketRef.current?.emit("signal", { to: remoteId, data: { sdp: pc.localDescription } });
        } catch { /* noop */ }
      };
    }
    return pc;
  }, [iceServers]);

  // ── handle an incoming signal ──
  const handleSignal = useCallback(async (from: string, data: any) => {
    let pc = pcsRef.current[from];
    if (!pc) pc = makePc(from, false);

    if (data.sdp) {
      await pc.setRemoteDescription(new RTCSessionDescription(data.sdp));
      if (data.sdp.type === "offer") {
        const answer = await pc.createAnswer();
        await pc.setLocalDescription(answer);
        socketRef.current?.emit("signal", { to: from, data: { sdp: pc.localDescription } });
      }
    } else if (data.candidate) {
      try { await pc.addIceCandidate(new RTCIceCandidate(data.candidate)); } catch { /* noop */ }
    }
  }, [makePc]);

  // ── connect + join ──
  useEffect(() => {
    let cancelled = false;
    setStatus("connecting");

    (async () => {
      // 1. get camera + mic — with graceful fallback
      let stream: MediaStream | null = null;
      const vwant = opts.startCamOff ? false : (opts.videoDeviceId ? { deviceId: { exact: opts.videoDeviceId } } : true);
      const awant = opts.audioDeviceId ? { deviceId: { exact: opts.audioDeviceId } } : true;
      // try camera + mic (using chosen devices)
      try {
        stream = await navigator.mediaDevices.getUserMedia({ video: vwant, audio: awant });
      } catch {
        // try audio only
        try {
          stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: awant });
          setCamOn(false);
        } catch {
          // join as viewer (no local media) — can still see/hear others + chat
          stream = null;
          setCamOn(false);
          setMicOn(false);
        }
      }
      if (cancelled) { stream?.getTracks().forEach((t) => t.stop()); return; }
      localStreamRef.current = stream;
      // honor pre-join choices
      if (opts.startMuted && stream) { const a = stream.getAudioTracks()[0]; if (a) { a.enabled = false; setMicOn(false); } }
      if (opts.startCamOff) setCamOn(false);
      setLocalStream(stream);

      // 2. connect socket to /meeting namespace
      const socket = io(`${socketUrl}/meeting`, {
        transports: ["websocket"],
        auth: { token },
      });
      socketRef.current = socket;

      socket.on("connect_error", (err) => {
        setError(err.message || "Connection failed");
        setStatus("error");
      });

      socket.on("connect", () => {
        if (roomJoinedRef.current) return;
        roomJoinedRef.current = true;
        socket.emit(
          "join",
          { roomId, auditId, user: { name: displayName, role } },
          (res: any) => {
            if (!res?.ok) { setError(res?.error || "Could not join meeting"); setStatus("error"); return; }
            setSelfId(String(res.you?.id || ""));
            setStatus("connected");
            // existing participants → we initiate to each
            const initial: Record<string, RemotePeer> = {};
            (res.participants || []).forEach((p: any) => {
              initial[p.id] = { id: p.id, name: p.name, initials: p.initials, role: p.role, connected: true };
              makePc(p.id, true); // initiator triggers onnegotiationneeded → offer
            });
            setPeers(initial);
          },
        );
      });

      // someone new joined → they will send us an offer; just record them
      socket.on("participant:joined", (p: any) => {
        setPeers((prev) => ({
          ...prev,
          [p.id]: { id: p.id, name: p.name, initials: p.initials, role: p.role, connected: true },
        }));
      });

      socket.on("participant:left", (p: any) => {
        const id = String(p.id);
        pcsRef.current[id]?.close();
        delete pcsRef.current[id];
        setPeers((prev) => { const n = { ...prev }; delete n[id]; return n; });
      });

      socket.on("participant:muted", (p: any) => {
        setPeers((prev) => prev[p.id] ? { ...prev, [p.id]: { ...prev[p.id], muted: p.muted } } : prev);
      });

      socket.on("signal", ({ from, data }: any) => { handleSignal(String(from), data); });

      socket.on("chat:message", (m: any) => {
        setChat((prev) => [...prev, {
          id: m.id || `${Date.now()}-${Math.random()}`,
          from: String(m.from ?? m.fromId ?? ""),
          fromName: m.fromName || m.name || "Participant",
          text: m.text ?? m.message ?? "",
          at: m.at || Date.now(),
        }]);
      });
    })();

    return () => {
      cancelled = true;
      roomJoinedRef.current = false;
      Object.values(pcsRef.current).forEach((pc) => pc.close());
      pcsRef.current = {};
      socketRef.current?.emit("leave", {});
      socketRef.current?.disconnect();
      socketRef.current = null;
      localStreamRef.current?.getTracks().forEach((t) => t.stop());
      localStreamRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [socketUrl, roomId, token]);

  // ── controls ──
  const toggleMic = useCallback(() => {
    const track = localStreamRef.current?.getAudioTracks?.()[0];
    if (!track) return;
    track.enabled = !track.enabled;
    setMicOn(track.enabled);
    socketRef.current?.emit("mute:state", { muted: !track.enabled });
  }, []);

  const toggleCam = useCallback(() => {
    const track = localStreamRef.current?.getVideoTracks?.()[0];
    if (!track) return;
    track.enabled = !track.enabled;
    setCamOn(track.enabled);
  }, []);

  const sendChat = useCallback((text: string) => {
    const t = text.trim();
    if (!t) return;
    socketRef.current?.emit("chat:send", { text: t });
    setChat((prev) => [...prev, { id: `me-${Date.now()}`, from: selfId, fromName: displayName, text: t, at: Date.now(), mine: true }]);
  }, [selfId, displayName]);

  const leave = useCallback(() => {
    socketRef.current?.emit("leave", {});
    Object.values(pcsRef.current).forEach((pc) => pc.close());
    pcsRef.current = {};
    socketRef.current?.disconnect();
    localStreamRef.current?.getTracks().forEach((t) => t.stop());
    setStatus("left");
  }, []);

  return {
    localStream, peers, chat, status, error, micOn, camOn, selfId,
    toggleMic, toggleCam, sendChat, leave,
  };
}
