import { useCallback, useEffect, useRef, useState } from 'react';
import { RoomEvent } from 'livekit-client';
import { useCall } from './useCall';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';

/** Data topic for cursor position messages */
const CURSOR_TOPIC = 'remote-cursor';

/** How often to send cursor updates (ms) */
const CURSOR_SEND_INTERVAL = 50;

/** How long before a cursor is considered stale and hidden (ms) */
const CURSOR_STALE_TIMEOUT = 3000;

/** Cursor position data sent over the data channel */
export interface CursorData {
  /** Message type identifier */
  type: 'cursor';
  /** Normalized X position (0–1) */
  x: number;
  /** Normalized Y position (0–1) */
  y: number;
  /** Whether the cursor is visible (false = user moved mouse away) */
  visible: boolean;
  /** Screen share participant ID (Matrix user ID) this cursor relates to */
  shareId: string;
}

/** Remote cursor state for rendering */
export interface RemoteCursorState {
  /** LiveKit participant identity */
  participantId: string;
  /** Normalized X position (0–1) */
  x: number;
  /** Normalized Y position (0–1) */
  y: number;
  /** Whether the cursor is currently visible */
  visible: boolean;
  /** Timestamp of last update */
  lastUpdate: number;
}

/**
 * Hook to send local cursor position over the LiveKit data channel
 * Tracks mouse movement over a screenshare container and broadcasts position
 * @param shareId - The screen share ID to associate cursor data with
 * @returns Object with onMouseMove/onMouseLeave handlers to attach to the container
 */
export function useSendCursor(shareId: string) {
  const { callService } = useCall();
  const [showRemoteCursor] = useSetting(settingsAtom, 'showRemoteCursor');
  const lastSendRef = useRef(0);
  const encoder = useRef(new TextEncoder());

  const sendCursorData = useCallback(
    (data: Omit<CursorData, 'type'>) => {
      const room = callService?.getLiveKitRoom();
      if (!room?.localParticipant) return;

      const now = Date.now();
      if (!data.visible || now - lastSendRef.current >= CURSOR_SEND_INTERVAL) {
        lastSendRef.current = now;
        const message: CursorData = { ...data, type: 'cursor' };
        const payload = encoder.current.encode(JSON.stringify(message));
        room.localParticipant
          .publishData(payload, { reliable: false, topic: CURSOR_TOPIC })
          .catch(() => {
            // Data publish can fail silently
          });
      }
    },
    [callService]
  );

  /**
   * Handle mouse movement over a screenshare container
   * Calculates normalized position and sends to remote participants
   */
  const handleMouseMove = useCallback(
    (e: React.MouseEvent<HTMLDivElement>) => {
      if (!showRemoteCursor) return;

      const rect = e.currentTarget.getBoundingClientRect();
      const x = (e.clientX - rect.left) / rect.width;
      const y = (e.clientY - rect.top) / rect.height;

      sendCursorData({ x, y, visible: true, shareId });
    },
    [showRemoteCursor, sendCursorData, shareId]
  );

  /**
   * Handle mouse leaving the screenshare container
   * Sends a hidden cursor update so remote stops showing cursor
   */
  const handleMouseLeave = useCallback(() => {
    if (!showRemoteCursor) return;
    sendCursorData({ x: 0, y: 0, visible: false, shareId });
  }, [showRemoteCursor, sendCursorData, shareId]);

  return { handleMouseMove, handleMouseLeave };
}

/**
 * Hook to receive remote cursor positions from the LiveKit data channel
 * Listens for cursor data from other participants and provides state for rendering
 * @param shareId - The screen share ID to filter cursor data for
 * @returns Map of participant IDs to their cursor states
 */
export function useReceiveCursors(shareId: string): Map<string, RemoteCursorState> {
  const { callService } = useCall();
  const [showRemoteCursor] = useSetting(settingsAtom, 'showRemoteCursor');
  const [cursors, setCursors] = useState<Map<string, RemoteCursorState>>(new Map());
  const decoder = useRef(new TextDecoder());

  useEffect(() => {
    if (!showRemoteCursor) {
      setCursors(new Map());
      return undefined;
    }

    const room = callService?.getLiveKitRoom();
    if (!room) return undefined;

    const handleDataReceived = (
      payload: Uint8Array,
      participant?: { identity: string },
      _kind?: unknown,
      topic?: string
    ) => {
      if (!participant) return;
      // Only process cursor messages by topic
      if (topic !== undefined && topic !== CURSOR_TOPIC) return;

      try {
        const text = decoder.current.decode(payload);
        const data = JSON.parse(text) as CursorData;

        // Validate this is actually a cursor message
        if (data.type !== 'cursor') return;
        if (data.shareId !== shareId) return;

        setCursors((prev) => {
          const next = new Map(prev);
          next.set(participant.identity, {
            participantId: participant.identity,
            x: data.x,
            y: data.y,
            visible: data.visible,
            lastUpdate: Date.now(),
          });
          return next;
        });
      } catch {
        // Ignore malformed data
      }
    };

    room.on(RoomEvent.DataReceived, handleDataReceived);

    // Periodically clean up stale cursors
    const cleanupInterval = setInterval(() => {
      setCursors((prev) => {
        const now = Date.now();
        let changed = false;
        const next = new Map(prev);

        next.forEach((cursor, key) => {
          if (now - cursor.lastUpdate > CURSOR_STALE_TIMEOUT) {
            next.delete(key);
            changed = true;
          }
        });

        return changed ? next : prev;
      });
    }, 1000);

    return () => {
      room.off(RoomEvent.DataReceived, handleDataReceived);
      clearInterval(cleanupInterval);
    };
  }, [callService, showRemoteCursor, shareId]);

  return cursors;
}
