import type { DocumentSettings } from '@/pages/editor/store/editorStore';
import type { PageData } from '../types';

export const INITIAL_PAGES_PRELOAD_COUNT = 5;

type MediaAsset = {
  url: string;
  kind: 'image' | 'video' | 'audio';
};

const MEDIA_LOAD_TIMEOUT_MS = 15_000;

function addImageUrl(urls: Map<string, MediaAsset>, url?: string) {
  if (!url) return;
  urls.set(url, { url, kind: 'image' });
}

function addVideoUrl(urls: Map<string, MediaAsset>, url?: string) {
  if (!url) return;
  urls.set(url, { url, kind: 'video' });
}

function addAudioUrl(urls: Map<string, MediaAsset>, url?: string) {
  if (!url) return;
  urls.set(url, { url, kind: 'audio' });
}

export function extractPageMediaAssets(
  pages: PageData[],
  documentSettings?: Partial<DocumentSettings>,
): MediaAsset[] {
  const assets = new Map<string, MediaAsset>();

  for (const page of pages) {
    if (page.backgroundType === 'image') {
      addImageUrl(assets, page.backgroundImageUrl);
    } else if (page.backgroundType === 'video') {
      addVideoUrl(assets, page.backgroundVideoUrl);
    }

    for (const element of page.elements) {
      if (element.visible === false) continue;

      if (element.type === 'image' || element.type === 'sticker') {
        addImageUrl(assets, element.imageUrl);
      } else if (element.type === 'video') {
        addVideoUrl(assets, element.videoUrl);
      } else if (element.type === 'audio') {
        addAudioUrl(assets, element.audioUrl);
      }
    }
  }

  if (documentSettings?.backgroundType === 'image') {
    addImageUrl(assets, documentSettings.backgroundImageUrl);
  } else if (documentSettings?.backgroundType === 'video') {
    addVideoUrl(assets, documentSettings.backgroundVideoUrl);
  }

  return Array.from(assets.values());
}

function preloadImage(url: string): Promise<void> {
  return new Promise((resolve) => {
    const img = new Image();
    const finish = () => {
      img.onload = null;
      img.onerror = null;
      resolve();
    };
    img.onload = finish;
    img.onerror = finish;
    img.src = url;
  });
}

function preloadVideo(url: string): Promise<void> {
  return new Promise((resolve) => {
    const video = document.createElement('video');
    video.preload = 'auto';
    video.muted = true;

    const finish = () => {
      video.onloadeddata = null;
      video.onerror = null;
      video.removeAttribute('src');
      video.load();
      resolve();
    };

    video.onloadeddata = finish;
    video.onerror = finish;
    video.src = url;
  });
}

function preloadAudio(url: string): Promise<void> {
  return new Promise((resolve) => {
    const audio = document.createElement('audio');
    audio.preload = 'auto';

    const finish = () => {
      audio.onloadeddata = null;
      audio.onerror = null;
      audio.removeAttribute('src');
      audio.load();
      resolve();
    };

    audio.onloadeddata = finish;
    audio.onerror = finish;
    audio.src = url;
  });
}

function withTimeout(promise: Promise<void>, ms: number): Promise<void> {
  return Promise.race([
    promise,
    new Promise<void>((resolve) => {
      window.setTimeout(resolve, ms);
    }),
  ]);
}

export function preloadMediaAsset(asset: MediaAsset): Promise<void> {
  const load =
    asset.kind === 'video'
      ? preloadVideo(asset.url)
      : asset.kind === 'audio'
        ? preloadAudio(asset.url)
        : preloadImage(asset.url);
  return withTimeout(load, MEDIA_LOAD_TIMEOUT_MS);
}

export async function preloadPagesMedia(
  pages: PageData[],
  documentSettings?: Partial<DocumentSettings>,
  onProgress?: (loaded: number, total: number) => void,
): Promise<void> {
  const assets = extractPageMediaAssets(pages, documentSettings);
  const total = assets.length;

  if (total === 0) {
    onProgress?.(0, 0);
    return;
  }

  let loaded = 0;
  await Promise.all(
    assets.map(async (asset) => {
      await preloadMediaAsset(asset);
      loaded += 1;
      onProgress?.(loaded, total);
    }),
  );
}
