// Lazy-loading page cache with 5-page ahead prefetch
// Shared by both comic and manga readers
// Feature Registration Pattern implementation

import type { ReaderContext } from "../core/reader-context";
import { detectPanels } from "./panel-detection.service";

export function init(context: ReaderContext): void {
  let state: PageCacheState | null = null;

  context.events.on("reader:loaded", (detail: { mediaItemId: string }) => {
    state = createPageCache(detail.mediaItemId);
  });

  context.events.on("page-cache:get", async (detail: { pageNumber: number }) => {
    if (state) {
      const result = await getCachedPage(state, detail.pageNumber);
      context.events.emit("page-cache:loaded", {
        page: detail.pageNumber,
        image: result.page,
      });
    }
  });

  context.events.on("page-cache:prefetch", (detail: { startPage: number }) => {
    if (state) {
      prefetchPages(state, detail.startPage);
    }
  });

  context.events.on("page-cache:cleanup", (detail: { currentPage: number }) => {
    if (state) {
      cleanupPageCache(state, detail.currentPage);
    }
  });

  context.events.on("page-cache:detected-panels", async (detail: { pageNumber: number }) => {
    if (state) {
      const panels = await detectPagePanels(state, detail.pageNumber);
      context.events.emit("page-cache:panels-ready", {
        pageNumber: detail.pageNumber,
        panels,
      });
    }
  });

  context.events.on("reader:unload", () => {
    if (state) {
      state.cache.clear();
      state.loading.clear();
      state.panelData.clear();
    }
  });
}

export interface PageCacheState {
  cache: Map<number, HTMLImageElement>;
  loading: Set<number>;
  maxAhead: number;
  mediaItemId: string;
  panelData: Map<number, { panels: any[]; method: string; confidence: number }>;
}

export function createPageCache(mediaItemId: string): PageCacheState {
  return {
    cache: new Map(),
    loading: new Set(),
    maxAhead: 5,
    mediaItemId,
    panelData: new Map(),
  };
}

export async function getCachedPage(
  state: PageCacheState,
  pageNumber: number,
): Promise<PageCacheState & { page: HTMLImageElement }> {
  if (state.cache.has(pageNumber)) {
    return { ...state, page: state.cache.get(pageNumber)! };
  }

  if (state.loading.has(pageNumber)) {
    return new Promise((resolve) => {
      const checkInterval = setInterval(() => {
        if (state.cache.has(pageNumber)) {
          clearInterval(checkInterval);
          resolve({ ...state, page: state.cache.get(pageNumber)! });
        }
      }, 100);
    }) as Promise<PageCacheState & { page: HTMLImageElement }>;
  }

  state.loading.add(pageNumber);

  const img = await loadComicPage(state, pageNumber);

  state.cache.set(pageNumber, img);
  state.loading.delete(pageNumber);

  prefetchPages(state, pageNumber + 1);
  cleanupPageCache(state, pageNumber);

  return { ...state, page: img };
}

export async function loadComicPage(
  state: PageCacheState,
  pageNumber: number,
): Promise<HTMLImageElement> {
  const token = localStorage.getItem("token");
  const response = await fetch(
    `/readers/${state.mediaItemId}/pages/${pageNumber}`,
    {
      headers: { Authorization: `Bearer ${token}` },
    },
  );

  if (!response.ok) {
    throw new Error(`Failed to load page ${pageNumber}`);
  }

  const blob = await response.blob();
  const img = new Image();
  img.src = URL.createObjectURL(blob);
  await new Promise((resolve) => {
    img.onload = resolve;
  });
  return img;
}

export function prefetchPages(state: PageCacheState, startPage: number): void {
  for (let i = startPage; i < startPage + state.maxAhead; i++) {
    if (!state.cache.has(i) && !state.loading.has(i)) {
      loadComicPage(state, i).then((img) => {
        state.cache.set(i, img);
      });
    }
  }
}

export function cleanupPageCache(
  state: PageCacheState,
  currentPage: number,
): PageCacheState {
  const keepPages = 10;
  const newCache = new Map(state.cache);

  for (const [page] of state.cache) {
    if (page < currentPage - keepPages) {
      newCache.delete(page);
    }
  }

  state.cache = newCache;
  return state;
}

export async function detectPagePanels(
  state: PageCacheState,
  pageNumber: number,
): Promise<any[]> {
  if (state.panelData?.has(pageNumber)) {
    return state.panelData.get(pageNumber)!.panels;
  }

  let image: HTMLImageElement;
  if (state.cache.has(pageNumber)) {
    image = state.cache.get(pageNumber)!;
  } else {
    image = await loadComicPage(state, pageNumber);
  }

  const canvas = document.createElement("canvas");
  canvas.width = image.width;
  canvas.height = image.height;
  const ctx = canvas.getContext("2d")!;
  ctx.drawImage(image, 0, 0);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

  const result = await detectPanels(imageData, true);

  if (!state.panelData) {
    state.panelData = new Map();
  }
  state.panelData.set(pageNumber, result);

  return result.panels;
}