// frontend/src/components/FloatingVideo.js
import React, { useCallback, useEffect, useRef, useState } from 'react';
import Draggable from 'react-draggable';
import useVideoStore from '../store/useVideoStore';
import useAuthStore from '../store/auth';
import mpegts from 'mpegts.js';
import Hls from 'hls.js';
import { CloseButton, Flex, Loader, Text, Box } from '@mantine/core';
import {
  applyConstraints,
  calculateNewDimensions,
  getClientCoordinates,
  getLivePlayerErrorMessage,
  getVODPlayerErrorMessage,
  getPlayerPrefs,
  savePlayerPrefs,
} from '../utils/components/FloatingVideoUtils.js';

const ResizeHandles = ({ startResize }) => {
  const HANDLE_SIZE = 18;
  const HANDLE_OFFSET = 0;

  const resizeHandleBaseStyle = {
    position: 'absolute',
    width: HANDLE_SIZE,
    height: HANDLE_SIZE,
    backgroundColor: 'transparent',
    borderRadius: 6,
    zIndex: 8,
    touchAction: 'none',
  };

  const resizeHandles = [
    {
      id: 'bottom-right',
      cursor: 'nwse-resize',
      xDir: 1,
      yDir: 1,
      isLeft: false,
      isTop: false,
      style: {
        bottom: HANDLE_OFFSET,
        right: HANDLE_OFFSET,
        borderBottom: '2px solid rgba(255, 255, 255, 0.9)',
        borderRight: '2px solid rgba(255, 255, 255, 0.9)',
        borderRadius: '0 0 6px 0',
      },
    },
    {
      id: 'bottom-left',
      cursor: 'nesw-resize',
      xDir: -1,
      yDir: 1,
      isLeft: true,
      isTop: false,
      style: {
        bottom: HANDLE_OFFSET,
        left: HANDLE_OFFSET,
        borderBottom: '2px solid rgba(255, 255, 255, 0.9)',
        borderLeft: '2px solid rgba(255, 255, 255, 0.9)',
        borderRadius: '0 0 0 6px',
      },
    },
    {
      id: 'top-right',
      cursor: 'nesw-resize',
      xDir: 1,
      yDir: -1,
      isLeft: false,
      isTop: true,
      style: {
        top: HANDLE_OFFSET,
        right: HANDLE_OFFSET,
        borderTop: '2px solid rgba(255, 255, 255, 0.9)',
        borderRight: '2px solid rgba(255, 255, 255, 0.9)',
        borderRadius: '0 6px 0 0',
      },
    },
    {
      id: 'top-left',
      cursor: 'nwse-resize',
      xDir: -1,
      yDir: -1,
      isLeft: true,
      isTop: true,
      style: {
        top: HANDLE_OFFSET,
        left: HANDLE_OFFSET,
        borderTop: '2px solid rgba(255, 255, 255, 0.9)',
        borderLeft: '2px solid rgba(255, 255, 255, 0.9)',
        borderRadius: '6px 0 0 0',
      },
    },
  ];

  return (
    <>
      {/* Resize handles */}
      {resizeHandles.map((handle) => (
        <Box
          key={handle.id}
          className="floating-video-no-drag"
          onMouseDown={(event) => startResize(event, handle)}
          onTouchStart={(event) => startResize(event, handle)}
          style={{
            ...resizeHandleBaseStyle,
            ...handle.style,
            cursor: handle.cursor,
          }}
        />
      ))}
    </>
  );
};

export default function FloatingVideo() {
  const isVisible = useVideoStore((s) => s.isVisible);
  const streamUrl = useVideoStore((s) => s.streamUrl);
  const contentType = useVideoStore((s) => s.contentType);
  const metadata = useVideoStore((s) => s.metadata);
  const hideVideo = useVideoStore((s) => s.hideVideo);

  const videoRef = useRef(null);
  const playerRef = useRef(null);
  const videoContainerRef = useRef(null);
  const resizeStateRef = useRef(null);
  const overlayTimeoutRef = useRef(null);
  const aspectRatioRef = useRef(320 / 180);
  const dragPositionRef = useRef(null);
  const dragOffsetRef = useRef({ x: 0, y: 0 });
  const initialPositionRef = useRef(null);
  // Ref kept in sync with videoSize state for use inside event handlers
  // where closures over state would be stale.
  const videoSizeRef = useRef(null);

  const [isLoading, setIsLoading] = useState(false);
  const [loadError, setLoadError] = useState(null);
  const [showOverlay, setShowOverlay] = useState(false);
  const [showControls, setShowControls] = useState(false);
  const [videoSize, setVideoSize] = useState(() => {
    const prefs = getPlayerPrefs();
    const saved = prefs.size;
    if (saved?.width >= 220 && saved?.height >= 124) {
      if (typeof window !== 'undefined') {
        // Cap to viewport minus a margin so the header is always reachable on
        // first render even if the saved size was set on a larger display.
        const maxW = window.innerWidth - 48; // VISIBLE_MARGIN
        const maxH = window.innerHeight - 83; // HEADER_HEIGHT(38) + VISIBLE_MARGIN(48) - 1 extra row
        if (saved.width > maxW || saved.height > maxH) {
          const scale = Math.min(maxW / saved.width, maxH / saved.height);
          return {
            width: Math.max(220, Math.round(saved.width * scale)),
            height: Math.max(124, Math.round(saved.height * scale)),
          };
        }
      }
      return saved;
    }
    return { width: 320, height: 180 };
  });
  const [isResizing, setIsResizing] = useState(false);
  const [dragPosition, setDragPosition] = useState(null);

  const MIN_WIDTH = 220;
  const MIN_HEIGHT = 124;
  const VISIBLE_MARGIN = 48; // keep part of the window visible when dragging
  const HEADER_HEIGHT = 38; // height of the close button header area
  const ERROR_HEIGHT = 45; // approximate height of error message area when displayed

  // Safely destroy the mpegts player to prevent errors
  const safeDestroyPlayer = () => {
    try {
      if (playerRef.current) {
        setIsLoading(false);
        setLoadError(null);

        if (videoRef.current) {
          videoRef.current.removeAttribute('src');
          videoRef.current.load();
        }

        try {
          playerRef.current.pause();
        } catch (e) {
          // Ignore pause errors
        }

        try {
          playerRef.current.destroy();
        } catch (error) {
          if (
            error.name !== 'AbortError' &&
            !error.message?.includes('aborted')
          ) {
            console.log('Error during player destruction:', error.message);
          }
        } finally {
          playerRef.current = null;
        }
      }
    } catch (error) {
      console.log('Error during player cleanup:', error);
      playerRef.current = null;
    }

    // Clear overlay timer
    if (overlayTimeoutRef.current) {
      clearTimeout(overlayTimeoutRef.current);
      overlayTimeoutRef.current = null;
    }
  };

  // Start overlay auto-hide timer
  const startOverlayTimer = () => {
    if (overlayTimeoutRef.current) {
      clearTimeout(overlayTimeoutRef.current);
    }
    overlayTimeoutRef.current = setTimeout(() => {
      setShowOverlay(false);
    }, 4000); // Hide after 4 seconds
  };

  // Initialize VOD player (native HTML5 with enhanced controls)
  const initializeVODPlayer = () => {
    if (!videoRef.current || !streamUrl) return;

    setIsLoading(true);
    setLoadError(null);
    setShowOverlay(false);
    setShowControls(false);

    console.log('Initializing VOD player for:', streamUrl);

    const video = videoRef.current;

    // Enhanced video element configuration for VOD
    video.preload = 'metadata';
    video.crossOrigin = 'anonymous';

    // Restore saved volume
    const { volume: savedVolume, muted: savedMuted } = getPlayerPrefs();
    if (typeof savedVolume === 'number') video.volume = savedVolume;
    if (typeof savedMuted === 'boolean') video.muted = savedMuted;
    // Always start playback from the beginning of the seekable range.
    let hasSeekedToStart = false;
    const seekToStart = () => {
      if (hasSeekedToStart) return;
      try {
        let target = 0;
        if (video.seekable && video.seekable.length > 0) {
          target = video.seekable.start(0);
        }
        // Only apply if we're not already at/near the start.  Avoid
        // setting currentTime when the video has no duration yet.
        if (
          Number.isFinite(target) &&
          Math.abs((video.currentTime || 0) - target) > 0.25
        ) {
          video.currentTime = target;
        }
        hasSeekedToStart = true;
      } catch {
        // ignore
      }
    };

    const handleLoadStart = () => setIsLoading(true);
    const handleLoadedMetadata = () => {
      seekToStart();
    };
    const handleLoadedData = () => {
      setIsLoading(false);
      // hls.js applies its `startPosition` after MEDIA_ATTACHED, which can
      // run later than `loadedmetadata`. Re-seek here as a safety net so a
      // hls.js live playlist doesn't snap to the live edge after our first
      // seek attempt happened against an empty seekable range.
      seekToStart();
    };
    const handleCanPlay = () => {
      setIsLoading(false);
      // Final fallback for the Safari native-HLS path where seekable.start(0)
      // is sometimes only valid by the time `canplay` fires.
      seekToStart();
      // Auto-play for VOD content
      video.play().catch((e) => {
        console.log('Auto-play prevented:', e);
        setLoadError('Auto-play was prevented. Click play to start.');
      });
      // Show overlay briefly when video is ready, then auto-hide
      setShowOverlay(true);
      startOverlayTimer();
    };
    const handleError = (e) => {
      setIsLoading(false);

      setLoadError(getVODPlayerErrorMessage(e.target.error));
    };

    // Enhanced progress tracking for VOD
    const handleProgress = () => {
      // if (video.buffered.length > 0) {
      //   const bufferedEnd = video.buffered.end(video.buffered.length - 1);
      //   const duration = video.duration;
      //   if (duration > 0) {
      //     const bufferedPercent = (bufferedEnd / duration) * 100;
      //     // You could emit this to a store for UI feedback
      //   }
      // }
    };

    // Add event listeners
    video.addEventListener('loadstart', handleLoadStart);
    video.addEventListener('loadedmetadata', handleLoadedMetadata);
    video.addEventListener('loadeddata', handleLoadedData);
    video.addEventListener('canplay', handleCanPlay);
    video.addEventListener('error', handleError);
    video.addEventListener('progress', handleProgress);

    // HLS handling: in-progress recordings expose .m3u8 playlists (and ended
    // recordings whose MKV concat hasn't completed yet still serve via /hls/).
    // Native <video src="*.m3u8"> only works on Safari/iOS, and even there it
    // cannot follow a growing playlist with a useful seekable window.  Use
    // hls.js when supported so the user gets a full DVR/timeshift window
    // across all browsers.  The HLS playlist uses #EXT-X-PLAYLIST-TYPE=EVENT-
    // style growth (omit_endlist + hls_list_size 0), so hls.js will treat the
    // entire recorded duration as the seekable range while the recording is
    // still in progress and as a complete VOD once it ends.
    const isHls =
      typeof streamUrl === 'string' &&
      (streamUrl.includes('.m3u8') ||
        streamUrl.includes('/hls/index') ||
        streamUrl.includes('application/vnd.apple.mpegurl'));
    let hls = null;

    if (isHls && Hls.isSupported()) {
      hls = new Hls({
        // Open at the very beginning of the recording rather than the live
        // edge.  Without this, an in-progress recording would start at "now"
        // and hide everything already recorded.  hls.js applies this AFTER
        // MEDIA_ATTACHED, so the listener-driven `seekToStart()` above is
        // also kept as a safety net for the Safari native-HLS path and for
        // edge cases where this initial-position logic loses to the user's
        // first interaction.
        startPosition: 0,
        // Allow seeking back to the start of the recording, regardless of
        // current playhead position.  Recordings can be hours long and the
        // user may want to scrub anywhere; we explicitly disable buffer
        // eviction by setting a very large back-buffer length.
        backBufferLength: 90 * 60, // 90 minutes
        maxBufferLength: 60,
        maxMaxBufferLength: 600,
        // For an in-progress recording, hls.js refreshes the playlist on
        // its target-duration cadence; let it follow the live edge but keep
        // the full DVR window seekable.
        liveSyncDurationCount: 3,
        liveMaxLatencyDurationCount: 10,
        enableWorker: true,
        lowLatencyMode: false,
        // Inject the JWT into every playlist + segment XHR.  Read the token
        // from the auth store at request time rather than capturing the
        // closure value at hls.js init, so a refreshed access token mid-
        // playback is picked up on the next segment fetch.
        xhrSetup: (xhr) => {
          const token = useAuthStore.getState().accessToken;
          if (token) {
            xhr.setRequestHeader('Authorization', `Bearer ${token}`);
          }
        },
      });
      hls.on(Hls.Events.ERROR, (_evt, data) => {
        if (data.fatal) {
          // eslint-disable-next-line no-console
          console.error('HLS fatal error:', data.type, data.details);
          if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
            try {
              hls.startLoad();
            } catch {
              // ignore
            }
          } else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
            try {
              hls.recoverMediaError();
            } catch {
              // ignore
            }
          } else {
            setLoadError(`HLS playback error: ${data.details || data.type}`);
          }
        }
      });
      hls.attachMedia(video);
      hls.on(Hls.Events.MEDIA_ATTACHED, () => {
        hls.loadSource(streamUrl);
      });
    } else if (isHls && video.canPlayType('application/vnd.apple.mpegurl')) {
      // Safari path: native HLS support, including seekable DVR windows.
      video.src = streamUrl;
      video.load();
    } else {
      // Plain progressive file (MKV/MP4): native HTML5.
      video.src = streamUrl;
      video.load();
    }

    // Store cleanup function
    playerRef.current = {
      destroy: () => {
        video.removeEventListener('loadstart', handleLoadStart);
        video.removeEventListener('loadedmetadata', handleLoadedMetadata);
        video.removeEventListener('loadeddata', handleLoadedData);
        video.removeEventListener('canplay', handleCanPlay);
        video.removeEventListener('error', handleError);
        video.removeEventListener('progress', handleProgress);
        if (hls) {
          try {
            hls.destroy();
          } catch {
            // ignore
          }
        }
        video.removeAttribute('src');
        video.load();
      },
    };
  };

  // Initialize live stream player (mpegts.js)
  const initializeLivePlayer = () => {
    if (!videoRef.current || !streamUrl) return;

    setIsLoading(true);
    setLoadError(null);
    setShowControls(false);

    try {
      if (!mpegts.getFeatureList().mseLivePlayback) {
        setIsLoading(false);
        setLoadError(
          "Your browser doesn't support live video streaming. Please try Chrome or Edge."
        );
        return;
      }

      // mpegts.js workers run in WorkerGlobalScope where relative URLs are
      // not resolved against the page origin. Always pass an absolute URL.
      const absoluteStreamUrl =
        streamUrl.startsWith('/') && typeof window !== 'undefined'
          ? `${window.location.origin}${streamUrl}`
          : streamUrl;

      // Read the JWT from the auth store at player-creation time rather than
      // relying on the closure-captured `accessToken` value.  mpegts.js has
      // no per-request setup hook (unlike hls.js's xhrSetup), so this header
      // is baked into the IO loader for the life of the player; we just want
      // to be sure we use the freshest token available at the moment of
      // connection rather than whatever React-render snapshot we closed over.
      const liveAccessToken = useAuthStore.getState().accessToken;

      const player = mpegts.createPlayer(
        {
          type: 'mpegts',
          url: absoluteStreamUrl,
          isLive: true,
          cors: true,
        },
        {
          enableWorker: true,
          enableStashBuffer: false,
          liveBufferLatencyChasing: false,
          liveSync: false,
          autoCleanupSourceBuffer: true,
          autoCleanupMaxBackwardDuration: 120,
          autoCleanupMinBackwardDuration: 60,
          reuseRedirectedURL: true,
          headers: liveAccessToken
            ? { Authorization: `Bearer ${liveAccessToken}` }
            : undefined,
        }
      );

      player.attachMediaElement(videoRef.current);

      // Restore saved volume
      const { volume: savedVolume, muted: savedMuted } = getPlayerPrefs();
      if (typeof savedVolume === 'number')
        videoRef.current.volume = savedVolume;
      if (typeof savedMuted === 'boolean') videoRef.current.muted = savedMuted;

      player.on(mpegts.Events.LOADING_COMPLETE, () => {
        setIsLoading(false);
      });

      player.on(mpegts.Events.METADATA_ARRIVED, () => {
        setIsLoading(false);
      });

      player.on(mpegts.Events.ERROR, (errorType, errorDetail) => {
        setIsLoading(false);

        if (errorType !== 'NetworkError' || !errorDetail?.includes('aborted')) {
          console.error('Player error:', errorType, errorDetail);

          setLoadError(getLivePlayerErrorMessage(errorType, errorDetail));
        }
      });

      player.load();

      player.on(mpegts.Events.MEDIA_INFO, () => {
        setIsLoading(false);
        try {
          player.play().catch((e) => {
            console.log('Auto-play prevented:', e);
            setLoadError('Auto-play was prevented. Click play to start.');
          });
        } catch (e) {
          console.log('Error during play:', e);
          setLoadError(`Playback error: ${e.message}`);
        }
      });

      playerRef.current = player;
    } catch (error) {
      setIsLoading(false);
      console.error('Error initializing player:', error);

      if (
        error.message?.includes('codec') ||
        error.message?.includes('format')
      ) {
        setLoadError(
          'Codec not supported by your browser. Please try a different browser (Chrome/Edge recommended).'
        );
      } else {
        setLoadError(`Initialization error: ${error.message}`);
      }
    }
  };

  useEffect(() => {
    if (!isVisible || !streamUrl) {
      safeDestroyPlayer();
      return;
    }

    // Clean up any existing player
    safeDestroyPlayer();

    // Initialize the appropriate player based on content type
    if (contentType === 'vod') {
      initializeVODPlayer();
    } else {
      initializeLivePlayer();
    }

    // Attach volume-change listener now that the video element is in the DOM
    const video = videoRef.current;
    const handleVolumeChange = () =>
      savePlayerPrefs({ volume: video.volume, muted: video.muted });
    video?.addEventListener('volumechange', handleVolumeChange);

    return () => {
      video?.removeEventListener('volumechange', handleVolumeChange);
      safeDestroyPlayer();
    };
  }, [isVisible, streamUrl, contentType]);

  // Modified hideVideo handler to clean up player first
  const handleClose = (e) => {
    if (e) {
      e.stopPropagation();
      e.preventDefault();
    }
    safeDestroyPlayer();
    setTimeout(() => {
      hideVideo();
    }, 50);
  };

  const clampToVisible = useCallback(
    (x, y) => {
      if (typeof window === 'undefined') return { x, y };

      const totalHeight = videoSize.height + HEADER_HEIGHT + ERROR_HEIGHT;
      const minX = 0;
      // minY = 0 ensures the header row is always within the viewport so the
      // user can always grab and reposition the window.
      const minY = 0;
      const maxX = Math.max(0, window.innerWidth - videoSize.width);
      const maxY = Math.max(0, window.innerHeight - totalHeight);

      return {
        x: Math.min(Math.max(x, minX), maxX),
        y: Math.min(Math.max(y, minY), maxY),
      };
    },
    [HEADER_HEIGHT, ERROR_HEIGHT, videoSize.height, videoSize.width]
  );

  const clampToVisibleWithSize = useCallback(
    (x, y, width, height) => {
      if (typeof window === 'undefined') return { x, y };

      const totalHeight = height + HEADER_HEIGHT + ERROR_HEIGHT;
      const minX = 0; // left edge must stay in viewport
      const minY = 0; // header must always be reachable
      const maxX = Math.max(0, window.innerWidth - width);
      const maxY = Math.max(0, window.innerHeight - totalHeight);

      return {
        x: Math.min(Math.max(x, minX), maxX),
        y: Math.min(Math.max(y, minY), maxY),
      };
    },
    [HEADER_HEIGHT, ERROR_HEIGHT]
  );

  const handleResizeMove = useCallback(
    (event) => {
      if (!resizeStateRef.current) return;

      // If the mouse button was released outside the window, stop resizing.
      // Remove the move listeners immediately; the mouseup/touchend listener
      // (endResize) will clean itself up the next time the user clicks.
      if (event.type === 'mousemove' && event.buttons === 0) {
        resizeStateRef.current = null;
        setIsResizing(false);
        window.removeEventListener('mousemove', handleResizeMove);
        window.removeEventListener('touchmove', handleResizeMove);
        return;
      }

      const { clientX, clientY } = getClientCoordinates(event);
      const {
        startX,
        startY,
        startWidth,
        startHeight,
        startPos,
        handle,
        aspectRatio,
      } = resizeStateRef.current;

      const ratio = aspectRatio || aspectRatioRef.current;
      const { width: nextWidth, height: nextHeight } = calculateNewDimensions(
        clientX - startX,
        clientY - startY,
        startWidth,
        startHeight,
        handle,
        ratio
      );

      const constrainedSize = applyConstraints(
        nextWidth,
        nextHeight,
        ratio,
        startPos,
        handle,
        MIN_WIDTH,
        MIN_HEIGHT,
        VISIBLE_MARGIN
      );

      setVideoSize({
        width: Math.round(constrainedSize.width),
        height: Math.round(constrainedSize.height),
      });

      updatePositionIfNeeded(
        handle,
        startPos,
        startWidth,
        startHeight,
        constrainedSize
      );
    },
    [MIN_HEIGHT, MIN_WIDTH, VISIBLE_MARGIN, clampToVisibleWithSize]
  );

  const updatePositionIfNeeded = (
    handle,
    startPos,
    startWidth,
    startHeight,
    newSize
  ) => {
    if (!handle.isLeft && !handle.isTop) return;

    const posX = startPos?.x ?? 0;
    const posY = startPos?.y ?? 0;
    let nextX = handle.isLeft ? posX + (startWidth - newSize.width) : posX;
    let nextY = handle.isTop ? posY + (startHeight - newSize.height) : posY;

    const clamped = clampToVisibleWithSize(
      nextX,
      nextY,
      newSize.width,
      newSize.height
    );
    const nextPos = {
      x: handle.isLeft ? clamped.x : nextX,
      y: handle.isTop ? clamped.y : nextY,
    };

    setDragPosition(nextPos);
    dragPositionRef.current = nextPos;
  };

  const endResize = useCallback(() => {
    setIsResizing(false);
    resizeStateRef.current = null;
    window.removeEventListener('mousemove', handleResizeMove);
    window.removeEventListener('mouseup', endResize);
    window.removeEventListener('touchmove', handleResizeMove);
    window.removeEventListener('touchend', endResize);
  }, [handleResizeMove]);

  const startResize = (event, handle) => {
    event.stopPropagation();
    event.preventDefault();

    const { clientX, clientY } = getClientCoordinates(event);

    const aspectRatio =
      videoSize.height > 0
        ? videoSize.width / videoSize.height
        : aspectRatioRef.current;
    aspectRatioRef.current = aspectRatio;
    const startPos = dragPositionRef.current ||
      initialPositionRef.current || { x: 0, y: 0 };

    resizeStateRef.current = {
      startX: clientX,
      startY: clientY,
      startWidth: videoSize.width,
      startHeight: videoSize.height,
      aspectRatio,
      startPos,
      handle,
    };

    setIsResizing(true);

    window.addEventListener('mousemove', handleResizeMove);
    window.addEventListener('mouseup', endResize);
    window.addEventListener('touchmove', handleResizeMove);
    window.addEventListener('touchend', endResize);
  };

  useEffect(() => {
    return () => {
      endResize();
    };
  }, [endResize]);

  useEffect(() => {
    dragPositionRef.current = dragPosition;
  }, [dragPosition]);

  // Keep videoSizeRef current so the window-resize handler never closes over
  // a stale videoSize value.
  useEffect(() => {
    videoSizeRef.current = videoSize;
  }, [videoSize]);

  // Re-clamp size and position when the browser viewport changes
  useEffect(() => {
    const handleWindowResize = () => {
      const maxW = window.innerWidth - VISIBLE_MARGIN;
      const maxH = window.innerHeight - VISIBLE_MARGIN;
      const { width: curW, height: curH } = videoSizeRef.current ?? {
        width: 320,
        height: 180,
      };

      let newW = curW;
      let newH = curH;
      if (curW > maxW || curH > maxH) {
        const scale = Math.min(maxW / curW, maxH / curH);
        newW = Math.max(MIN_WIDTH, Math.round(curW * scale));
        newH = Math.max(MIN_HEIGHT, Math.round(curH * scale));
        setVideoSize({ width: newW, height: newH });
      }

      if (dragPositionRef.current) {
        const totalH = newH + HEADER_HEIGHT + ERROR_HEIGHT;
        const clamped = {
          x: Math.min(
            Math.max(dragPositionRef.current.x, 0),
            Math.max(0, window.innerWidth - newW)
          ),
          y: Math.min(
            Math.max(dragPositionRef.current.y, 0),
            Math.max(0, window.innerHeight - totalH)
          ),
        };
        if (
          clamped.x !== dragPositionRef.current.x ||
          clamped.y !== dragPositionRef.current.y
        ) {
          setDragPosition(clamped);
          dragPositionRef.current = clamped;
        }
      }
    };
    window.addEventListener('resize', handleWindowResize);
    return () => window.removeEventListener('resize', handleWindowResize);
  }, [MIN_WIDTH, MIN_HEIGHT, VISIBLE_MARGIN, HEADER_HEIGHT, ERROR_HEIGHT]);

  // Persist size whenever it changes, but skip high-frequency writes during
  // an active resize drag — the final value is captured when isResizing clears.
  useEffect(() => {
    if (!isResizing) {
      savePlayerPrefs({ size: videoSize });
    }
  }, [videoSize, isResizing]);

  // Persist position when a resize ends
  useEffect(() => {
    if (!isResizing && dragPositionRef.current) {
      savePlayerPrefs({ position: dragPositionRef.current });
    }
  }, [isResizing]);

  // Initialize the floating window near bottom-right once
  useEffect(() => {
    if (initialPositionRef.current || typeof window === 'undefined') return;

    // Try to restore the last saved position
    const savedPos = getPlayerPrefs().position;
    if (savedPos) {
      const clamped = clampToVisible(savedPos.x, savedPos.y);
      initialPositionRef.current = clamped;
      setDragPosition(clamped);
      dragPositionRef.current = clamped;
      return;
    }

    const totalHeight = videoSize.height + HEADER_HEIGHT + ERROR_HEIGHT;
    const initialX = Math.max(10, window.innerWidth - videoSize.width - 20);
    const initialY = Math.max(10, window.innerHeight - totalHeight - 20);
    const pos = clampToVisible(initialX, initialY);

    initialPositionRef.current = pos;
    setDragPosition(pos);
    dragPositionRef.current = pos;
  }, [
    clampToVisible,
    videoSize.height,
    videoSize.width,
    HEADER_HEIGHT,
    ERROR_HEIGHT,
  ]);

  const handleDragStart = useCallback(
    (event, data) => {
      const clientX = event.touches?.[0]?.clientX ?? event.clientX;
      const clientY = event.touches?.[0]?.clientY ?? event.clientY;
      const rect = videoContainerRef.current?.getBoundingClientRect();

      if (clientX != null && clientY != null && rect) {
        dragOffsetRef.current = {
          x: clientX - rect.left,
          y: clientY - rect.top,
        };
      } else {
        dragOffsetRef.current = { x: 0, y: 0 };
      }

      const clamped = clampToVisible(data?.x ?? 0, data?.y ?? 0);
      setDragPosition(clamped);
      dragPositionRef.current = clamped;
    },
    [clampToVisible]
  );

  const handleDrag = useCallback(
    (event) => {
      // If the mouse button was released outside the browser window the
      // mouseup event is never delivered to react-draggable. Detect this on
      // the next mousemove and fire a synthetic mouseup so it stops dragging.
      if (event.type === 'mousemove' && event.buttons === 0) {
        window.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
        return;
      }
      const clientX = event.touches?.[0]?.clientX ?? event.clientX;
      const clientY = event.touches?.[0]?.clientY ?? event.clientY;
      if (clientX == null || clientY == null) return;

      const nextX = clientX - (dragOffsetRef.current?.x ?? 0);
      const nextY = clientY - (dragOffsetRef.current?.y ?? 0);
      const clamped = clampToVisible(nextX, nextY);
      setDragPosition(clamped);
      dragPositionRef.current = clamped;
    },
    [clampToVisible]
  );

  const handleDragStop = useCallback(
    (_, data) => {
      const clamped = clampToVisible(data?.x ?? 0, data?.y ?? 0);
      setDragPosition(clamped);
      dragPositionRef.current = clamped;
      savePlayerPrefs({ position: clamped });
    },
    [clampToVisible]
  );

  // If the floating video is hidden or no URL is selected, do not render
  if (!isVisible || !streamUrl) {
    return null;
  }

  return (
    <Draggable
      nodeRef={videoContainerRef}
      cancel=".floating-video-no-drag"
      disabled={isResizing}
      position={dragPosition || undefined}
      defaultPosition={initialPositionRef.current || { x: 0, y: 0 }}
      onStart={handleDragStart}
      onDrag={handleDrag}
      onStop={handleDragStop}
    >
      <div
        ref={videoContainerRef}
        style={{
          position: 'fixed',
          top: 0,
          left: 0,
          width: `${videoSize.width}px`,
          zIndex: 9999,
          backgroundColor: '#333',
          borderRadius: '8px',
          overflow: 'visible',
          boxShadow: '0 2px 10px rgba(0,0,0,0.7)',
        }}
      >
        {/* Header row with optional title and close button */}
        <Flex
          justify="space-between"
          align="center"
          style={{
            padding: '3px 3px 3px 8px',
            minHeight: '38px',
            cursor: 'grab',
            userSelect: 'none',
          }}
        >
          {metadata?.name ? (
            <Text
              size="xs"
              fw={600}
              style={{
                color: 'rgba(255,255,255,0.9)',
                flex: 1,
                overflow: 'hidden',
                whiteSpace: 'nowrap',
                textOverflow: 'ellipsis',
                paddingRight: 4,
                textShadow: '0px 1px 3px rgba(0,0,0,0.8)',
                userSelect: 'none',
              }}
            >
              {metadata.name}
            </Text>
          ) : (
            <Box style={{ flex: 1 }} />
          )}
          <CloseButton
            onClick={handleClose}
            onTouchEnd={handleClose}
            onMouseDown={(e) => e.stopPropagation()}
            onTouchStart={(e) => e.stopPropagation()}
            style={{
              minHeight: '32px',
              minWidth: '32px',
              cursor: 'pointer',
              touchAction: 'manipulation',
              flexShrink: 0,
            }}
          />
        </Flex>

        {/* Video container with relative positioning for the overlay */}
        <Box
          style={{ position: 'relative' }}
          onMouseEnter={() => {
            setShowControls(true);
            if (contentType === 'vod' && !isLoading) {
              setShowOverlay(true);
              if (overlayTimeoutRef.current) {
                clearTimeout(overlayTimeoutRef.current);
              }
            }
          }}
          onMouseLeave={() => {
            if (contentType === 'vod' && !isLoading) {
              startOverlayTimer();
            }
          }}
        >
          {/* Enhanced video element with better controls for VOD */}
          <video
            ref={videoRef}
            controls={showControls}
            className="floating-video-no-drag"
            style={{
              width: '100%',
              height: `${videoSize.height}px`,
              backgroundColor: '#000',
              borderRadius: '0 0 8px 8px',
              // Better controls styling for VOD
              ...(contentType === 'vod' && {
                controlsList: 'nodownload',
                playsInline: true,
              }),
            }}
            // Add poster for VOD if available
            {...(contentType === 'vod' && {
              poster: metadata?.logo?.url, // Use VOD poster if available
            })}
          />

          {/* VOD title overlay when not loading - auto-hides after 4 seconds */}
          {!isLoading && metadata && contentType === 'vod' && showOverlay && (
            <Box
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                right: 0,
                background: 'linear-gradient(rgba(0,0,0,0.8), transparent)',
                padding: '10px 10px 20px',
                color: 'white',
                pointerEvents: 'none', // Allow clicks to pass through to video controls
                transition: 'opacity 0.3s ease-in-out',
                opacity: showOverlay ? 1 : 0,
              }}
            >
              <Text
                size="sm"
                weight={500}
                style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.8)' }}
              >
                {metadata.name}
              </Text>
              {metadata.year && (
                <Text
                  size="xs"
                  color="dimmed"
                  style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.8)' }}
                >
                  {metadata.year}
                </Text>
              )}
            </Box>
          )}

          {/* Loading overlay - only show when loading */}
          {isLoading && (
            <Box
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                height: '100%',
                backgroundColor: 'rgba(0, 0, 0, 0.7)',
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                justifyContent: 'center',
                zIndex: 5,
              }}
            >
              <Loader color="cyan" size="md" />
              <Text color="white" size="sm" mt={10}>
                Loading {contentType === 'vod' ? 'video' : 'stream'}...
              </Text>
            </Box>
          )}
        </Box>

        {/* Error message below video - doesn't block controls */}
        {!isLoading && loadError && (
          <Box
            style={{
              padding: '10px',
              backgroundColor: '#2d1b2e',
              borderTop: '1px solid #444',
            }}
          >
            <Text color="red" size="xs" style={{ textAlign: 'center' }}>
              {loadError}
            </Text>
          </Box>
        )}

        <ResizeHandles startResize={startResize} />
      </div>
    </Draggable>
  );
}
