import React, { useState, useEffect, useRef, useCallback } from "react";
import "./MediaControl.css";
import VolumeOff from "@mui/icons-material/VolumeOff";
import VolumeUp from "@mui/icons-material/VolumeUp";
import MicOff from "@mui/icons-material/MicOff";
import Mic from "@mui/icons-material/Mic";
import VideocamOff from "@mui/icons-material/VideocamOff";
import Videocam from "@mui/icons-material/Videocam";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import { ReadyState } from "react-use-websocket";
import { Session, GlobalContext } from "./GlobalContext";
import { useContext } from "react";
import WebRTCStatus from "./WebRTCStatus";
import Moveable from "react-moveable";
import { flushSync } from "react-dom";

const debug = true;
// When true, do not send host candidates to the signaling server. Keeps TURN relays preferred.
const FILTER_HOST_CANDIDATES = false; // Temporarily disabled to test direct connections

/* ---------- Synthetic Tracks Helpers ---------- */

// Helper to hash a string to a color
function nameToColor(name: string): string {
  // Simple hash function (djb2)
  let hash = 5381;
  for (let i = 0; i < name.length; i++) {
    hash = (hash << 5) + hash + name.charCodeAt(i);
  }
  // Generate HSL color from hash
  const hue = Math.abs(hash) % 360;
  const sat = 60 + (Math.abs(hash) % 30); // 60-89%
  const light = 45 + (Math.abs(hash) % 30); // 45-74%
  return `hsl(${hue},${sat}%,${light}%)`;
}

// Accepts an optional name for color selection
const createAnimatedVideoTrack = ({
  width = 320,
  height = 240,
  name,
}: { width?: number; height?: number; name?: string } = {}): MediaStreamTrack => {
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;

  const ctx = canvas.getContext("2d");
  if (!ctx) throw new Error("Could not get canvas context");

  // Pick color based on name, fallback to default
  const ballColor = name ? nameToColor(name) : "#00ff88";

  const ball = {
    x: width / 2,
    y: height / 2,
    radius: Math.min(width, height) * 0.06,
    dx: 3,
    dy: 2,
    color: ballColor,
  };

  const stream = canvas.captureStream(15);
  const track = stream.getVideoTracks()[0];

  let animationId: number;

  function drawFrame() {
    if (!ctx) return;
    ctx.fillStyle = "#000000";
    ctx.fillRect(0, 0, width, height);

    ball.x += ball.dx;
    ball.y += ball.dy;
    if (ball.x + ball.radius >= width || ball.x - ball.radius <= 0) ball.dx = -ball.dx;
    if (ball.y + ball.radius >= height || ball.y - ball.radius <= 0) ball.dy = -ball.dy;
    ball.x = Math.max(ball.radius, Math.min(width - ball.radius, ball.x));
    ball.y = Math.max(ball.radius, Math.min(height - ball.radius, ball.y));

    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
    ctx.fillStyle = ball.color;
    ctx.fill();

    ctx.fillStyle = "#ffffff";
    ctx.font = "12px Arial";
    ctx.fillText(`Frame: ${Date.now() % 10000}`, 10, 20);
  }

  function animate() {
    drawFrame();
    animationId = requestAnimationFrame(animate);
  }
  animate();

  // Store cleanup function on track
  (track as any).stopAnimation = () => {
    if (animationId) cancelAnimationFrame(animationId);
  };

  track.enabled = true;
  return track;
};

const createSilentAudioTrack = (): MediaStreamTrack => {
  const audioContext = new AudioContext();
  const oscillator = audioContext.createOscillator();
  const gainNode = audioContext.createGain();
  const destination = audioContext.createMediaStreamDestination();
  gainNode.gain.value = 0;
  oscillator.connect(gainNode);
  gainNode.connect(destination);
  oscillator.start();
  const track = destination.stream.getAudioTracks()[0];

  // Store cleanup function
  (track as any).stopOscillator = () => {
    oscillator.stop();
    audioContext.close();
  };

  track.enabled = true;
  return track;
};

/* ---------- Peer Types ---------- */

interface Peer {
  session_id: string;
  peer_name: string;
  attributes: Record<string, any>;
  muted: boolean;
  video_on: boolean;
  local: boolean;
  dead: boolean;
  connection?: RTCPeerConnection;
  connectionState?: string;
  isNegotiating?: boolean;
}
export type { Peer };

interface AddPeerConfig {
  peer_id: string;
  peer_name: string;
  has_media?: boolean; // Whether this peer provides audio/video streams
  should_create_offer?: boolean;
}

interface SessionDescriptionData {
  peer_id: string;
  peer_name: string;
  session_description: RTCSessionDescriptionInit;
}

interface IceCandidateData {
  peer_id: string;
  peer_name: string;
  candidate: RTCIceCandidateInit;
}

interface RemovePeerData {
  peer_name: string;
  peer_id: string;
}

/* ---------- Video Element with Fade-in ---------- */

interface VideoProps extends React.VideoHTMLAttributes<HTMLVideoElement> {
  srcObject: MediaStream;
  local?: boolean;
}

const Video: React.FC<VideoProps> = ({ srcObject, local, ...props }) => {
  const refVideo = useRef<HTMLVideoElement>(null);
  const clickHandlerRef = useRef<(() => void) | null>(null);
  const hasUserInteractedRef = useRef<boolean>(false);

  useEffect(() => {
    if (!refVideo.current || !srcObject) return;

    const ref = refVideo.current;
    console.log("media-agent - <Video/> - Setting video srcObject:", srcObject);

    // Remove any existing click handler
    if (clickHandlerRef.current) {
      ref.removeEventListener("click", clickHandlerRef.current);
      clickHandlerRef.current = null;
      ref.style.cursor = "default";
    }

    // Check if srcObject actually changed
    const oldSrcObject = ref.srcObject;
    ref.srcObject = srcObject;

    // Always mute initially to satisfy autoplay policy
    ref.muted = true;

    const playVideo = async () => {
      try {
        // Force restart playback even if it was already playing
        if (!ref.paused) {
          ref.pause();
          await new Promise((resolve) => setTimeout(resolve, 50));
        }

        await ref.play();
        ref.classList.add("fade-in");
        console.log(`media-agent - <Video/> - Video started playing for ${local ? "local" : "remote"} video`);

        // For remote videos, only unmute if user has already interacted
        if (!local && hasUserInteractedRef.current && !props.muted) {
          try {
            ref.muted = false;
          } catch (e) {
            console.log("media-agent - <Video/> - Unmute requires user interaction");
          }
        }

        // If not local and not yet interacted, add click handler for unmuting
        if (!local && !hasUserInteractedRef.current) {
          const handleClick = async () => {
            hasUserInteractedRef.current = true;
            try {
              ref.muted = false;
              ref.removeEventListener("click", handleClick);
              clickHandlerRef.current = null;
              ref.style.cursor = "default";
              console.log(`media-agent - <Video/> - Unmuted after user interaction`);
            } catch (err) {
              console.error(`media-agent - <Video/> - Failed to unmute:`, err);
            }
          };

          clickHandlerRef.current = handleClick;
          ref.addEventListener("click", handleClick);
          ref.style.cursor = "pointer";

          // Show visual hint that click is needed for audio
          console.log(`media-agent - <Video/> - Click video to enable audio`);
        }
      } catch (error) {
        console.error(`media-agent - <Video/> - Error playing video for ${local ? "local" : "remote"} video`, error);

        // If autoplay fails completely, add handler to start playback
        const handleClick = async () => {
          hasUserInteractedRef.current = true;
          try {
            await ref.play();
            ref.muted = false;
            ref.removeEventListener("click", handleClick);
            clickHandlerRef.current = null;
            ref.style.cursor = "default";
            console.log(`media-agent - <Video/> - Video started after user click`);
          } catch (err) {
            console.error(`media-agent - <Video/> - Failed to play after click:`, err);
          }
        };

        clickHandlerRef.current = handleClick;
        ref.addEventListener("click", handleClick);
        ref.style.cursor = "pointer";
      }
    };

    // Always try to play when srcObject changes
    const timeoutId = setTimeout(playVideo, 100);

    return () => {
      clearTimeout(timeoutId);
      if (ref && oldSrcObject !== srcObject) {
        ref.pause();
        if (clickHandlerRef.current) {
          ref.removeEventListener("click", clickHandlerRef.current);
          clickHandlerRef.current = null;
        }
        ref.classList.remove("fade-in");
        ref.style.cursor = "default";
      }
    };
  }, [srcObject, local, props.muted]);

  // Monitor document interaction separately
  useEffect(() => {
    const handleInteraction = () => {
      hasUserInteractedRef.current = true;
      // Try to unmute any playing remote videos
      if (refVideo.current && !local && !refVideo.current.paused && refVideo.current.muted && !props.muted) {
        try {
          refVideo.current.muted = false;
          console.log("media-agent - <Video/> - Unmuted after document interaction");
        } catch (e) {
          // Still might fail if interaction wasn't sufficient
        }
      }
    };

    document.addEventListener("click", handleInteraction, { once: true });
    document.addEventListener("keydown", handleInteraction, { once: true });

    return () => {
      document.removeEventListener("click", handleInteraction);
      document.removeEventListener("keydown", handleInteraction);
    };
  }, [local, props.muted]);

  return <video ref={refVideo} autoPlay playsInline muted {...props} />;
};

/* ---------- MediaAgent (signaling + peer setup) ---------- */

type MediaAgentProps = {
  session: Session;
  peers: Record<string, Peer>;
  setPeers: React.Dispatch<React.SetStateAction<Record<string, Peer>>>;
};

type JoinStatus = { status: "Not joined" | "Joining" | "Joined" | "Error"; message?: string };

const MediaAgent = (props: MediaAgentProps) => {
  const { peers, setPeers, session } = props;
  const [joinStatus, setJoinStatus] = useState<JoinStatus>({ status: "Not joined" });
  const [media, setMedia] = useState<MediaStream | null>(null);
  const [pendingPeers, setPendingPeers] = useState<AddPeerConfig[]>([]);

  // Extract has_media value to avoid unnecessary effect re-runs when session object changes
  const localUserHasMedia = session?.has_media !== false; // Default to true for backward compatibility

  // Refs for singleton resources
  const mediaStreamRef = useRef<MediaStream | null>(null);
  const connectionsRef = useRef<Map<string, RTCPeerConnection>>(new Map());
  const mountedRef = useRef<boolean>(true);
  // Keep track of last processed message to avoid duplicates
  const lastProcessedMessageRef = useRef<string | null>(null);
  const isNegotiatingRef = useRef<Map<string, boolean>>(new Map());
  const makingOfferRef = useRef<Map<string, boolean>>(new Map());
  const initiatedOfferRef = useRef<Set<string>>(new Set());
  const pendingIceCandidatesRef = useRef<Map<string, RTCIceCandidateInit[]>>(new Map());

  // Update peer states when connection state changes
  const updatePeerConnectionState = useCallback(
    (peerId: string, connectionState: string, isNegotiating: boolean = false) => {
      setPeers((prevPeers) => {
        const updatedPeers = { ...prevPeers };
        if (updatedPeers[peerId]) {
          updatedPeers[peerId] = {
            ...updatedPeers[peerId],
            connectionState,
            isNegotiating,
          };
        }
        return updatedPeers;
      });
    },
    [setPeers]
  );

  // Use the global websocket provided by RoomView to avoid duplicate sockets
  const { sendJsonMessage, lastJsonMessage, readyState } = useContext(GlobalContext);

  useEffect(() => {
    for (let peer in peers) {
      console.log(`media-agent - useEffect(peers): Peer ${peers[peer].peer_name}:`, peers[peer]);
    }
  }, [peers]);

  const addPeer = useCallback(
    async (config: AddPeerConfig) => {
      console.log(`media-agent - addPeer:${config.peer_name} - Callback triggered`, config);
      // Check if connection already exists
      const existingConnection = connectionsRef.current.get(config.peer_id);
      if (existingConnection) {
        const state = existingConnection.connectionState;
        if (state === "new" || state === "connected" || state === "connecting") {
          console.log(
            `media-agent - addPeer:${config.peer_name} Connection already exists for ${config.peer_name} in state: ${state}`
          );
          return;
        } else {
          // Clean up failed/closed connection
          console.log(
            `media-agent - addPeer:${config.peer_name} Cleaning up stale connection for ${config.peer_name} in state: ${state}`
          );
          existingConnection.close();
          connectionsRef.current.delete(config.peer_id);
        }
      }

      // Queue peer if we need local media but don't have it yet
      // Only queue if we're expected to provide media (local user has media)
      const localUserHasMedia = session?.has_media !== false; // Default to true for backward compatibility

      // Only need to wait for media if we (local user) are supposed to provide it
      if (!media && localUserHasMedia) {
        console.log(`media-agent - addPeer:${config.peer_name} - No local media yet, queuing peer`);
        setPendingPeers((prev) => {
          // Avoid duplicate queuing
          if (!prev.some((p) => p.peer_id === config.peer_id)) {
            return [...prev, config];
          }
          return prev;
        });
        return;
      }

      const peer_id = config.peer_id;

      // Check if peer already exists and is alive
      if (peer_id in peers && !peers[peer_id].dead) {
        console.log(`media-agent - addPeer:${config.peer_name} - ${config.peer_name} already in peers and alive`);
        return;
      }

      // Create or revive peer
      const updatedPeers = { ...peers };
      const peer: Peer = {
        session_id: peer_id,
        peer_name: config.peer_name,
        attributes: {},
        muted: updatedPeers[peer_id]?.muted || false,
        video_on: updatedPeers[peer_id]?.video_on !== false,
        local: false,
        dead: false,
      };

      updatedPeers[peer_id] = peer;
      console.log(
        `media-agent - addPeer:${peer.peer_name} - ${peer_id in peers ? "reviving" : "starting new"} peer ${
          peer.peer_name
        }`
      );

      // Create RTCPeerConnection
      const connection = new RTCPeerConnection({
        iceTransportPolicy: "all", // Allow both direct and relay connections
        iceServers: [
          { urls: "stun:ketrenos.com:3478" },
          {
            urls: "turns:ketrenos.com:5349",
            username: "ketra",
            credential: "ketran",
          },
          // DO NOT add Google's public STUN server as fallback; if ketrenos.com STUN/TURN fails,
          // this is an infrastructure failure that must be resolved (not silently bypassed).
          // { urls: "stun:stun.l.google.com:19302" },
        ],
      });

      peer.connection = connection;
      connectionsRef.current.set(peer_id, connection);

      if (config.should_create_offer) {
        initiatedOfferRef.current.add(peer_id);
        // Pre-set the makingOffer flag to prevent negotiationneeded from firing
        makingOfferRef.current.set(peer_id, true);
      }

      // Set up event handlers
      connection.addEventListener("negotiationneeded", async (event) => {
        console.log(`media-agent - addPeer:${peer.peer_name} negotiationneeded - ${connection.signalingState}`, event);

        // Check if we're already making an offer (set by should_create_offer)
        const makingOffer = makingOfferRef.current.get(peer_id) || false;
        if (makingOffer) {
          console.log(`media-agent - addPeer:${peer.peer_name} Already making offer, skipping negotiationneeded...`);
          return;
        }

        // Check if we're already negotiating
        const isNegotiating = isNegotiatingRef.current.get(peer_id) || false;
        if (isNegotiating) {
          console.log(`media-agent - addPeer:${peer.peer_name} Already negotiating, skipping...`);
          return;
        }

        // Only proceed if we're in a stable state
        if (connection.signalingState !== "stable") {
          console.log(
            `media-agent - addPeer:${peer.peer_name} Skipping negotiation, signalingState: ${connection.signalingState}`
          );
          return;
        }

        console.log(`media-agent - addPeer:${peer.peer_name} Handling negotiationneeded for ${peer.peer_name}`);

        // Mark as negotiating
        isNegotiatingRef.current.set(peer_id, true);
        updatePeerConnectionState(peer_id, connection.connectionState, true);

        try {
          makingOfferRef.current.set(peer_id, true);
          isNegotiatingRef.current.set(peer_id, true);

          // Set transceiver direction to avoid m-line ordering issues
          const transceivers = connection.getTransceivers();
          transceivers.forEach((transceiver) => {
            if (transceiver.sender.track) {
              transceiver.direction = "sendrecv";
            }
          });

          const local_description = await connection.createOffer();

          // Check if connection is still in the right state
          if (connection.signalingState !== "stable") {
            console.log(`media-agent - addPeer:${peer.peer_name} State changed during offer creation, aborting`);
            return;
          }

          await connection.setLocalDescription(local_description);

          sendJsonMessage({
            type: "relaySessionDescription",
            data: { peer_id: peer_id, session_description: local_description },
          });
          console.log(`media-agent - addPeer:${peer.peer_name} Offer sent`, local_description);
        } catch (error) {
          console.error(`media-agent - addPeer:${peer.peer_name} negotiationneeded handling failed:`, error);
        } finally {
          makingOfferRef.current.set(peer_id, false);
          // Keep isNegotiating true until we get an answer
        }
      });

      connection.addEventListener("connectionstatechange", (event) => {
        console.log(
          `media-agent - addPeer:${peer.peer_name} connectionstatechange - `,
          connection.connectionState,
          event
        );

        // Update peer connection state
        updatePeerConnectionState(peer_id, connection.connectionState);

        if (connection.connectionState === "failed") {
          console.error(`media-agent - addPeer:${peer.peer_name} Connection failed for`, peer.peer_name);

          // Immediate cleanup of failed connection
          connectionsRef.current.delete(peer_id);
          makingOfferRef.current.delete(peer_id);
          isNegotiatingRef.current.delete(peer_id);
          initiatedOfferRef.current.delete(peer_id);

          // Clean up the peer from state
          setPeers((prevPeers) => {
            const updated = { ...prevPeers };
            delete updated[peer_id];
            return updated;
          });

          // Close the connection
          try {
            connection.close();
          } catch (e) {
            console.warn(`media-agent - Error closing failed connection:`, e);
          }
        } else if (connection.connectionState === "disconnected") {
          console.warn(
            `media-agent - addPeer:${peer.peer_name} Connection disconnected for`,
            peer.peer_name,
            "- may recover"
          );

          // Set a timeout for disconnected state recovery
          setTimeout(() => {
            if (connection.connectionState === "disconnected" || connection.connectionState === "failed") {
              console.log(`media-agent - addPeer:${peer.peer_name} Connection did not recover, cleaning up`);
              connectionsRef.current.delete(peer_id);
            }
          }, 10000); // Give 10 seconds for recovery
        } else if (connection.connectionState === "connected") {
          console.log(`media-agent - addPeer:${peer.peer_name} Connection established successfully`);
          // Clear any negotiation flags on successful connection
          isNegotiatingRef.current.set(peer_id, false);
          makingOfferRef.current.set(peer_id, false);
        }
      });

      connection.addEventListener("icecandidateerror", (event: Event) => {
        const evt = event as RTCPeerConnectionIceErrorEvent;
        // Try to extract candidate type from the hostCandidate string if present
        const hostCand = (evt as any).hostCandidate || null;
        const parseType = (candStr: string | null) => {
          if (!candStr) return "unknown";
          const m = /\btyp\s+(host|srflx|relay|prflx)\b/.exec(candStr);
          return m ? m[1] : "unknown";
        };
        const hostType = parseType(hostCand);
        console.error(`media-agent - addPeer:${peer.peer_name} ICE candidate error for ${peer.peer_name}:`, {
          evt,
          hostCandidate: hostCand,
          hostType,
          address: (evt as any).address,
          port: (evt as any).port,
          url: (evt as any).url,
        });
      });

      connection.addEventListener("track", (event) => {
        console.log(
          `media-agent - addPeer:${peer.peer_name} Track event:`,
          event.track.kind,
          "muted:",
          event.track.muted,
          "enabled:",
          event.track.enabled
        );

        // Force unmute check after a delay
        setTimeout(() => {
          console.log(
            `media-agent - addPeer:${peer.peer_name} Track after delay:`,
            event.track.kind,
            "muted:",
            event.track.muted,
            "enabled:",
            event.track.enabled
          );
        }, 1000);
      });

      const _parseCandidateType = (candStr: string | null) => {
        if (!candStr) return "eoc"; // end of candidates
        const m = /\btyp\s+(host|srflx|relay|prflx)\b/.exec(candStr);
        return m ? m[1] : "unknown";
      };

      connection.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
        if (!event.candidate) {
          console.log(`media-agent - addPeer:${peer.peer_name} ICE gathering complete: ${connection.connectionState}`);
          return;
        }

        const raw = event.candidate?.candidate || null;
        const candType = _parseCandidateType(raw);
        console.log(`media-agent - addPeer:${peer.peer_name} onicecandidate - type=${candType}`, event.candidate);

        // Optionally filter host candidates so we prefer TURN relays.
        if (FILTER_HOST_CANDIDATES && candType === "host") {
          console.log(`media-agent - addPeer:${peer.peer_name} onicecandidate - skipping host candidate`);
          return;
        }

        // Send candidate to signaling server
        sendJsonMessage({
          type: "relayICECandidate",
          data: {
            peer_id,
            candidate: event.candidate,
          },
        });
      };

      connection.ontrack = (event: RTCTrackEvent) => {
        console.log(`media-agent - addPeer:${config.peer_name} ontrack event received`, event);

        let stream: MediaStream;
        if (event.streams && event.streams[0]) {
          stream = event.streams[0];
        } else {
          stream = new MediaStream();
          stream.addTrack(event.track);
        }

        console.log(`media-agent - addPeer:${config.peer_name} ontrack - Stream:`, stream);
        console.log(`media-agent - addPeer:${config.peer_name} ontrack - Track:`, event.track);

        // Update peer with stream - use peer_id from closure, not peer object
        setPeers((prevPeers) => {
          const updated = { ...prevPeers };
          if (updated[peer_id]) {
            // Don't check connection reference - just update if peer exists
            updated[peer_id].attributes = {
              ...updated[peer_id].attributes,
              srcObject: stream,
            };
            console.log(
              `media-agent - addPeer:${config.peer_name} ontrack - remote ${peer_id} track assigned, srcObject:`,
              stream
            );
          } else {
            console.error(`media-agent - addPeer:${config.peer_name} ontrack - peer ${peer_id} not found in state`);
          }
          return updated;
        });
      };

      connection.oniceconnectionstatechange = (event) => {
        console.log(
          `media-agent - addPeer:${peer.peer_name} iceconnectionstatechange - `,
          connection.iceConnectionState,
          event
        );

        if (connection.iceConnectionState === "failed") {
          console.error(`media-agent - ICE connection failed for ${peer.peer_name}`);
          // Log ICE candidate gathering stats for debugging
          connection
            .getStats()
            .then((stats) => {
              const candidateStats: any[] = [];
              stats.forEach((report) => {
                if (report.type === "local-candidate" || report.type === "remote-candidate") {
                  candidateStats.push({
                    type: report.type,
                    candidateType: (report as any).candidateType,
                    protocol: (report as any).protocol,
                    address: (report as any).address,
                    port: (report as any).port,
                  });
                }
              });
              console.error(`media-agent - ICE candidates for failed connection to ${peer.peer_name}:`, candidateStats);
            })
            .catch((e) => console.log("Could not get stats:", e));
        } else if (connection.iceConnectionState === "disconnected") {
          console.warn(`media-agent - ICE connection disconnected for ${peer.peer_name}, may recover`);
        } else if (connection.iceConnectionState === "connected") {
          console.log(`media-agent - ICE connection established successfully for ${peer.peer_name}`);
        } else if (connection.iceConnectionState === "checking") {
          console.log(`media-agent - ICE connection checking for ${peer.peer_name}`);
        } else if (connection.iceConnectionState === "completed") {
          console.log(`media-agent - ICE connection completed for ${peer.peer_name}`);
        }
      };

      // Add local tracks to the connection only if we have media and it's valid
      console.log(
        `media-agent - addPeer:${peer.peer_name} Adding local tracks to new peer connection (localHasMedia=${localUserHasMedia})`
      );
      if (media && localUserHasMedia) {
        media.getTracks().forEach((t) => {
          // Check if we should enable/disable the track based on local mute state
          const localPeer = peers[session.id];
          if (localPeer) {
            if (t.kind === "audio") {
              t.enabled = !localPeer.muted;
              console.log(
                `media-agent - addPeer:${peer.peer_name} Audio track ${t.id} enabled: ${t.enabled} (local muted: ${localPeer.muted})`
              );
            } else if (t.kind === "video") {
              t.enabled = localPeer.video_on;
              console.log(
                `media-agent - addPeer:${peer.peer_name} Video track ${t.id} enabled: ${t.enabled} (local video_on: ${localPeer.video_on})`
              );
            }
          }

          console.log(`media-agent - addPeer:${peer.peer_name} Adding track:`, {
            kind: t.kind,
            enabled: t.enabled,
            muted: t.muted,
            readyState: t.readyState,
            label: t.label,
            id: t.id,
          });

          // Respect the local user's muted/video state. Do not force-enable or
          // mark pending consent for bots here. The local user's track.enabled
          // state (set above) will be used when adding tracks to the connection.

          // Add the existing track to the connection and rely on the track.enabled
          // value which was set above based on the local peer's muted/video state.
          connection.addTrack(t, media);
        });
      } else if (!localUserHasMedia) {
        console.log(`media-agent - addPeer:${peer.peer_name} - Local user has no media, skipping track addition`);
      } else {
        console.log(`media-agent - addPeer:${peer.peer_name} - No local media available yet`);
      }

      // Update peers state
      setPeers(updatedPeers);

      // Create offer if needed - with proper locking
      if (config.should_create_offer) {
        console.log(`media-agent - addPeer:${peer.peer_name} Creating RTC offer to ${peer.peer_name}`);

        try {
          isNegotiatingRef.current.set(peer_id, true);

          const local_description = await connection.createOffer();
          console.log(`media-agent - addPeer:${peer.peer_name} Local offer description created`, local_description);

          await connection.setLocalDescription(local_description);

          sendJsonMessage({
            type: "relaySessionDescription",
            data: {
              peer_id,
              session_description: local_description,
            },
          });
          console.log(`media-agent - addPeer:${peer.peer_name} Offer sent to ${peer.peer_name}`);
        } catch (err) {
          console.error(`media-agent - addPeer:${peer.peer_name} Failed to create/send offer:`, err);
          isNegotiatingRef.current.set(peer_id, false);
          updatePeerConnectionState(peer_id, connection.connectionState, false);
        } finally {
          // Clear the makingOffer flag after we're done
          makingOfferRef.current.set(peer_id, false);
        }
      }
    },
    [peers, setPeers, media, sendJsonMessage, updatePeerConnectionState, session?.has_media, session?.id]
  );

  // Process queued peers when media becomes available
  useEffect(() => {
    if (media && pendingPeers.length > 0) {
      console.log(`media-agent - Processing ${pendingPeers.length} queued peers`);
      const peersToAdd = [...pendingPeers];
      setPendingPeers([]);
      peersToAdd.forEach((config) => addPeer(config));
    }
  }, [media, pendingPeers, addPeer]);

  const sessionDescription = useCallback(
    async (props: SessionDescriptionData) => {
      const { peer_id, peer_name, session_description } = props;
      const peer = peers[peer_id];

      console.log(`media-agent - sessionDescription:${peer_name} - Callback triggered.`, props);

      if (!peer || !peer.connection) {
        console.error(`media-agent - sessionDescription:${peer_name} - No peer for ${peer_id}`);
        return;
      }

      const pc = peer.connection;
      const desc = new RTCSessionDescription(session_description);

      // Check for glare condition (both peers creating offers simultaneously)
      const makingOffer = makingOfferRef.current.get(peer_id) || false;
      const offerCollision = desc.type === "offer" && (makingOffer || pc.signalingState !== "stable");

      // Use polite peer pattern - we're impolite if we initiated the connection
      const weInitiated = initiatedOfferRef.current.has(peer_id);
      const ignoreOffer = weInitiated && offerCollision;

      if (ignoreOffer) {
        console.log(`media-agent - sessionDescription:${peer_name} - Ignoring offer due to collision`);
        return;
      }

      console.log(`media-agent - sessionDescription:${peer_name} - `, {
        peer_id,
        type: desc.type,
        signalingState: pc.signalingState,
      });

      try {
        await pc.setRemoteDescription(desc);
        isNegotiatingRef.current.set(peer_id, false); // Negotiation complete
        updatePeerConnectionState(peer_id, pc.connectionState, false);
        console.log(`media-agent - sessionDescription:${peer_name} - Remote description set`);

        // Process any queued ICE candidates
        const pendingCandidates = pendingIceCandidatesRef.current.get(peer_id);
        if (pendingCandidates && pendingCandidates.length > 0) {
          console.log(
            `media-agent - sessionDescription:${peer_name} - Processing ${pendingCandidates.length} queued ICE candidates`
          );

          for (const candidate of pendingCandidates) {
            try {
              if (!candidate.candidate) {
                // End-of-candidates signal
                await pc.addIceCandidate(undefined);
                console.log(`media-agent - sessionDescription:${peer_name} - Queued end-of-candidates added`);
              } else {
                // Coerce and sanitize the incoming candidate before handing to the browser
                let candStr: string | null = candidate.candidate ?? null;
                if (typeof candStr === "string") {
                  candStr = candStr.trim();
                  // Strip leading 'a=' if present (sometimes sent from SDP parsing)
                  if (candStr.startsWith("a=candidate:")) {
                    candStr = candStr.replace(/^a=/, "");
                  }
                  // Ensure the string starts with the expected keyword
                  if (!candStr.startsWith("candidate:")) {
                    candStr = `candidate:${candStr}`;
                  }
                }

                const candidateInit: RTCIceCandidateInit = {
                  candidate: candStr ?? "",
                  sdpMid: candidate.sdpMid ?? undefined,
                  sdpMLineIndex: typeof candidate.sdpMLineIndex === "number" ? candidate.sdpMLineIndex : undefined,
                };

                try {
                  await pc.addIceCandidate(candidateInit);
                  console.log(`media-agent - sessionDescription:${peer_name} - Queued ICE candidate added`);
                } catch (err) {
                  console.error(`media-agent - sessionDescription:${peer_name} - Failed to add queued ICE candidate:`, {
                    candidateInit,
                    rawCandidate: candidate,
                    err,
                  });
                }
              }
            } catch (err) {
              console.error(`media-agent - sessionDescription:${peer_name} - Failed to add queued ICE candidate:`, err);
            }
          }

          // Clear the queue
          pendingIceCandidatesRef.current.delete(peer_id);
        }
      } catch (err) {
        console.error(`media-agent - sessionDescription:${peer_name} - Failed to set remote description:`, err);
        return;
      }

      // Create answer for offers
      if (session_description.type === "offer") {
        console.log(
          `media-agent - sessionDescription:${peer_name} - Creating answer for received offer in state:`,
          pc.signalingState
        );
        try {
          const local_description = await pc.createAnswer();
          await pc.setLocalDescription(local_description);
          sendJsonMessage({
            type: "relaySessionDescription",
            data: { peer_id, session_description: local_description },
          });
          console.log(`media-agent - sessionDescription:${peer_name} - Answer sent`);
        } catch (err) {
          console.error(`media-agent - sessionDescription:${peer_name} - Failed to create/send answer:`, err);
        }
      }
    },
    [peers, sendJsonMessage, updatePeerConnectionState]
  );

  const removePeer = useCallback(
    (props: RemovePeerData) => {
      const { peer_id, peer_name } = props;
      const peer = peers[peer_id];
      if (!peer) {
        console.error(`media-agent - removePeer:${peer_name} - No peer for ${peer_id}`);
        return;
      }

      console.log(`media-agent - removePeer:${peer_name} - Removing peer.`);

      // Close and remove connection
      const connection = connectionsRef.current.get(peer_id);
      if (connection) {
        console.log(`media-agent - removePeer:${peer_name} - Closing connection.`);
        connection.close();
        connectionsRef.current.delete(peer_id);
      }

      // Clean up negotiation state
      isNegotiatingRef.current.delete(peer_id);
      makingOfferRef.current.delete(peer_id);
      initiatedOfferRef.current.delete(peer_id);
      pendingIceCandidatesRef.current.delete(peer_id);

      // Update peers state
      const updatedPeers = { ...peers };
      if (updatedPeers[peer_id]) {
        updatedPeers[peer_id].dead = true;
        updatedPeers[peer_id].connection = undefined;
      }

      setPeers(updatedPeers);
    },
    [peers, setPeers]
  );

  const iceCandidate = useCallback(
    async (props: IceCandidateData) => {
      const { peer_id, peer_name, candidate } = props;
      const peer = peers[peer_id];
      const parse = (candStr: string | null) => {
        if (!candStr) return "eoc";
        const m = /\btyp\s+(host|srflx|relay|prflx)\b/.exec(candStr);
        return m ? m[1] : "unknown";
      };
      console.log(`media-agent - iceCandidate:${peer_name} - `, {
        peer_id,
        candidate,
        peer,
        candidateType: parse(candidate?.candidate || null),
      });

      if (!peer?.connection) {
        console.error(`media-agent - iceCandidate:${peer_name} - No peer connection for ${peer_id}`);
        return;
      }

      // Check if remote description is set
      if (!peer.connection.remoteDescription) {
        console.log(`media-agent - iceCandidate:${peer_name} - Remote description not set yet, queuing ICE candidate`);

        // Queue the candidate
        const pending = pendingIceCandidatesRef.current.get(peer_id) || [];
        pending.push(candidate);
        pendingIceCandidatesRef.current.set(peer_id, pending);
        return;
      }

      // Add the ICE candidate
      if (!candidate.candidate) {
        // End-of-candidates signal
        peer.connection
          .addIceCandidate(undefined)
          .then(() =>
            console.log(`media-agent - iceCandidate::${peer_name} - End-of-candidates added for ${peer.peer_name}`)
          )
          .catch((err) =>
            console.error(`media-agent - iceCandidate::${peer_name} - Failed to add end-of-candidates:`, err)
          );
      } else {
        // Sanitize and coerce incoming candidate
        let candStr: string | null = candidate.candidate ?? null;
        if (typeof candStr === "string") {
          candStr = candStr.trim();
          if (candStr.startsWith("a=candidate:")) {
            candStr = candStr.replace(/^a=/, "");
          }
          if (!candStr.startsWith("candidate:")) {
            candStr = `candidate:${candStr}`;
          }
        }

        const candidateInit: RTCIceCandidateInit = {
          candidate: candStr ?? "",
          sdpMid: candidate.sdpMid ?? undefined,
          sdpMLineIndex: typeof candidate.sdpMLineIndex === "number" ? candidate.sdpMLineIndex : undefined,
        };

        peer.connection
          .addIceCandidate(candidateInit)
          .then(() =>
            console.log(`media-agent - iceCandidate::${peer_name} - ICE candidate added for ${peer.peer_name}`)
          )
          .catch((err) =>
            console.error(`media-agent - iceCandidate::${peer_name} - Failed to add ICE candidate:`, {
              candidateInit,
              rawCandidate: candidate,
              err,
            })
          );
      }
    },
    [peers]
  );

  const handleWebSocketMessage = useCallback(
    (data: any) => {
      switch (data.type) {
        case "join_status":
          setJoinStatus({ status: data.status, message: data.message });
          break;
        case "addPeer":
          addPeer(data.data);
          break;
        case "removePeer":
          removePeer(data.data);
          break;
        case "iceCandidate":
          iceCandidate(data.data);
          break;
        case "sessionDescription":
          sessionDescription(data.data);
          break;
        default:
          break;
      }
    },
    [addPeer, removePeer, iceCandidate, sessionDescription]
  );

  // Effect runs when new message arrives
  useEffect(() => {
    if (!lastJsonMessage || !session) return;

    const msgKey = JSON.stringify(lastJsonMessage);
    if (lastProcessedMessageRef.current === msgKey) {
      // already handled this one
      return;
    }
    lastProcessedMessageRef.current = msgKey;

    handleWebSocketMessage(lastJsonMessage);
  }, [lastJsonMessage, handleWebSocketMessage, session]);

  // Join lobby when media is ready
  useEffect(() => {
    // Only attempt to join once we have local media, an open socket, and a known session name.
    // Joining with a null/empty name can cause the signaling server to treat the peer as anonymous
    // which results in other peers not receiving expected addPeer/track messages.
    if (media && joinStatus.status === "Not joined" && readyState === ReadyState.OPEN && session && session.name) {
      console.log(`media-agent - Initiating media join for ${session.name}`);
      setJoinStatus({ status: "Joining" });
      sendJsonMessage({
        type: "join",
        data: {
          has_media: session.has_media !== false, // Default to true for backward compatibility
        },
      });
    }
  }, [media, joinStatus.status, sendJsonMessage, readyState, session.name, session.has_media]);

  // Update local peer in peers list
  useEffect(() => {
    if (!session || !media) return;

    setPeers((prevPeers) => {
      const updated = { ...prevPeers };

      // Remove old local peer if session ID changed
      Object.keys(updated).forEach((key) => {
        if (updated[key].local && key !== session.id) {
          delete updated[key];
        }
      });

      // Add or update current local peer
      if (!updated[session.id]) {
        // Disable tracks based on initial muted state before assigning to peer
        if (media) {
          media.getAudioTracks().forEach((track) => {
            track.enabled = false; // Start muted
            console.log(`media-agent - Local audio track ${track.id} disabled (initial state)`);
          });
          media.getVideoTracks().forEach((track) => {
            track.enabled = false; // Start with video off
            console.log(`media-agent - Local video track ${track.id} disabled (initial state)`);
          });
        }

        updated[session.id] = {
          peer_name: session.name || "Unknown",
          session_id: session.id,
          local: true,
          muted: true,
          video_on: false,
          attributes: {
            local: true,
            srcObject: media,
          },
          dead: false,
        };
        console.log(`media-agent - Added local peer to peers list with tracks disabled`);
      }

      return updated;
    });
  }, [session, media, setPeers]);

  // Setup local media
  const setup_local_media = useCallback(async (): Promise<MediaStream> => {
    console.log(`media-agent - Requesting access to local audio/video`);
    const attempt = { get_audio: true, get_video: true };
    let media = null;

    // Try to get user media with fallback
    while (attempt.get_audio || attempt.get_video) {
      try {
        const constraints: any = {};
        if (attempt.get_audio) constraints.audio = true;
        if (attempt.get_video) constraints.video = true;

        console.log(`media-agent - Attempting getUserMedia: audio=${attempt.get_audio}, video=${attempt.get_video}`);
        media = await navigator.mediaDevices.getUserMedia(constraints);
        break;
      } catch (error) {
        if (attempt.get_video && attempt.get_audio) {
          console.log(`media-agent - Disabling video, trying audio only`);
          attempt.get_video = false;
        } else if (attempt.get_audio && !attempt.get_video) {
          console.log(`media-agent - Disabling audio, trying video only`);
          attempt.get_video = true;
          attempt.get_audio = false;
        } else {
          console.log(`media-agent - No media available`);
          attempt.get_video = false;
          attempt.get_audio = false;
        }
      }
    }

    // Build tracks array
    const tracks: MediaStreamTrack[] = [];
    let hasRealAudio = false;
    let hasRealVideo = false;

    if (media) {
      const audioTracks = media.getAudioTracks();
      const videoTracks = media.getVideoTracks();

      if (audioTracks.length > 0) {
        const audioTrack = audioTracks[0];
        tracks.push(audioTrack);
        hasRealAudio = true;
        console.log("media-agent - Using real audio:", {
          enabled: audioTrack.enabled,
          muted: audioTrack.muted,
          readyState: audioTrack.readyState,
          label: audioTrack.label,
          id: audioTrack.id,
        });
      }

      if (videoTracks.length > 0) {
        videoTracks[0].applyConstraints({
          width: { min: 160, max: 320 },
          height: { min: 120, max: 240 },
        });
        tracks.push(videoTracks[0]);
        hasRealVideo = true;
        console.log("media-agent - Using real video");
      }
    }

    // Add synthetic tracks if needed
    if (!hasRealAudio) {
      tracks.push(createSilentAudioTrack());
      console.log("media-agent - Using synthetic audio");
    }

    if (!hasRealVideo) {
      tracks.push(createAnimatedVideoTrack({ name: session.name || "" }));
      console.log("media-agent - Using synthetic video");
    }

    const finalMedia = new MediaStream(tracks);
    console.log(`media-agent - Media setup complete:`, {
      totalTracks: finalMedia.getTracks().length,
      audioTracks: finalMedia.getAudioTracks().length,
      videoTracks: finalMedia.getVideoTracks().length,
      hasRealAudio,
      hasRealVideo,
    });
    return finalMedia;
  }, [session.name]);

  // Initialize media once
  useEffect(() => {
    mountedRef.current = true;

    if (mediaStreamRef.current || readyState !== ReadyState.OPEN) return;

    // Capture the connections at effect setup time
    const connectionsToCleanup = connectionsRef.current;

    if (localUserHasMedia) {
      console.log(`media-agent - Setting up local media`);
      setup_local_media().then((mediaStream) => {
        if (!mountedRef.current) {
          // Component unmounted, clean up
          mediaStream.getTracks().forEach((track) => {
            track.stop();
            if ((track as any).stopAnimation) (track as any).stopAnimation();
            if ((track as any).stopOscillator) (track as any).stopOscillator();
          });
          return;
        }

        mediaStreamRef.current = mediaStream;
        setMedia(mediaStream);
      });
    } else {
      console.log(`media-agent - Local user has no media, creating empty stream`);
      // Create an empty media stream for users without media
      const emptyStream = new MediaStream();
      mediaStreamRef.current = emptyStream;
      setMedia(emptyStream);
    }

    return () => {
      mountedRef.current = false;

      // Clean up media stream
      if (mediaStreamRef.current) {
        mediaStreamRef.current.getTracks().forEach((track) => {
          track.stop();
          if ((track as any).stopAnimation) (track as any).stopAnimation();
          if ((track as any).stopOscillator) (track as any).stopOscillator();
        });
        mediaStreamRef.current = null;
      }

      // Clean up connections using the captured ref value
      connectionsToCleanup.forEach((connection) => connection.close());
      connectionsToCleanup.clear();
    };
  }, [readyState, setup_local_media, localUserHasMedia]);

  return null;
};

/* ---------- MediaControl (UI per peer) ---------- */

interface MediaControlProps {
  isSelf: boolean;
  peer: Peer;
  className?: string;
  sendJsonMessage?: (msg: any) => void;
  remoteAudioMuted?: boolean;
  remoteVideoOff?: boolean;
}

const MediaControl: React.FC<MediaControlProps> = ({
  isSelf,
  peer,
  className,
  sendJsonMessage,
  remoteAudioMuted,
  remoteVideoOff,
}) => {
  const [muted, setMuted] = useState<boolean>(peer?.muted || false);
  const [videoOn, setVideoOn] = useState<boolean>(peer?.video_on !== false);
  const [isValid, setIsValid] = useState<boolean>(false);
  const [frame, setFrame] = useState<{
    translate: [number, number];
    width?: number;
    height?: number;
  }>({ translate: [0, 0] });
  const containerRef = useRef<HTMLDivElement>(null);
  const targetRef = useRef<HTMLDivElement>(null);
  const spacerRef = useRef<HTMLDivElement>(null);
  const moveableRef = useRef<any>(null);
  const [isDragging, setIsDragging] = useState<boolean>(false);
  // Get sendJsonMessage from props
  // Reset drag state on pointerup/touchend/mouseup anywhere in the document
  useEffect(() => {
    const resetDrag = () => setIsDragging(false);
    document.addEventListener("pointerup", resetDrag);
    document.addEventListener("touchend", resetDrag);
    document.addEventListener("mouseup", resetDrag);
    return () => {
      document.removeEventListener("pointerup", resetDrag);
      document.removeEventListener("touchend", resetDrag);
      document.removeEventListener("mouseup", resetDrag);
    };
  }, []);

  useEffect(() => {
    console.log(
      `media-agent - MediaControl mounted for peer ${peer?.peer_name}, local=${peer?.local}, hasSrcObject=${!!peer
        ?.attributes?.srcObject}`
    );
    if (!peer) return;
    console.log(`media-agent - MediaControl peer changed for ${peer.peer_name}, updating state`);
    setMuted(peer.muted);
    setVideoOn(peer.video_on);
  }, [peer]);

  // Initialize size to match spacer
  useEffect(() => {
    if (spacerRef.current && targetRef.current && !frame.width) {
      const spacerRect = spacerRef.current.getBoundingClientRect();
      targetRef.current.style.width = `${spacerRect.width}px`;
      targetRef.current.style.height = `${spacerRect.height}px`;
    }
  }, [frame.width]);

  useEffect(() => {
    if (!peer || peer.dead || !peer.attributes?.srcObject) {
      console.log(
        `media-agent - isValid effect:${peer?.peer_name} - No valid media`,
        peer?.dead,
        peer?.attributes?.srcObject
      );
      setIsValid(false);
      return;
    }

    const stream = peer.attributes.srcObject as MediaStream;

    // For remote peers, check if we have a connected peer connection
    // For local peers, just check if we have live tracks
    if (!peer.local && peer.connection) {
      const isConnected =
        peer.connection.connectionState === "connected" || peer.connection.connectionState === "connecting";
      console.log(
        `media-agent - isValid effect:${peer.peer_name} - Remote peer connection state: ${peer.connection.connectionState}`
      );
      setIsValid(isConnected);
    } else {
      const hasLiveTracks = stream.getTracks().some((track) => track.readyState === "live");
      console.log(`media-agent - isValid effect:${peer.peer_name} - Local peer has live tracks: ${hasLiveTracks}`);
      setIsValid(hasLiveTracks);
    }

    // Listen for connection state changes for remote peers
    if (!peer.local && peer.connection) {
      const handleConnectionChange = () => {
        const isConnected =
          peer.connection?.connectionState === "connected" || peer.connection?.connectionState === "connecting";
        console.log(
          `media-agent - isValid effect:${peer.peer_name} - Connection state changed: ${peer.connection?.connectionState}`
        );
        setIsValid(isConnected);
      };

      console.log(`media-agent - isValid effect:${peer.peer_name} - Adding connection state listener`);
      peer.connection.addEventListener("connectionstatechange", handleConnectionChange);

      return () => {
        peer.connection?.removeEventListener("connectionstatechange", handleConnectionChange);
      };
    }

    // Listen for track state changes
    const handleTrackStateChange = () => {
      if (!peer.local && peer.connection) {
        const isConnected =
          peer.connection.connectionState === "connected" || peer.connection.connectionState === "connecting";
        console.log(
          `media-agent - isValid effect:${peer.peer_name} - Track state changed, connection state: ${peer.connection.connectionState}`
        );
        setIsValid(isConnected);
      } else {
        const currentlyLive = stream.getTracks().some((track) => track.readyState === "live");
        console.log(
          `media-agent - isValid effect:${peer.peer_name} - Track state changed, live tracks: ${currentlyLive}`
        );
        setIsValid(currentlyLive);
      }
    };

    stream.getTracks().forEach((track) => {
      track.addEventListener("unmute", handleTrackStateChange);
      track.addEventListener("ended", handleTrackStateChange);
    });

    return () => {
      stream.getTracks().forEach((track) => {
        track.removeEventListener("unmute", handleTrackStateChange);
        track.removeEventListener("ended", handleTrackStateChange);
      });
    };
  }, [peer]);

  useEffect(() => {
    if (!peer || peer.dead || !peer.attributes?.srcObject) {
      console.log(
        `media-agent - Audio track control: skipping for ${peer?.peer_name} (dead=${peer?.dead}, hasSrcObject=${!!peer
          ?.attributes?.srcObject})`
      );
      return;
    }

    console.log(`media-agent - Audio track control useEffect running for ${peer.peer_name}, muted=${muted}`);
    const stream = peer.attributes.srcObject as MediaStream;
    stream.getAudioTracks().forEach((t) => {
      const shouldEnable = !muted;
      console.log(`media-agent - Setting audio track ${t.id} enabled: ${shouldEnable} (was ${t.enabled})`);
      t.enabled = shouldEnable;
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [muted, peer?.attributes?.srcObject, peer?.dead, peer]);

  useEffect(() => {
    if (!peer || peer.dead || !peer.attributes?.srcObject) return;

    console.log(`media-agent - Video track control useEffect running for ${peer.peer_name}, videoOn=${videoOn}`);
    const stream = peer.attributes.srcObject as MediaStream;
    stream.getVideoTracks().forEach((t) => {
      const shouldEnable = videoOn;
      console.log(`media-agent - Setting video track ${t.id} enabled: ${shouldEnable} (was ${t.enabled})`);
      t.enabled = shouldEnable;
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [videoOn, peer?.attributes?.srcObject, peer?.dead, peer]);

  // Debug target element
  useEffect(() => {
    console.log("Target ref current:", targetRef.current, "for peer:", peer?.session_id);
    if (targetRef.current) {
      console.log("Target element rect:", targetRef.current.getBoundingClientRect());
      console.log("Target element computed style:", {
        position: getComputedStyle(targetRef.current).position,
        left: getComputedStyle(targetRef.current).left,
        top: getComputedStyle(targetRef.current).top,
        transform: getComputedStyle(targetRef.current).transform,
        width: getComputedStyle(targetRef.current).width,
        height: getComputedStyle(targetRef.current).height,
      });
    }
  }, [peer?.session_id]);

  const toggleMute = useCallback(
    (e: React.MouseEvent | React.TouchEvent) => {
      e.stopPropagation();
      if (!peer) return;
      const newMutedState = !muted;
      setMuted(newMutedState);
      peer.muted = newMutedState;
      console.log(`media-agent - toggleMute: ${peer.peer_name} muted=${newMutedState}`);
      // Only broadcast if this is the local user (isSelf)
      if (isSelf && sendJsonMessage) {
        sendJsonMessage({
          type: "peer_state_update",
          data: {
            peer_id: peer.session_id,
            muted: newMutedState,
            video_on: videoOn,
          },
        });
      }
    },
    [peer, muted, videoOn, sendJsonMessage, isSelf]
  );

  const toggleVideo = useCallback(
    (e: React.MouseEvent | React.TouchEvent) => {
      e.stopPropagation();
      if (!peer) return;
      const newVideoState = !videoOn;
      setVideoOn(newVideoState);
      peer.video_on = newVideoState;
      console.log(`media-agent - toggleVideo: ${peer.peer_name} video_on=${newVideoState}`);
      // Only broadcast if this is the local user (isSelf)
      if (isSelf && sendJsonMessage) {
        sendJsonMessage({
          type: "peer_state_update",
          data: {
            peer_id: peer.session_id,
            muted: muted,
            video_on: newVideoState,
          },
        });
      }
    },
    [peer, videoOn, muted, sendJsonMessage, isSelf]
  );

  // Snap-back functionality
  const checkSnapBack = (x: number, y: number) => {
    if (!spacerRef.current) return false;

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const spacerRect = spacerRef.current.getBoundingClientRect();
    const threshold = 50; // pixels from original position to trigger snap

    // Check if close to original position
    const closeToOrigin = Math.abs(x) < threshold && Math.abs(y) < threshold;

    return closeToOrigin;
  };

  if (!peer) return null;

  const colorAudio = isValid ? "primary" : "disabled";
  const colorVideo = isValid ? "primary" : "disabled";

  return (
    <Box
      sx={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        minWidth: "200px",
        minHeight: "100px",
      }}
    >
      <div
        ref={containerRef}
        className="MediaControlContainer"
        style={{
          position: "relative",
          width: "max-content",
          height: "max-content",
        }}
      >
        {/* Drop target spacer */}
        <div
          ref={spacerRef}
          className={`MediaControlSpacer ${className}`}
          style={{
            opacity: isDragging ? 1 : 0.3,
            transition: "opacity 0.2s",
          }}
        >
          {isDragging && (
            <div
              style={{
                position: "absolute",
                top: "50%",
                left: "50%",
                transform: "translate(-50%, -50%)",
                fontSize: "0.7em",
                color: "#888",
                pointerEvents: "none",
              }}
            >
              Drop here
            </div>
          )}
        </div>

        {/* Moveable element - positioned absolute relative to container */}
        <div
          ref={targetRef}
          className={`MediaControl ${className}`}
          data-peer={peer.session_id}
          style={{
            position: "absolute",
            top: "0px",
            left: "0px",
            width: frame.width ? `${frame.width}px` : undefined,
            height: frame.height ? `${frame.height}px` : undefined,
            transform: `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`,
          }}
        >
          <Box className="Controls">
            {isSelf ? (
              <IconButton onClick={toggleMute}>
                {muted ? <MicOff color={colorAudio} /> : <Mic color={colorAudio} />}
              </IconButton>
            ) : (
              <Box sx={{ display: "flex", flexDirection: "row", gap: 0, alignItems: "center", p: 0, m: 0 }}>
                <IconButton onClick={toggleMute}>
                  {muted ? <VolumeOff color={colorAudio} /> : <VolumeUp color={colorAudio} />}
                </IconButton>
                {remoteAudioMuted && <MicOff color="warning" />}
              </Box>
            )}
            <Box
              sx={{
                display: "flex",
                flexDirection: "row",
                gap: 0,
                alignItems: "center",
                p: 0,
                m: 0,
              }}
            >
              <IconButton onClick={toggleVideo}>
                {videoOn ? <Videocam color={colorVideo} /> : <VideocamOff color={colorVideo} />}
              </IconButton>
              {!isSelf && remoteVideoOff && <VideocamOff color="warning" />}
            </Box>
          </Box>
          {isValid ? (
            peer.attributes?.srcObject && (
              <>
                <Video
                  key={`video-${peer.session_id}-${peer.attributes.srcObject.id}`}
                  className="Video"
                  data-id={peer.peer_name}
                  autoPlay
                  srcObject={peer.attributes.srcObject}
                  local={peer.local}
                  muted={peer.local || muted}
                />
                <WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
                <WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
              </>
            )
          ) : (
            <>
              <div className="Video">Waiting for media…</div>
              <WebRTCStatus isNegotiating={peer.isNegotiating || false} connectionState={peer.connectionState} />
            </>
          )}
        </div>

        <Moveable
          ref={moveableRef}
          flushSync={flushSync}
          container={containerRef.current}
          pinchable={true}
          draggable={true}
          target={targetRef.current}
          resizable={true}
          keepRatio={true}
          hideDefaultLines={false}
          snappable={true}
          snapThreshold={5}
          origin={false}
          edge
          onDragStart={(e: any) => {
            const controls = containerRef.current?.querySelector(".Controls");
            const target = e.inputEvent?.target as HTMLElement;
            if (controls && target && (target.closest("button") || target.closest(".MuiIconButton-root"))) {
              if (typeof e.stopDrag === "function") {
                e.stopDrag();
              }
              return;
            }
            setIsDragging(true);
          }}
          onDrag={(e) => {
            if (targetRef.current) {
              targetRef.current.style.transform = e.transform;
            }
            const matrix = new DOMMatrix(e.transform);
            const shouldSnap = checkSnapBack(matrix.m41, matrix.m42);
            if (shouldSnap && spacerRef.current) {
              spacerRef.current.style.borderColor = "#0088ff";
            } else if (spacerRef.current) {
              spacerRef.current.style.borderColor = "#666";
            }
          }}
          onDragEnd={(e) => {
            setIsDragging(false);
            if (targetRef.current) {
              const computedStyle = getComputedStyle(targetRef.current);
              const transform = computedStyle.transform;
              if (transform && transform !== "none") {
                const matrix = new DOMMatrix(transform);
                const shouldSnap = checkSnapBack(matrix.m41, matrix.m42);
                if (shouldSnap) {
                  targetRef.current.style.transform = "translate(0px, 0px)";
                  setFrame({ translate: [0, 0], width: frame.width, height: frame.height });
                  if (spacerRef.current) {
                    const spacerRect = spacerRef.current.getBoundingClientRect();
                    targetRef.current.style.width = `${spacerRect.width}px`;
                    targetRef.current.style.height = `${spacerRect.height}px`;
                    setFrame({ translate: [0, 0] });
                  }
                } else {
                  setFrame({
                    translate: [matrix.m41, matrix.m42],
                    width: frame.width,
                    height: frame.height,
                  });
                }
              } else {
                setFrame({ translate: [0, 0], width: frame.width, height: frame.height });
              }
            }
            if (spacerRef.current) {
              spacerRef.current.style.borderColor = "#666";
            }
          }}
          onResizeStart={(e) => {
            e.setOrigin(["%", "%"]);
            setIsDragging(true);
          }}
          onResize={(e) => {
            e.target.style.width = `${e.width}px`;
            e.target.style.height = `${e.height}px`;
            setFrame({
              ...frame,
              width: e.width,
              height: e.height,
            });
          }}
          onResizeEnd={() => {
            setIsDragging(false);
          }}
        />
      </div>
    </Box>
  );
};

export { MediaControl, MediaAgent };
