/**
 * usePageSwitch Hook
 *
 * Unified page navigation hook that eliminates white/black flashes during page transitions.
 * Uses preloaded blob URLs when available and keeps previous background visible
 * until new one is ready to paint.
 *
 * Features:
 * - Blob URL resolution from preload cache (instant display)
 * - Presigned URL fallback (retries with proxy on CORS failure)
 * - Previous background overlay for smooth transitions
 * - Ready state management for Image onLoad coordination
 */

import { useCallback, useRef, useState } from 'react';
import {
  resolveAssetPlaybackUrl,
  markPresignedUrlFailed,
  isRelativeStoragePath,
} from '../lib/assetUrl';
import { baseURLApi } from '../config';
import { logger } from '../lib/logger';

/**
 * Minimal page interface for page switching
 */
export interface SwitchablePage {
  id: string;
  background_image_url?: string;
  background_video_url?: string;
  background_audio_url?: string;
}

/**
 * Preload cache provider interface
 */
export interface PreloadCacheProvider {
  /** Instant lookup - returns decoded blob URL ready to display (O(1)) */
  getReadyBlobUrl?: (url: string) => string | null;
  /** Fallback: async blob URL from cache (creates new blob URL) */
  getCachedBlobUrl?: (url: string) => Promise<string | null>;
  preloadedUrls?: Set<string>;
}

export interface UsePageSwitchOptions {
  /** Preload cache provider for blob URL resolution */
  preloadCache?: PreloadCacheProvider;
}

export interface UsePageSwitchResult {
  /** Currently displayed background image URL */
  currentBgImageUrl: string;
  /** Currently displayed background video URL */
  currentBgVideoUrl: string;
  /** Currently displayed background audio URL */
  currentBgAudioUrl: string;
  /** Previous background image URL (for overlay) */
  previousBgImageUrl: string;
  /** Whether we're in the middle of a page switch */
  isSwitching: boolean;
  /** Whether the new background is ready to display */
  isNewBgReady: boolean;
  /**
   * Switch to a new page with smooth transition.
   * Resolves blob URLs from cache, shows previous background until new one is ready.
   */
  switchToPage: (
    targetPage: SwitchablePage | null,
    onSwitched?: () => void,
  ) => Promise<void>;
  /**
   * Directly set backgrounds without transition overlay.
   * Use for initial page load.
   */
  setBackgroundsDirectly: (
    imageUrl: string,
    videoUrl: string,
    audioUrl: string,
  ) => void;
  /**
   * Mark the new background as ready to display.
   * Call this from Image onLoad callback.
   */
  markBackgroundReady: () => void;
  /**
   * Clear the previous background overlay.
   * Call after transition completes or when ready to show new background.
   */
  clearPreviousBackground: () => void;
}

/**
 * Check if URL is a presigned S3 URL
 */
const isPresignedUrl = (url: string): boolean => {
  return url.includes('X-Amz-Signature=') || url.includes('x-amz-signature=');
};

/**
 * Build proxy URL from storage key for fallback
 */
const buildProxyUrl = (storageKey: string): string => {
  const normalizedPath = storageKey.replace(/^\/+/, '');
  return `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(normalizedPath)}`;
};

/**
 * Load and decode an image with presigned URL fallback.
 * Returns the URL that successfully loaded.
 */
const loadImageWithFallback = (
  url: string,
  storageKey?: string,
): Promise<string> => {
  return new Promise((resolve) => {
    const img = new window.Image();

    const tryLoad = (srcUrl: string, isRetry = false) => {
      img.src = srcUrl;

      img.onload = () => {
        if (typeof img.decode === 'function') {
          img
            .decode()
            .then(() => resolve(srcUrl))
            .catch(() => resolve(srcUrl));
        } else {
          resolve(srcUrl);
        }
      };

      img.onerror = () => {
        // If presigned URL failed and we have storage key, retry with proxy
        if (!isRetry && isPresignedUrl(srcUrl) && storageKey) {
          logger.info('Image presigned URL failed, retrying with proxy', {
            storageKey: storageKey.slice(-50),
          });
          markPresignedUrlFailed(storageKey);
          const proxyUrl = buildProxyUrl(storageKey);
          tryLoad(proxyUrl, true);
        } else {
          // Give up but still resolve to not block navigation
          resolve(srcUrl);
        }
      };
    };

    tryLoad(url);
  });
};

/**
 * Hook for smooth page switching without white/black flashes.
 *
 * Strategy:
 * 1. When switching pages, check if target background is in preload cache
 * 2. If cached, use blob URL (instant local data)
 * 3. If not cached, load with presigned URL fallback
 * 4. Keep previous background visible until new one is ready
 *
 * @example
 * const pageSwitch = usePageSwitch({
 *   preloadCache: {
 *     getCachedBlobUrl: preloadOrchestrator?.getCachedBlobUrl,
 *     preloadedUrls: preloadOrchestrator?.preloadedUrls,
 *   },
 * });
 *
 * // Switch to a page (with transition)
 * await pageSwitch.switchToPage(targetPage, () => {
 *   setActivePageId(targetPage.id);
 * });
 *
 * // In render, show previous background overlay while switching
 * {pageSwitch.previousBgImageUrl && !pageSwitch.isNewBgReady && (
 *   <div style={{ backgroundImage: `url("${pageSwitch.previousBgImageUrl}")` }} />
 * )}
 *
 * // On Image onLoad, mark background as ready
 * <Image onLoad={() => pageSwitch.markBackgroundReady()} />
 */
export function usePageSwitch(
  options: UsePageSwitchOptions = {},
): UsePageSwitchResult {
  const { preloadCache } = options;

  // Ref to track preload cache (avoids dependency issues with object identity)
  const preloadCacheRef = useRef(preloadCache);
  preloadCacheRef.current = preloadCache;

  // Current backgrounds
  const [currentBgImageUrl, setCurrentBgImageUrl] = useState('');
  const [currentBgVideoUrl, setCurrentBgVideoUrl] = useState('');
  const [currentBgAudioUrl, setCurrentBgAudioUrl] = useState('');

  // Refs to track current URLs for use in callbacks (avoids dependency issues)
  const currentBgImageUrlRef = useRef('');
  const currentBgVideoUrlRef = useRef('');
  const currentBgAudioUrlRef = useRef('');
  currentBgImageUrlRef.current = currentBgImageUrl;
  currentBgVideoUrlRef.current = currentBgVideoUrl;
  currentBgAudioUrlRef.current = currentBgAudioUrl;

  // Previous background for overlay
  const [previousBgImageUrl, setPreviousBgImageUrl] = useState('');
  const previousBgImageUrlRef = useRef('');
  previousBgImageUrlRef.current = previousBgImageUrl;

  // Transition state
  const [isSwitching, setIsSwitching] = useState(false);
  const [isNewBgReady, setIsNewBgReady] = useState(true);

  // Track blob URLs we created so we can revoke them
  const createdBlobUrlsRef = useRef<Set<string>>(new Set());

  /**
   * Revoke blob URLs that we created to prevent memory leaks
   */
  const revokeBlobUrl = useCallback((url: string) => {
    if (url.startsWith('blob:') && createdBlobUrlsRef.current.has(url)) {
      URL.revokeObjectURL(url);
      createdBlobUrlsRef.current.delete(url);
    }
  }, []);

  /**
   * Resolve a storage path to a displayable URL.
   * Priority: 1) ready blob URL by storage path, 2) cached blob URL by storage path,
   * 3) ready blob URL by resolved URL, 4) cached blob URL, 5) presigned URL with fallback
   */
  const resolveToDisplayUrl = useCallback(
    async (storagePath: string | undefined): Promise<string> => {
      if (!storagePath) return '';

      const cache = preloadCacheRef.current;

      // 1. Try in-memory storage path lookup first (instant, same session)
      if (cache?.getReadyBlobUrl) {
        const readyUrl = cache.getReadyBlobUrl(storagePath);
        if (readyUrl) {
          logger.info('Using ready blob URL (storage key)', {
            storagePath: storagePath.slice(-50),
          });
          return readyUrl;
        }
      }

      // 2. Try persistent cache by storage path (survives page refresh)
      if (cache?.getCachedBlobUrl) {
        try {
          const blobUrl = await cache.getCachedBlobUrl(storagePath);
          if (blobUrl) {
            createdBlobUrlsRef.current.add(blobUrl);
            logger.info('Using cached blob URL (storage key)', {
              storagePath: storagePath.slice(-50),
            });
            return blobUrl;
          }
        } catch {
          // Fall through to URL resolution
        }
      }

      // 3. Resolve to playback URL and try lookup (fallback for resolved URLs)
      const originalUrl = resolveAssetPlaybackUrl(storagePath);
      if (!originalUrl) return '';

      // Try instant blob URL lookup by resolved URL
      if (cache?.getReadyBlobUrl) {
        const readyUrl = cache.getReadyBlobUrl(originalUrl);
        if (readyUrl) {
          logger.info('Using ready blob URL', { url: originalUrl.slice(-50) });
          return readyUrl;
        }
      }

      // Fallback: try cached blob URL by resolved URL (check Cache API directly)
      if (cache?.getCachedBlobUrl) {
        try {
          const blobUrl = await cache.getCachedBlobUrl(originalUrl);
          if (blobUrl) {
            createdBlobUrlsRef.current.add(blobUrl);
            logger.info('Using cached blob URL for background', {
              originalUrl: originalUrl.slice(-50),
            });
            return blobUrl;
          }
        } catch {
          // Fall through
        }
      }

      // Load with presigned URL fallback (handles CORS failures)
      const storageKey = isRelativeStoragePath(storagePath)
        ? storagePath
        : undefined;
      return loadImageWithFallback(originalUrl, storageKey);
    },
    [],
  );

  /**
   * Resolve video/audio URL.
   * Priority: 1) ready blob URL by storage path, 2) cached blob URL by storage path,
   * 3) ready blob URL by resolved URL, 4) cached blob URL, 5) resolved URL
   */
  const resolveMediaUrl = useCallback(
    async (storagePath: string | undefined): Promise<string> => {
      if (!storagePath) return '';

      const cache = preloadCacheRef.current;

      // 1. Try in-memory storage path lookup first (instant, same session)
      if (cache?.getReadyBlobUrl) {
        const readyUrl = cache.getReadyBlobUrl(storagePath);
        if (readyUrl) {
          return readyUrl;
        }
      }

      // 2. Try persistent cache by storage path (survives page refresh)
      if (cache?.getCachedBlobUrl) {
        try {
          const blobUrl = await cache.getCachedBlobUrl(storagePath);
          if (blobUrl) {
            createdBlobUrlsRef.current.add(blobUrl);
            return blobUrl;
          }
        } catch {
          // Fall through
        }
      }

      // 3. Resolve URL and try lookup by resolved URL
      const originalUrl = resolveAssetPlaybackUrl(storagePath);
      if (!originalUrl) return '';

      if (cache?.getReadyBlobUrl) {
        const readyUrl = cache.getReadyBlobUrl(originalUrl);
        if (readyUrl) {
          return readyUrl;
        }
      }

      // Try cached blob URL by resolved URL (check Cache API directly)
      if (cache?.getCachedBlobUrl) {
        try {
          const blobUrl = await cache.getCachedBlobUrl(originalUrl);
          if (blobUrl) {
            createdBlobUrlsRef.current.add(blobUrl);
            return blobUrl;
          }
        } catch {
          // Fall through
        }
      }

      return originalUrl;
    },
    [],
  );

  /**
   * Switch to a new page with smooth transition
   */
  const switchToPage = useCallback(
    async (targetPage: SwitchablePage | null, onSwitched?: () => void) => {
      if (!targetPage) {
        setCurrentBgImageUrl('');
        setCurrentBgVideoUrl('');
        setCurrentBgAudioUrl('');
        setPreviousBgImageUrl('');
        setIsSwitching(false);
        setIsNewBgReady(true);
        onSwitched?.();
        return;
      }

      // Save current image as previous for overlay (use ref to avoid dependency)
      if (currentBgImageUrlRef.current) {
        setPreviousBgImageUrl(currentBgImageUrlRef.current);
      }

      setIsSwitching(true);
      setIsNewBgReady(false);

      // Resolve URLs in parallel, preferring cached blob URLs
      const [imageUrl, videoUrl, audioUrl] = await Promise.all([
        resolveToDisplayUrl(targetPage.background_image_url),
        resolveMediaUrl(targetPage.background_video_url),
        resolveMediaUrl(targetPage.background_audio_url),
      ]);

      // Set new backgrounds
      setCurrentBgImageUrl(imageUrl);
      setCurrentBgVideoUrl(videoUrl);
      setCurrentBgAudioUrl(audioUrl);

      // Notify caller that backgrounds are set
      onSwitched?.();

      // For blob URLs, mark ready immediately (local data)
      if (imageUrl.startsWith('blob:') || !imageUrl) {
        requestAnimationFrame(() => {
          requestAnimationFrame(() => {
            setIsNewBgReady(true);
          });
        });
      }
      // For remote images, wait for Image onLoad (caller should use markBackgroundReady)
    },
    [resolveToDisplayUrl, resolveMediaUrl],
  );

  /**
   * Directly set backgrounds without transition overlay
   */
  const setBackgroundsDirectly = useCallback(
    (imageUrl: string, videoUrl: string, audioUrl: string) => {
      // Revoke old blob URLs (use refs to avoid dependency)
      revokeBlobUrl(currentBgImageUrlRef.current);
      revokeBlobUrl(currentBgVideoUrlRef.current);
      revokeBlobUrl(currentBgAudioUrlRef.current);
      revokeBlobUrl(previousBgImageUrlRef.current);

      setCurrentBgImageUrl(imageUrl);
      setCurrentBgVideoUrl(videoUrl);
      setCurrentBgAudioUrl(audioUrl);
      setPreviousBgImageUrl('');
      setIsSwitching(false);
      setIsNewBgReady(true);
    },
    [revokeBlobUrl],
  );

  /**
   * Mark background as ready (call from Image onLoad)
   */
  const markBackgroundReady = useCallback(() => {
    setIsNewBgReady(true);
  }, []);

  /**
   * Clear the previous background overlay
   */
  const clearPreviousBackground = useCallback(() => {
    const prevUrl = previousBgImageUrlRef.current;
    setPreviousBgImageUrl('');
    setIsSwitching(false);

    // Revoke the previous blob URL after clearing
    if (prevUrl) {
      revokeBlobUrl(prevUrl);
    }
  }, [revokeBlobUrl]);

  return {
    currentBgImageUrl,
    currentBgVideoUrl,
    currentBgAudioUrl,
    previousBgImageUrl,
    isSwitching,
    isNewBgReady,
    switchToPage,
    setBackgroundsDirectly,
    markBackgroundReady,
    clearPreviousBackground,
  };
}
