import { useCallback, useEffect, useRef, useState } from 'react';

import { calculateThumbnailDimensions } from '@/utils/videoAspectRatio';

interface UseVideoFramesOptions {
  /**
   * Optional HTMLVideoElement that we're using to render the video.
   * If not provided, we create one internally.
   */
  video?: HTMLVideoElement | null;
  /**
   * Canvas where the thumbnails are drawn. Can be an HTMLCanvasElement or OffscreenCanvas.
   * If not provided, an internal OffscreenCanvas will be created.
   */
  canvas?: HTMLCanvasElement | OffscreenCanvas | null;
  /**
   * Video file URL to load. You could provide either video or videoUrl or both.
   */
  videoUrl?: string;
  /**
   * Duration of the video in milliseconds. Required if videoUrl is provided.
   */
  videoDuration?: number;
  /**
   * Number of thumbnails to generate, or specific timestamps where we want to pull thumbnails.
   * If array of numbers, generates thumbnails at those specific timestamps (in milliseconds).
   * If single number, generates that many thumbnails evenly distributed.
   */
  thumbnails?: number | number[];
  /**
   * Without specific thumbnails, pull thumbnails every {thumbnailInterval} seconds.
   * Only used when thumbnails is not specified.
   */
  thumbnailInterval?: number;
  /**
   * Timeout in milliseconds for video seek operations
   */
  seekTimeout?: number;
  /**
   * Quality of generated thumbnails (0-1)
   */
  thumbnailQuality?: number;
  /**
   * Whether to generate a sprite sheet instead of individual thumbnails
   * When true, returns spriteSheetUrl instead of individual thumbnails
   */
  generateSpriteSheet?: boolean;
}

interface UseVideoFramesReturn {
  /**
   * Array of generated frame thumbnail URLs
   */
  thumbnails: string[];
  /**
   * URL of generated sprite sheet (when generateSpriteSheet is true)
   */
  spriteSheetUrl?: string;
  /**
   * Whether frames are currently being generated
   */
  isGenerating: boolean;
  /**
   * Whether an error occurred during frame generation
   */
  isError: boolean;
  /**
   * Error message if generation failed
   */
  error?: string;
  /**
   * Reference to the internal video element (if no external video was provided)
   */
  videoRef: React.RefObject<HTMLVideoElement | null>;
}

/**
 * Generates thumbnail frames from a video for timeline display.
 * Uses OffscreenCanvas to efficiently extract frames at regular intervals or specific timestamps.
 *
 * @example
 * ```tsx
 * // Generate 4 evenly distributed thumbnails
 * const { thumbnails, isGenerating, isError } = useVideoFrames({
 *   videoUrl: 'https://example.com/video.mp4',
 *   videoDuration: 10000, // 10 seconds
 *   thumbnails: 4,
 * });
 *
 * // Generate thumbnails at specific timestamps
 * const { thumbnails, isGenerating } = useVideoFrames({
 *   video: videoRef,
 *   videoUrl: 'https://example.com/video.mp4',
 *   videoDuration: 10000,
 *   thumbnails: [0, 3000, 5000, 8000], // at 0s, 3s, 5s, 8s
 * });
 *
 * // Generate thumbnails every 2 seconds
 * const { thumbnails, isGenerating } = useVideoFrames({
 *   videoUrl: 'https://example.com/video.mp4',
 *   videoDuration: 10000,
 *   thumbnailInterval: 2, // every 2 seconds
 * });
 *
 * // Use external canvas (HTMLCanvasElement or OffscreenCanvas)
 * const canvasRef = useRef<HTMLCanvasElement>(null);
 * const { thumbnails, isGenerating } = useVideoFrames({
 *   videoUrl: 'https://example.com/video.mp4',
 *   videoDuration: 10000,
 *   canvas: canvasRef.current,
 *   thumbnails: 4,
 * });
 * ```
 *
 * @param options - Configuration options for frame generation
 * @returns Object containing thumbnails, loading state, and error information
 */
const useVideoFrames = (
  options: UseVideoFramesOptions = {}
): UseVideoFramesReturn => {
  const {
    video: externalVideo,
    canvas: externalCanvas,
    videoUrl,
    videoDuration,
    thumbnails = 10,
    thumbnailInterval,
    seekTimeout = 2000,
    thumbnailQuality = 0.7,
    generateSpriteSheet = false,
  } = options;

  const [thumbnailsList, setThumbnailsList] = useState<string[]>([]);
  const [spriteSheetUrl, setSpriteSheetUrl] = useState<string>();
  const [isGenerating, setIsGenerating] = useState(false);
  const [isError, setIsError] = useState(false);
  const [error, setError] = useState<string>();
  const [framesGenerated, setFramesGenerated] = useState(false);

  // Track created object URLs for cleanup
  const createdUrlsRef = useRef<Set<string>>(new Set());

  // Function to clean up object URLs
  const cleanupUrls = useCallback(() => {
    createdUrlsRef.current.forEach((url) => {
      URL.revokeObjectURL(url);
    });
    createdUrlsRef.current.clear();
  }, []);

  // Internal refs
  const internalVideoRef = useRef<HTMLVideoElement>(null);
  const offscreenCanvasRef = useRef<OffscreenCanvas | null>(null);

  // Use external video element if provided, otherwise use internal one
  const videoElement = externalVideo || internalVideoRef.current;

  // Use external canvas if provided, otherwise use internal OffscreenCanvas
  const canvasElement = externalCanvas || offscreenCanvasRef.current;

  // Calculate thumbnail timestamps based on options
  const getThumbnailTimestamps = useCallback((): number[] => {
    if (!videoDuration) return [];

    if (Array.isArray(thumbnails)) {
      // Specific timestamps provided
      return thumbnails.filter((ts) => ts >= 0 && ts <= videoDuration);
    }

    if (typeof thumbnails === 'number') {
      // Generate evenly distributed thumbnails
      const count = thumbnails;
      const interval = videoDuration / count;
      return Array.from({ length: count }, (_, i) => i * interval);
    }

    if (thumbnailInterval) {
      // Generate thumbnails at regular intervals
      const timestamps: number[] = [];
      for (
        let time = 0;
        time <= videoDuration;
        time += thumbnailInterval * 1000
      ) {
        timestamps.push(time);
      }
      return timestamps;
    }

    return [];
  }, [videoDuration, thumbnails, thumbnailInterval]);

  const generateFrameThumbnails = useCallback(async () => {
    if (!videoElement || !videoUrl || !videoDuration) {
      return;
    }

    // Clean up previous URLs before generating new ones
    cleanupUrls();

    const video = videoElement;
    const timestamps = getThumbnailTimestamps();

    if (timestamps.length === 0) {
      return;
    }

    // Initialize OffscreenCanvas if not already done and no external canvas provided
    if (!canvasElement && !externalCanvas) {
      offscreenCanvasRef.current = new OffscreenCanvas(120, 68);
    }

    setIsGenerating(true);
    setIsError(false);
    setError(undefined);

    const thumbnails: string[] = [];
    const canvas = canvasElement || offscreenCanvasRef.current;

    if (!canvas) {
      setIsError(true);
      setError('No canvas available for thumbnail generation');
      setIsGenerating(false);
      return;
    }

    const ctx = canvas.getContext('2d') as
      | CanvasRenderingContext2D
      | OffscreenCanvasRenderingContext2D
      | null;

    if (!ctx) {
      setIsError(true);
      setError('Failed to get canvas context');
      setIsGenerating(false);
      return;
    }

    try {
      for (const timestamp of timestamps) {
        video.currentTime = timestamp / 1000;

        await new Promise<void>((resolve) => {
          const timeout = setTimeout(() => {
            video.removeEventListener('seeked', onSeeked);
            resolve();
          }, seekTimeout);

          const onSeeked = () => {
            clearTimeout(timeout);
            video.removeEventListener('seeked', onSeeked);
            resolve();
          };
          video.addEventListener('seeked', onSeeked);
        });

        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;

        if (video.videoWidth === 0 || video.videoHeight === 0) {
          continue;
        }

        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

        // Handle different canvas types
        let dataUrl: string;
        if (canvas instanceof OffscreenCanvas) {
          // OffscreenCanvas has convertToBlob method
          const blob = await canvas.convertToBlob({
            type: 'image/jpeg',
            quality: thumbnailQuality,
          });
          dataUrl = URL.createObjectURL(blob);
          // Track object URL for cleanup
          createdUrlsRef.current.add(dataUrl);
        } else {
          // HTMLCanvasElement - use toDataURL method (no cleanup needed)
          dataUrl = canvas.toDataURL('image/jpeg', thumbnailQuality);
        }

        thumbnails.push(dataUrl);
      }

      setThumbnailsList(thumbnails);

      // Generate sprite sheet if requested
      if (generateSpriteSheet && thumbnails.length > 0) {
        // Calculate thumbnail dimensions based on video aspect ratio
        const { width: thumbnailWidth, height: thumbnailHeight } =
          calculateThumbnailDimensions(video.videoWidth, video.videoHeight);

        const columns = Math.ceil(Math.sqrt(thumbnails.length));
        const rows = Math.ceil(thumbnails.length / columns);

        // Create sprite sheet canvas
        const spriteCanvas = new OffscreenCanvas(
          columns * thumbnailWidth,
          rows * thumbnailHeight
        );
        const spriteCtx = spriteCanvas.getContext('2d');

        if (spriteCtx) {
          // Clear sprite sheet with black background
          spriteCtx.fillStyle = 'black';
          spriteCtx.fillRect(0, 0, spriteCanvas.width, spriteCanvas.height);

          // Load and draw each thumbnail to sprite sheet
          const imagePromises = thumbnails.map((thumbnail, index) => {
            return new Promise<void>((resolve) => {
              const img = new Image();
              img.crossOrigin = 'anonymous';
              img.onload = () => {
                const col = index % columns;
                const row = Math.floor(index / columns);
                spriteCtx.drawImage(
                  img,
                  col * thumbnailWidth,
                  row * thumbnailHeight,
                  thumbnailWidth,
                  thumbnailHeight
                );
                resolve();
              };
              img.onerror = () => resolve(); // Skip failed images
              img.src = thumbnail;
            });
          });

          await Promise.all(imagePromises);

          // Convert sprite sheet to blob/URL
          const blob = await spriteCanvas.convertToBlob({
            type: 'image/jpeg',
            quality: thumbnailQuality,
          });
          const spriteUrl = URL.createObjectURL(blob);
          createdUrlsRef.current.add(spriteUrl);
          setSpriteSheetUrl(spriteUrl);
        }
      }
    } catch (err) {
      setIsError(true);
      setError(
        err instanceof Error ? err.message : 'Failed to generate thumbnails'
      );
    } finally {
      setIsGenerating(false);
    }
  }, [
    videoElement,
    videoUrl,
    videoDuration,
    canvasElement,
    externalCanvas,
    getThumbnailTimestamps,
    seekTimeout,
    thumbnailQuality,
    cleanupUrls,
    generateSpriteSheet,
  ]);

  useEffect(() => {
    if (framesGenerated || !videoUrl || !videoDuration) return;

    const video = videoElement;
    if (!video) return;

    if (video.src !== videoUrl) {
      video.src = videoUrl;
      video.load();
    }

    if (
      video.readyState >= 3 &&
      video.videoWidth > 0 &&
      video.videoHeight > 0
    ) {
      generateFrameThumbnails();
      setFramesGenerated(true);
    } else {
      const handleVideoReady = () => {
        generateFrameThumbnails();
        setFramesGenerated(true);
      };

      if (video.readyState < 2) {
        video.addEventListener('loadedmetadata', handleVideoReady, {
          once: true,
        });
        video.addEventListener('canplay', handleVideoReady, { once: true });
        video.addEventListener('canplaythrough', handleVideoReady, {
          once: true,
        });
      }
    }
  }, [
    generateFrameThumbnails,
    videoUrl,
    videoDuration,
    framesGenerated,
    videoElement,
  ]);

  useEffect(() => {
    // Clean up URLs when video URL changes
    cleanupUrls();
    setThumbnailsList([]);
    setSpriteSheetUrl(undefined);
    setFramesGenerated(false);
    setIsGenerating(false);
    setIsError(false);
    setError(undefined);
  }, [videoUrl, cleanupUrls]);

  // Cleanup URLs on unmount
  useEffect(() => {
    return () => {
      cleanupUrls();
    };
  }, [cleanupUrls]);

  return {
    thumbnails: thumbnailsList,
    spriteSheetUrl,
    isGenerating,
    isError,
    error,
    videoRef: internalVideoRef,
  };
};

export default useVideoFrames;
