/**
 * usePreloadOrchestrator Hook
 *
 * Main coordinator for online mode asset preloading.
 * Manages the priority queue and orchestrates downloads based on navigation.
 */

import { useEffect, useRef, useCallback, useState } from 'react';
import { useNeighborGraph } from './useNeighborGraph';
import { useNetworkAware } from './useNetworkAware';
import { downloadEventBus } from '../lib/offline/DownloadEventBus';
import { downloadManager } from '../lib/offline/DownloadManager';
import { StorageManager } from '../lib/offline/StorageManager';
import { PRELOAD_CONFIG } from '../config/preload.config';
import { OFFLINE_CONFIG } from '../config/offline.config';
import {
  resolveAssetPlaybackUrl,
  extractStoragePath,
  queuePresignedUrls,
  isRelativeStoragePath,
  markPresignedUrlFailed,
  markPresignedUrlsVerified,
  isPresignedUrl,
  buildProxyUrl,
} from '../lib/assetUrl';
import { logger } from '../lib/logger';
import type { BlobUrlReadyEvent } from '../types/offline';
import type {
  PreloadPage,
  PreloadPageLink,
  PreloadElement,
} from '../types/preload';

interface UsePreloadOrchestratorOptions {
  pages: PreloadPage[];
  pageLinks: PreloadPageLink[];
  elements: PreloadElement[];
  currentPageId: string | null;
  pageHistory?: string[];
  enabled?: boolean;
  maxNeighborDepth?: number;
}

interface PreloadQueueItem {
  id: string;
  url: string;
  storageKey?: string; // Original storage key for presigned URL cache invalidation
  priority: number;
  assetType: 'image' | 'video' | 'audio' | 'transition' | 'other';
  pageId: string;
}

interface UsePreloadOrchestratorResult {
  isPreloading: boolean;
  preloadedUrls: Set<string>;
  queueLength: number;
  /** Version counter that increments when blob URLs become ready (triggers re-renders) */
  readyUrlsVersion: number;
  preloadAsset: (url: string, priority?: number) => void;
  clearQueue: () => void;
  getCachedBlobUrl: (url: string) => Promise<string | null>;
  isUrlPreloaded: (url: string) => Promise<boolean>;
  /** Instant lookup - returns decoded blob URL or null */
  getReadyBlobUrl: (url: string) => string | null;
}

/**
 * Generate a unique ID for preload jobs
 */
const generateJobId = (): string => {
  return `preload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
};

/**
 * Map asset type string to AssetType enum expected by DownloadManager
 */
const mapAssetType = (
  assetType: 'image' | 'video' | 'audio' | 'transition' | 'other',
): 'image' | 'video' | 'audio' | 'transition' | 'other' => {
  return assetType;
};

export function usePreloadOrchestrator(
  options: UsePreloadOrchestratorOptions,
): UsePreloadOrchestratorResult {
  const {
    pages,
    pageLinks,
    elements,
    currentPageId,
    enabled = true,
    maxNeighborDepth = 1, // Only preload immediate neighbors by default
  } = options;

  const [isPreloading, setIsPreloading] = useState(false);
  const [preloadedUrls] = useState(() => new Set<string>());
  const [queueLength, setQueueLength] = useState(0);
  // Version counter to trigger re-renders when blob URLs become ready
  const [readyUrlsVersion, setReadyUrlsVersion] = useState(0);

  const queueRef = useRef<PreloadQueueItem[]>([]);
  const isProcessingRef = useRef(false);
  const lastPreloadedPageRef = useRef<string | null>(null);
  const lastPreloadedLinksCountRef = useRef<number>(0);

  // Use neighbor graph for determining what to preload
  const neighborGraph = useNeighborGraph({
    pages,
    pageLinks,
    elements,
    maxDepth: maxNeighborDepth,
  });

  // Use network info for adaptive preloading
  const { networkInfo } = useNetworkAware();

  // Subscribe to blob URL ready events from DownloadManager
  useEffect(() => {
    const unsubscribe = downloadEventBus.on(
      OFFLINE_CONFIG.events.blobUrlReady as Parameters<
        typeof downloadEventBus.on
      >[0],
      (data: BlobUrlReadyEvent) => {
        logger.info('[PRELOAD] Blob URL ready from DownloadManager', {
          storageKey: data.storageKey.slice(-50),
        });
        preloadedUrls.add(data.storageKey);
        setReadyUrlsVersion((v) => v + 1);
      },
    );
    return unsubscribe;
  }, [preloadedUrls]);

  // Cleanup blob URLs on unmount
  useEffect(() => {
    return () => {
      downloadManager.clearBlobUrls();
    };
  }, []);

  // Process the queue using DownloadManager
  const processQueue = useCallback(async () => {
    if (isProcessingRef.current) return;
    if (!networkInfo.isOnline) return;
    if (queueRef.current.length === 0) {
      setIsPreloading(false);
      return;
    }

    isProcessingRef.current = true;
    setIsPreloading(true);

    // Process all items in queue
    while (queueRef.current.length > 0) {
      const item = queueRef.current.shift();
      if (!item) break;

      setQueueLength(queueRef.current.length);

      // Get canonical storage key
      const storageKey = item.storageKey || extractStoragePath(item.url);

      // Skip if already preloaded
      if (preloadedUrls.has(storageKey)) {
        continue;
      }

      logger.info('[PRELOAD] Queuing with DownloadManager', {
        url: item.url.slice(-50),
        storageKey: storageKey.slice(-50),
        assetType: item.assetType,
        priority: item.priority,
      });

      // Use DownloadManager for unified download and blob URL creation
      // Mark presigned URL as verified if download succeeds
      downloadManager
        .addJob({
          assetId: item.id,
          projectId: '', // Not needed for online preload
          url: item.url,
          filename: item.url.split('/').pop() || 'asset',
          variantType: 'original',
          assetType: mapAssetType(item.assetType),
          priority: item.priority,
          storageKey,
          createBlobUrl: true, // Create blob URL for instant display
          persist: false, // Don't persist for online preload (in-memory only)
        })
        .then(() => {
          if (isPresignedUrl(item.url)) {
            markPresignedUrlsVerified();
          }
        })
        .catch(async (err) => {
          logger.error('[PRELOAD] Download failed', {
            url: item.url.slice(-50),
            error: err?.message,
          });

          // If presigned URL failed (e.g., CORS), retry with proxy URL
          if (storageKey && isPresignedUrl(item.url)) {
            markPresignedUrlFailed(storageKey);
            const proxyUrl = buildProxyUrl(storageKey);
            logger.info('[PRELOAD] Retrying with proxy URL', {
              storageKey: storageKey.slice(-50),
              proxyUrl: proxyUrl.slice(-60),
            });

            try {
              await downloadManager.addJob({
                assetId: item.id,
                projectId: '',
                url: proxyUrl,
                filename: storageKey.split('/').pop() || 'asset',
                variantType: 'original',
                assetType: mapAssetType(item.assetType),
                priority: item.priority,
                storageKey,
                createBlobUrl: true,
                persist: false,
              });
              logger.info('[PRELOAD] Proxy download complete', {
                url: proxyUrl.slice(-60),
              });
            } catch (retryErr) {
              logger.error('[PRELOAD] Proxy download also failed', {
                url: proxyUrl.slice(-60),
                error: retryErr instanceof Error ? retryErr.message : 'unknown',
              });
            }
          }
        });

      preloadedUrls.add(storageKey);
    }

    setIsPreloading(false);
    isProcessingRef.current = false;
  }, [networkInfo.isOnline, preloadedUrls]);

  // Add item to queue with priority sorting
  const addToQueue = useCallback(
    (item: PreloadQueueItem) => {
      const storageKey = item.storageKey || extractStoragePath(item.url);

      // Skip if already in queue or preloaded
      if (
        preloadedUrls.has(storageKey) ||
        queueRef.current.some(
          (q) => (q.storageKey || extractStoragePath(q.url)) === storageKey,
        )
      ) {
        return;
      }

      logger.info('[PRELOAD] Adding to queue', {
        url: item.url.slice(-60),
        storageKey: storageKey.slice(-50),
        assetType: item.assetType,
        priority: item.priority,
        queueLength: queueRef.current.length + 1,
      });

      // Insert in priority order (higher priority first)
      const insertIndex = queueRef.current.findIndex(
        (q) => q.priority < item.priority,
      );

      if (insertIndex === -1) {
        queueRef.current.push(item);
      } else {
        queueRef.current.splice(insertIndex, 0, item);
      }

      setQueueLength(queueRef.current.length);
      processQueue();
    },
    [preloadedUrls, processQueue],
  );

  // Manual preload function
  const preloadAsset = useCallback(
    (url: string, priority = 100) => {
      addToQueue({
        id: generateJobId(),
        url,
        priority,
        assetType: 'other',
        pageId: currentPageId || '',
      });
    },
    [addToQueue, currentPageId],
  );

  // Clear queue
  const clearQueue = useCallback(() => {
    queueRef.current = [];
    setQueueLength(0);
  }, []);

  // Get a cached asset as a blob URL (for video playback)
  // StorageManager.getAsset checks both IndexedDB (large files ≥ 5MB) and Cache API (small files)
  const getCachedBlobUrl = useCallback(
    async (url: string): Promise<string | null> => {
      try {
        const blob = await StorageManager.getAsset(url);
        if (blob) {
          return URL.createObjectURL(blob);
        }
        return null;
      } catch {
        return null;
      }
    },
    [],
  );

  // Check if URL is preloaded (in cache)
  const isUrlPreloaded = useCallback(
    async (url: string): Promise<boolean> => {
      const storageKey = extractStoragePath(url);
      // First check in-memory set
      if (preloadedUrls.has(storageKey)) return true;

      // Then check via StorageManager
      return StorageManager.hasAsset(storageKey);
    },
    [preloadedUrls],
  );

  // Instant lookup - returns decoded blob URL or null (O(1) Map lookup)
  // Uses DownloadManager's unified blob URL cache
  const getReadyBlobUrl = useCallback((url: string): string | null => {
    return downloadManager.getReadyBlobUrl(url);
  }, []);

  // Initialize ready blob URLs from cache for current page's assets
  // This ensures getReadyBlobUrl works on the first render for both backgrounds and element icons
  useEffect(() => {
    if (!currentPageId) return;

    const currentPage = pages.find((p) => p.id === currentPageId);
    if (!currentPage) return;

    const initializeFromCache = async () => {
      // Collect background URLs
      const bgUrls = [
        currentPage.background_image_url,
        currentPage.background_video_url,
        currentPage.background_audio_url,
      ].filter(Boolean) as string[];

      // Collect element asset URLs (icons, images, etc.) from current page
      const currentPageElements = elements.filter(
        (el) => el.pageId === currentPageId,
      );
      const elementAssetUrls: string[] = [];
      currentPageElements.forEach((element) => {
        if (!element.content_json) return;
        try {
          const content =
            typeof element.content_json === 'string'
              ? JSON.parse(element.content_json)
              : element.content_json;

          // Extract URLs from known asset fields
          const urlFields = PRELOAD_CONFIG.assetFields.all as readonly string[];
          const checkObject = (obj: Record<string, unknown>) => {
            if (!obj || typeof obj !== 'object') return;
            for (const [key, value] of Object.entries(obj)) {
              if (
                typeof value === 'string' &&
                value &&
                urlFields.includes(key)
              ) {
                elementAssetUrls.push(value);
              } else if (typeof value === 'object' && value !== null) {
                checkObject(value as Record<string, unknown>);
              }
            }
          };
          checkObject(content);
        } catch {
          // Ignore parse errors
        }
      });

      // Initialize all URLs from cache via DownloadManager
      const allUrls = [...bgUrls, ...elementAssetUrls];

      for (const storagePath of allUrls) {
        const storageKey = extractStoragePath(storagePath);

        // Skip if already ready
        if (downloadManager.getReadyBlobUrl(storageKey)) continue;

        // Check if cached and create blob URL if so
        const fullUrl = resolveAssetPlaybackUrl(storagePath);
        const hasAsset = await StorageManager.hasAsset(storageKey);
        if (hasAsset) {
          // Use DownloadManager.addJob with createBlobUrl to create the blob URL
          await downloadManager.addJob({
            assetId: `init-${storageKey}`,
            projectId: '',
            url: fullUrl,
            filename: storageKey.split('/').pop() || 'asset',
            variantType: 'original',
            assetType: 'other',
            storageKey,
            createBlobUrl: true,
            persist: false,
          });
        }
      }
    };

    initializeFromCache();
  }, [currentPageId, pages, elements]);

  // React to page changes - preload neighbors
  useEffect(() => {
    if (!enabled || !currentPageId || !networkInfo.isOnline) {
      return;
    }

    // Skip if we already preloaded for this page with the same data
    // Re-preload if pageLinks count changed (data just loaded)
    const currentLinksCount = pageLinks.length;
    const samePageAndData =
      lastPreloadedPageRef.current === currentPageId &&
      lastPreloadedLinksCountRef.current === currentLinksCount;

    if (samePageAndData) {
      return;
    }
    lastPreloadedPageRef.current = currentPageId;
    lastPreloadedLinksCountRef.current = currentLinksCount;

    logger.info('[PRELOAD] Starting preload for page', {
      currentPageId,
      maxNeighborDepth,
    });

    // Get prioritized assets based on current page
    const assets = neighborGraph.getPrioritizedAssets(
      currentPageId,
      maxNeighborDepth,
    );

    logger.info('[PRELOAD] Found assets from neighbor graph', {
      assetCount: assets.length,
      assets: assets.map((a) => ({ type: a.assetType, url: a.url.slice(-50) })),
    });

    // Collect all raw storage paths that need presigning
    const storagePaths: string[] = [];
    const currentPage = pages.find((p) => p.id === currentPageId);

    if (
      currentPage?.background_image_url &&
      isRelativeStoragePath(currentPage.background_image_url)
    ) {
      storagePaths.push(currentPage.background_image_url);
    }
    if (
      currentPage?.background_video_url &&
      isRelativeStoragePath(currentPage.background_video_url)
    ) {
      storagePaths.push(currentPage.background_video_url);
    }
    if (
      currentPage?.background_audio_url &&
      isRelativeStoragePath(currentPage.background_audio_url)
    ) {
      storagePaths.push(currentPage.background_audio_url);
    }

    assets.forEach((asset) => {
      if (isRelativeStoragePath(asset.url)) {
        storagePaths.push(asset.url);
      }
    });

    // Always collect neighbor background URLs for presigning
    // This ensures instant navigation to neighbor pages
    const neighbors = neighborGraph.getNeighbors(currentPageId, 1);
    neighbors.forEach(({ pageId }) => {
      const page = pages.find((p) => p.id === pageId);
      if (
        page?.background_image_url &&
        isRelativeStoragePath(page.background_image_url)
      ) {
        storagePaths.push(page.background_image_url);
      }
      // Always collect neighbor video URLs for smooth transitions
      if (
        page?.background_video_url &&
        isRelativeStoragePath(page.background_video_url)
      ) {
        storagePaths.push(page.background_video_url);
      }
      // Also collect neighbor audio URLs
      if (
        page?.background_audio_url &&
        isRelativeStoragePath(page.background_audio_url)
      ) {
        storagePaths.push(page.background_audio_url);
      }
    });

    // Batch fetch presigned URLs, then add to queue
    // Helper to resolve URL - prefer presigned if available, else fallback to proxy
    const resolveUrl = (
      storageKey: string,
      presignedUrls: Record<string, string>,
    ): string => {
      // Use presigned URL if available (will be tested on actual download)
      if (presignedUrls[storageKey]) {
        return presignedUrls[storageKey];
      }
      // Fallback to resolveAssetPlaybackUrl (will use proxy)
      return resolveAssetPlaybackUrl(storageKey);
    };

    // Two-phase preloading: current page first, then neighbors
    const addAssetsToQueue = async (
      presignedUrls: Record<string, string> = {},
    ) => {
      // Helper to determine max bytes for partial preload (online mode only)
      // IMPORTANT: Only applies to NEIGHBOR pages, not the current page
      // Transitions always use partial preload (regardless of page)
      const getMaxBytesForAsset = (
        assetType: 'image' | 'video' | 'audio' | 'transition' | 'other',
        isNeighborPage: boolean,
      ): number | undefined => {
        if (!PRELOAD_CONFIG.partialPreload.enabled) return undefined;

        // Transitions always use partial preload - they need just enough to start quickly
        if (assetType === 'transition') {
          return PRELOAD_CONFIG.partialPreload.transitionMaxBytes;
        }

        // Current page assets should be fully downloaded for best UX
        if (!isNeighborPage) return undefined;

        // Neighbor page media uses partial preload
        switch (assetType) {
          case 'video':
            return PRELOAD_CONFIG.partialPreload.videoMaxBytes;
          case 'audio':
            return PRELOAD_CONFIG.partialPreload.audioMaxBytes;
          default:
            return undefined; // Images need full download for display
        }
      };

      // Helper to create download job
      const createDownloadJob = (
        id: string,
        storageKey: string,
        priority: number,
        assetType: 'image' | 'video' | 'audio' | 'transition' | 'other',
        pageId: string,
      ): Promise<void> | null => {
        const resolvedUrl = resolveUrl(storageKey, presignedUrls);
        if (!resolvedUrl) return null;

        const normalizedKey = isRelativeStoragePath(storageKey)
          ? storageKey
          : extractStoragePath(resolvedUrl);

        // Skip if already preloaded
        if (preloadedUrls.has(normalizedKey)) return null;

        preloadedUrls.add(normalizedKey);

        // Determine if partial preload applies (neighbor pages only, media files only)
        const isNeighborPage = pageId !== currentPageId;
        const maxBytes = getMaxBytesForAsset(assetType, isNeighborPage);
        // Create blob URL for images (instant navigation) and full downloads
        // Partial downloads (video/audio/transition) use presigned URL directly for playback
        const createBlobUrl = assetType === 'image' || maxBytes === undefined;

        return downloadManager
          .addJob({
            assetId: id,
            projectId: '',
            url: resolvedUrl,
            filename: resolvedUrl.split('/').pop() || 'asset',
            variantType: 'original',
            assetType: mapAssetType(assetType),
            priority,
            storageKey: normalizedKey,
            createBlobUrl,
            persist: false,
            maxBytes,
          })
          .then(() => {
            if (isPresignedUrl(resolvedUrl)) {
              markPresignedUrlsVerified();
            }
          })
          .catch(async (err) => {
            logger.error('[PRELOAD] Download failed', {
              url: resolvedUrl.slice(-50),
              error: err?.message,
            });
            // Retry with proxy if presigned URL failed
            if (isPresignedUrl(resolvedUrl)) {
              markPresignedUrlFailed(normalizedKey);
              const proxyUrl = buildProxyUrl(normalizedKey);
              try {
                await downloadManager.addJob({
                  assetId: id,
                  projectId: '',
                  url: proxyUrl,
                  filename: normalizedKey.split('/').pop() || 'asset',
                  variantType: 'original',
                  assetType: mapAssetType(assetType),
                  priority,
                  storageKey: normalizedKey,
                  createBlobUrl,
                  persist: false,
                  maxBytes, // Preserve partial preload behavior for retry
                });
              } catch {
                // Ignore retry failures
              }
            }
          });
      };

      // ============================================
      // PHASE 1: Load current page IMAGE backgrounds only and WAIT
      // Video/audio backgrounds stream on their own - don't block on them
      // ============================================
      logger.info('[PRELOAD] Phase 1: Loading current page backgrounds');

      const currentPageImageJobs: Promise<void>[] = [];

      // Current page IMAGE background - WAIT for this (essential for visual)
      if (currentPage?.background_image_url) {
        const job = createDownloadJob(
          `bg-img-${currentPageId}`,
          currentPage.background_image_url,
          PRELOAD_CONFIG.priority.currentPage + 200,
          'image',
          currentPageId,
        );
        if (job) currentPageImageJobs.push(job);
      }

      // Current page VIDEO/AUDIO backgrounds - DON'T wait (they can stream)
      // These are started but not awaited - video player buffers on its own
      if (currentPage?.background_video_url) {
        createDownloadJob(
          `bg-vid-${currentPageId}`,
          currentPage.background_video_url,
          PRELOAD_CONFIG.priority.currentPage + 150,
          'video',
          currentPageId,
        );
        // Not pushed to awaited jobs - video streams on its own
      }
      if (currentPage?.background_audio_url) {
        createDownloadJob(
          `bg-aud-${currentPageId}`,
          currentPage.background_audio_url,
          PRELOAD_CONFIG.priority.currentPage + 100,
          'audio',
          currentPageId,
        );
        // Not pushed to awaited jobs - audio streams on its own
      }

      // Wait ONLY for IMAGE backgrounds (they're small and essential)
      // Video/audio can stream - don't block the page
      const phase1Start = Date.now();
      if (currentPageImageJobs.length > 0) {
        logger.info('[PRELOAD] Waiting for current page image backgrounds', {
          count: currentPageImageJobs.length,
        });
        await Promise.all(currentPageImageJobs);
        logger.info('[PRELOAD] Phase 1 complete', {
          elapsed: `${Date.now() - phase1Start}ms`,
        });
      } else {
        logger.info('[PRELOAD] Phase 1 complete (no image backgrounds)');
      }

      // ============================================
      // PHASE 2: Preload everything else (don't wait)
      // - Current page element assets (full downloads)
      // - Neighbor page backgrounds (partial preload for video/audio)
      // - Neighbor page element assets (partial preload for video/audio)
      // - Transition videos from page links (partial preload - 3MB)
      // ============================================
      logger.info('[PRELOAD] Phase 2: Preloading neighbors and transitions');

      // Current page element assets (moved from Phase 1 for faster startup)
      const currentPageAssets = assets.filter(
        (asset) => asset.pageId === currentPageId,
      );
      currentPageAssets.forEach((asset) => {
        createDownloadJob(
          generateJobId(),
          asset.url,
          asset.priority,
          asset.assetType,
          asset.pageId,
        );
      });

      // Neighbor page element assets
      const neighborAssets = assets.filter(
        (asset) => asset.pageId !== currentPageId,
      );
      neighborAssets.forEach((asset) => {
        createDownloadJob(
          generateJobId(),
          asset.url,
          asset.priority,
          asset.assetType,
          asset.pageId,
        );
      });

      // Neighbor background assets
      const neighbors = neighborGraph.getNeighbors(currentPageId, 1);
      neighbors.forEach(({ pageId }) => {
        const page = pages.find((p) => p.id === pageId);
        if (page?.background_image_url) {
          createDownloadJob(
            `bg-img-${pageId}`,
            page.background_image_url,
            PRELOAD_CONFIG.priority.neighborBase + 100,
            'image',
            pageId,
          );
        }
        if (page?.background_video_url) {
          createDownloadJob(
            `bg-vid-${pageId}`,
            page.background_video_url,
            PRELOAD_CONFIG.priority.neighborBase + 50,
            'video',
            pageId,
          );
        }
        if (page?.background_audio_url) {
          createDownloadJob(
            `bg-aud-${pageId}`,
            page.background_audio_url,
            PRELOAD_CONFIG.priority.neighborBase + 30,
            'audio',
            pageId,
          );
        }
      });

      logger.info('[PRELOAD] Phase 2: Neighbor assets queued');
    };

    // If there are storage paths to presign, fetch them first
    if (storagePaths.length > 0) {
      logger.info('[PRELOAD] Fetching presigned URLs', {
        count: storagePaths.length,
      });
      queuePresignedUrls(storagePaths)
        .then(async () => {
          logger.info('[PRELOAD] Presigned URLs fetched, adding to queue');
          await addAssetsToQueue();
        })
        .catch(async (error) => {
          logger.error(
            '[PRELOAD] Failed to fetch presigned URLs, falling back to proxy',
            {
              error: error?.message,
            },
          );
          // Fallback: add to queue without presigned URLs (will use backend proxy)
          await addAssetsToQueue();
        });
    } else {
      // No storage paths to presign, add directly to queue
      addAssetsToQueue();
    }
  }, [
    enabled,
    currentPageId,
    networkInfo.isOnline,
    neighborGraph,
    pages,
    pageLinks,
    addToQueue,
    maxNeighborDepth,
  ]);

  return {
    isPreloading,
    preloadedUrls,
    queueLength,
    readyUrlsVersion,
    preloadAsset,
    clearQueue,
    getCachedBlobUrl,
    isUrlPreloaded,
    getReadyBlobUrl,
  };
}
