/**
 * useEpubReader — load EPUB blob, manage CFI location, and persist reading progress.
 */

import { useCallback, useEffect, useRef, useState } from "react";
import { booksApi } from "../api/books";
import { getReadingProgress, updateReadingProgress } from "../api/reader";
import type { ReadingProgress, ReadingSettings } from "../types/reader";
import {
  applyRenditionSettings,
  isEpubCfi,
  marginWidthToGap,
  resolveSpineHref,
  updateChapterTitleFromRendition,
  type EpubRendition,
} from "../utils/epubRendition";

type EpubRenditionWithManager = EpubRendition & {
  hooks?: {
    content?: {
      register: (fn: (contents: { document: Document }) => void) => void;
    };
  };
};

export interface ReadingAnchor {
  cfi: string;
  percentage: number;
}

function percentageFromCfi(rendition: EpubRendition, cfi: string): number {
  if (!rendition.book?.locations) return 0;
  const pct = rendition.book.locations.percentageFromCfi(cfi);
  if (pct === null || pct === undefined) return 0;
  return Math.round(pct * 100);
}

function cfiFromRendition(rendition: EpubRendition | null): string | null {
  if (!rendition) return null;
  const start = (rendition as EpubRendition & { location?: { start?: { cfi?: string } } }).location
    ?.start;
  const cfi = start?.cfi;
  return cfi && isEpubCfi(cfi) ? cfi : null;
}

export interface UseEpubReaderReturn {
  epubUrl: string | null;
  location: string | number;
  chapterTitle: string;
  progress: ReadingProgress | null;
  isLoading: boolean;
  error: string | null;
  isBookmarkPeekActive: boolean;
  readingAnchor: ReadingAnchor | null;
  handleLocationChanged: (loc: string) => void;
  handleGetRendition: (rendition: EpubRendition) => void;
  navigateToHref: (href: string) => void;
  beginBookmarkPeek: (targetCfi: string) => void;
  resumeReadingAnchor: () => void;
  goToNextPage: () => void;
  goToPrevPage: () => void;
  applySettings: (settings: ReadingSettings) => void;
  renditionRef: React.RefObject<EpubRendition | null>;
  epubOptions: {
    flow: "paginated";
    manager: "default";
    spread: "none";
    width: string;
    height: string;
    gap: number;
  };
}

export function useEpubReader(
  bookId: number,
  marginWidth: number,
  initialEpubLocation?: string,
): UseEpubReaderReturn {
  const [epubUrl, setEpubUrl] = useState<string | null>(null);
  const [location, setLocation] = useState<string | number>(0);
  const [chapterTitle, setChapterTitle] = useState("");
  const [progress, setProgress] = useState<ReadingProgress | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [isBookmarkPeekActive, setIsBookmarkPeekActive] = useState(false);
  const [readingAnchor, setReadingAnchor] = useState<ReadingAnchor | null>(null);

  const renditionRef = useRef<EpubRendition | null>(null);
  const settingsRef = useRef<ReadingSettings | null>(null);
  const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const pendingPersistRef = useRef<{ cfi: string; percentage: number } | null>(null);
  const savedProgressPercentRef = useRef<number | null>(null);
  const isBookmarkPeekRef = useRef(false);
  const readingAnchorRef = useRef<ReadingAnchor | null>(null);

  const epubOptions = {
    flow: "paginated" as const,
    manager: "default" as const,
    spread: "none" as const,
    width: "100%",
    height: "100%",
    gap: marginWidthToGap(marginWidth),
  };

  const clearPeekState = useCallback(() => {
    isBookmarkPeekRef.current = false;
    readingAnchorRef.current = null;
    setIsBookmarkPeekActive(false);
    setReadingAnchor(null);
  }, []);

  const setPeekState = useCallback((anchor: ReadingAnchor) => {
    readingAnchorRef.current = anchor;
    isBookmarkPeekRef.current = true;
    setReadingAnchor(anchor);
    setIsBookmarkPeekActive(true);
  }, []);

  const flushProgress = useCallback(
    async (cfi: string, percentage: number) => {
      if (isBookmarkPeekRef.current) return;
      try {
        const patch: Parameters<typeof updateReadingProgress>[1] = { epub_location: cfi };
        if (percentage > 0) {
          patch.percentage = percentage;
          patch.current_position = percentage;
        }
        const updated = await updateReadingProgress(bookId, patch);
        setProgress(updated);
        if (percentage > 0) savedProgressPercentRef.current = percentage;
      } catch {
        /* silent — progress save is best-effort */
      }
    },
    [bookId],
  );

  const scheduleProgress = useCallback(
    (cfi: string, percentage: number) => {
      if (isBookmarkPeekRef.current) return;
      pendingPersistRef.current = { cfi, percentage };
      if (debounceTimer.current) clearTimeout(debounceTimer.current);
      debounceTimer.current = setTimeout(() => {
        const pending = pendingPersistRef.current;
        if (pending) void flushProgress(pending.cfi, pending.percentage);
      }, 800);
    },
    [flushProgress],
  );

  const captureAnchorFromCurrent = useCallback((): ReadingAnchor | null => {
    const rendition = renditionRef.current;
    let cfi: string | null = null;
    if (typeof location === "string" && isEpubCfi(location)) {
      cfi = location;
    } else {
      cfi = cfiFromRendition(rendition);
    }
    if (!cfi) return null;
    const percentage = rendition ? percentageFromCfi(rendition, cfi) : 0;
    return { cfi, percentage };
  }, [location]);

  const beginBookmarkPeek = useCallback(
    (targetCfi: string) => {
      if (!isEpubCfi(targetCfi)) return;

      if (!isBookmarkPeekRef.current) {
        let anchor = captureAnchorFromCurrent();
        if (!anchor && progress?.epub_location && isEpubCfi(progress.epub_location)) {
          const pct = progress.percentage ?? progress.current_position ?? 0;
          anchor = { cfi: progress.epub_location, percentage: pct > 0 ? Math.round(pct) : 0 };
        }
        if (anchor) setPeekState(anchor);
      }

      setLocation(targetCfi);
    },
    [captureAnchorFromCurrent, progress, setPeekState],
  );

  const resumeReadingAnchor = useCallback(() => {
    const anchor = readingAnchorRef.current;
    clearPeekState();

    if (!anchor?.cfi || !isEpubCfi(anchor.cfi)) return;

    setLocation(anchor.cfi);
    const rendition = renditionRef.current;
    const pct = rendition ? percentageFromCfi(rendition, anchor.cfi) : anchor.percentage;
    void flushProgress(anchor.cfi, pct > 0 ? pct : anchor.percentage);
  }, [clearPeekState, flushProgress]);

  useEffect(() => {
    let blobUrl: string | null = null;
    let cancelled = false;

    savedProgressPercentRef.current = null;
    pendingPersistRef.current = null;
    clearPeekState();

    async function loadEpub() {
      setIsLoading(true);
      setError(null);
      try {
        const [progressData, blob] = await Promise.all([
          getReadingProgress(bookId).catch(() => null),
          booksApi.getEpubFile(bookId),
        ]);
        if (cancelled) return;

        blobUrl = URL.createObjectURL(blob);
        setEpubUrl(blobUrl);

        const savedPct = progressData?.current_position ?? progressData?.percentage ?? 0;
        savedProgressPercentRef.current =
          typeof savedPct === "number" && savedPct > 0 ? savedPct : null;

        const bookmarkPeekCfi =
          initialEpubLocation && isEpubCfi(initialEpubLocation) ? initialEpubLocation : null;
        const savedCfi =
          progressData?.epub_location && isEpubCfi(progressData.epub_location)
            ? progressData.epub_location
            : null;

        if (bookmarkPeekCfi && savedCfi) {
          const anchorPct =
            typeof savedPct === "number" && savedPct > 0 ? Math.round(savedPct) : 0;
          setPeekState({ cfi: savedCfi, percentage: anchorPct });
          setProgress(progressData);
          setLocation(bookmarkPeekCfi);
        } else if (bookmarkPeekCfi) {
          setLocation(bookmarkPeekCfi);
        } else if (savedCfi) {
          setProgress(progressData);
          setLocation(savedCfi);
        } else if (progressData) {
          setProgress(progressData);
        }
      } catch (err: unknown) {
        if (!cancelled) {
          setError(err instanceof Error ? err.message : "Failed to load EPUB");
        }
      } finally {
        if (!cancelled) setIsLoading(false);
      }
    }

    void loadEpub();

    return () => {
      cancelled = true;
      if (blobUrl) URL.revokeObjectURL(blobUrl);
      if (debounceTimer.current) clearTimeout(debounceTimer.current);
      if (!isBookmarkPeekRef.current) {
        const pending = pendingPersistRef.current;
        if (pending) void flushProgress(pending.cfi, pending.percentage);
      }
      clearPeekState();
    };
  }, [bookId, initialEpubLocation, flushProgress, clearPeekState, setPeekState]);

  const handleLocationChanged = useCallback(
    (loc: string) => {
      setLocation(loc);

      if (!isEpubCfi(loc)) return;

      const rendition = renditionRef.current;
      const percentage = rendition ? percentageFromCfi(rendition, loc) : 0;

      if (rendition) {
        void updateChapterTitleFromRendition(rendition).then((title) => {
          if (title) setChapterTitle(title);
        });
      }

      scheduleProgress(loc, percentage);
    },
    [scheduleProgress],
  );

  const navigateToHref = useCallback((href: string) => {
    const rendition = renditionRef.current;
    if (!rendition) {
      setLocation(href);
      return;
    }

    void rendition.book.ready.then(() => {
      const target = resolveSpineHref(rendition.book, href);
      setLocation(target);
      return rendition.display(target);
    });
  }, []);

  const syncProgressFromRendition = useCallback(
    (attempt = 0) => {
      if (isBookmarkPeekRef.current) return;

      const rendition = renditionRef.current;
      if (!rendition) return;

      const start = (rendition as EpubRendition & { location?: { start?: { cfi?: string } } }).location
        ?.start;
      const cfi = start?.cfi;
      if (!cfi || !isEpubCfi(cfi)) return;

      const percentage = percentageFromCfi(rendition, cfi);
      void flushProgress(cfi, percentage);

      if (percentage <= 0 && attempt < 2) {
        setTimeout(() => syncProgressFromRendition(attempt + 1), attempt === 0 ? 600 : 1500);
      }
    },
    [flushProgress],
  );

  const handleGetRendition = useCallback((rendition: EpubRendition) => {
    const r = rendition as EpubRenditionWithManager;
    renditionRef.current = rendition;

    const resizeReader = () => {
      const container = document.querySelector(".reader-epub-container");
      if (container && "resize" in rendition) {
        (rendition as EpubRendition & { resize: (w: number, h: number) => void }).resize(
          container.clientWidth,
          container.clientHeight,
        );
      }
    };

    r.hooks?.content?.register?.((contents: { document: Document }) => {
      if (contents.document?.body) {
        contents.document.body.style.userSelect = "text";
        contents.document.body.style.webkitUserSelect = "text";
      }
    });

    void r.book.ready
      .then(() => r.book.locations.generate(1600))
      .then(resizeReader)
      .then(() => updateChapterTitleFromRendition(r))
      .then((title) => {
        if (title) setChapterTitle(title);
      })
      .then(() => syncProgressFromRendition());

    window.addEventListener("resize", resizeReader);

    if (settingsRef.current) {
      applyRenditionSettings(rendition, settingsRef.current);
    }

    return () => window.removeEventListener("resize", resizeReader);
  }, [syncProgressFromRendition]);

  const applySettings = useCallback((settings: ReadingSettings) => {
    settingsRef.current = settings;
    if (renditionRef.current) {
      applyRenditionSettings(renditionRef.current, settings);
    }
  }, []);

  const goToNextPage = useCallback(() => {
    renditionRef.current?.next();
  }, []);

  const goToPrevPage = useCallback(() => {
    renditionRef.current?.prev();
  }, []);

  return {
    epubUrl,
    location,
    chapterTitle,
    progress,
    isLoading,
    error,
    isBookmarkPeekActive,
    readingAnchor,
    handleLocationChanged,
    handleGetRendition,
    navigateToHref,
    beginBookmarkPeek,
    resumeReadingAnchor,
    goToNextPage,
    goToPrevPage,
    applySettings,
    epubOptions,
    renditionRef,
  };
}
