"use client";

import {
  Fragment,
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
  readProgress,
  writeProgress,
} from "@/components/ReadingProgressButton";

type ChapterMeta = {
  id: number;
  number: number;
  title: string;
  totalPages: number;
};

type PageMeta = { number: number; width: number; height: number };

type PageReaderProps = {
  mangaSlug: string;
  mangaTitle: string;
  startChapterNumber: number;
  prevChapter: number | null;
  nextChapter: number | null;
  chapters: ChapterMeta[];
  initialChapterMeta: PageMeta[];
};

const PREFETCH_NEXT_AT = 3;
const IMAGE_BATCH_RADIUS = 3;
const DOUBLE_TAP_MS = 280;

const pageKey = (chNum: number, pNum: number) => `${chNum}-${pNum}`;

type IntersectingPage = {
  chNum: number;
  pNum: number;
  el: HTMLDivElement;
};

export function PageReader({
  mangaSlug,
  mangaTitle,
  startChapterNumber,
  prevChapter,
  nextChapter,
  chapters,
  initialChapterMeta,
}: PageReaderProps) {
  const [showUI, setShowUI] = useState(true);
  const [showDrawer, setShowDrawer] = useState(false);
  const [chapterMetas, setChapterMetas] = useState<Record<number, PageMeta[]>>({
    [startChapterNumber]: initialChapterMeta,
  });
  const [images, setImages] = useState<Record<string, string>>({});
  const [currentChapterNum, setCurrentChapterNum] =
    useState(startChapterNumber);
  const [currentPageNum, setCurrentPageNum] = useState(() => {
    if (typeof window === "undefined") return 1;
    const p = readProgress(mangaSlug);
    if (p && p.chapter === startChapterNumber && p.page > 1) return p.page;
    return 1;
  });

  // Observer stays stable across state updates.
  const imagesRef = useRef(images);
  const chapterMetasRef = useRef(chapterMetas);
  useEffect(() => {
    imagesRef.current = images;
  }, [images]);
  useEffect(() => {
    chapterMetasRef.current = chapterMetas;
  }, [chapterMetas]);

  const metaInflightRef = useRef<Set<number>>(new Set());
  const imagesInflightRef = useRef<Set<string>>(new Set());
  const pageElRef = useRef<Map<string, HTMLDivElement>>(new Map());
  const observerRef = useRef<IntersectionObserver | null>(null);
  const hiddenByScrollRef = useRef(false);
  const drawerScrollRef = useRef<HTMLDivElement | null>(null);
  const drawerActiveRef = useRef<HTMLAnchorElement | null>(null);
  // Pages currently inside the observer's viewport margin. The scroll tick
  // walks this small set instead of every loaded page.
  const intersectingPagesRef = useRef<Map<string, IntersectingPage>>(new Map());

  const loadedChapterNumbers = useMemo(() => {
    return Object.keys(chapterMetas)
      .map(Number)
      .filter((n) => n >= startChapterNumber)
      .sort((a, b) => a - b);
  }, [chapterMetas, startChapterNumber]);

  const chapterByNumber = useMemo(() => {
    const m = new Map<number, ChapterMeta>();
    for (const c of chapters) m.set(c.number, c);
    return m;
  }, [chapters]);

  const fetchImagesAround = useCallback(
    async (chapterNum: number, pageNum: number) => {
      const meta = chapterMetasRef.current[chapterNum];
      const chapter = chapterByNumber.get(chapterNum);
      if (!meta || !chapter) return;
      const start = Math.max(1, pageNum - IMAGE_BATCH_RADIUS);
      const end = Math.min(meta.length, pageNum + IMAGE_BATCH_RADIUS);
      const toFetch: number[] = [];
      for (let p = start; p <= end; p++) {
        const k = pageKey(chapterNum, p);
        if (imagesRef.current[k] || imagesInflightRef.current.has(k)) continue;
        imagesInflightRef.current.add(k);
        toFetch.push(p);
      }
      if (toFetch.length === 0) return;
      const minP = toFetch[0];
      const maxP = toFetch[toFetch.length - 1];
      try {
        const res = await fetch(
          `/api/pages?chapterId=${chapter.id}&offset=${minP - 1}&limit=${
            maxP - minP + 1
          }`
        );
        const batch: { number: number; imageUrl: string }[] = await res.json();
        setImages((prev) => {
          const next = { ...prev };
          for (const item of batch) {
            next[pageKey(chapterNum, item.number)] = item.imageUrl;
          }
          return next;
        });
      } catch {
        // observer will re-trigger on next intersection
      } finally {
        for (const p of toFetch)
          imagesInflightRef.current.delete(pageKey(chapterNum, p));
      }
    },
    [chapters]
  );

  const prefetchNextChapterMeta = useCallback(
    async (currentChapterNumArg: number) => {
      const idx = chapters.findIndex((c) => c.number === currentChapterNumArg);
      if (idx < 0 || idx >= chapters.length - 1) return;
      const next = chapters[idx + 1];
      if (chapterMetasRef.current[next.number]) return;
      if (metaInflightRef.current.has(next.number)) return;
      metaInflightRef.current.add(next.number);
      try {
        const res = await fetch(`/api/chapters/${next.id}/meta`);
        const meta: PageMeta[] = await res.json();
        setChapterMetas((prev) => ({ ...prev, [next.number]: meta }));
      } catch {
        // will retry next observer fire
      } finally {
        metaInflightRef.current.delete(next.number);
      }
    },
    [chapters]
  );

  useEffect(() => {
    observerRef.current = new IntersectionObserver(
      (entries) => {
        for (const e of entries) {
          const el = e.target as HTMLDivElement;
          const chNum = Number(el.dataset.chapter);
          const pNum = Number(el.dataset.page);
          if (!chNum || !pNum) continue;
          const key = pageKey(chNum, pNum);
          if (e.isIntersecting) {
            intersectingPagesRef.current.set(key, { chNum, pNum, el });
            fetchImagesAround(chNum, pNum);
            const chapter = chapterByNumber.get(chNum);
            if (chapter && pNum >= chapter.totalPages - PREFETCH_NEXT_AT) {
              prefetchNextChapterMeta(chNum);
            }
          } else {
            intersectingPagesRef.current.delete(key);
          }
        }
      },
      { rootMargin: "1200px" }
    );
    for (const el of pageElRef.current.values()) {
      observerRef.current.observe(el);
    }
    return () => observerRef.current?.disconnect();
  }, [fetchImagesAround, prefetchNextChapterMeta, chapterByNumber]);

  const setPageRef = useCallback((key: string, el: HTMLDivElement | null) => {
    const observer = observerRef.current;
    const prev = pageElRef.current.get(key);
    if (prev && observer) observer.unobserve(prev);
    if (el) {
      pageElRef.current.set(key, el);
      if (observer) observer.observe(el);
    } else {
      pageElRef.current.delete(key);
    }
  }, []);

  // Sync scroll + rAF re-scroll: defends against browser scroll-restoration
  // on hard reload (the sync pass handles soft nav where Link scroll={false}).
  const resumeDoneRef = useRef(false);
  useLayoutEffect(() => {
    if (resumeDoneRef.current) return;
    resumeDoneRef.current = true;
    const p = readProgress(mangaSlug);
    if (!p || p.chapter !== startChapterNumber || p.page <= 1) return;
    const scrollToResume = () => {
      const el = pageElRef.current.get(pageKey(startChapterNumber, p.page));
      if (!el) return;
      window.scrollTo({
        top: el.offsetTop,
        behavior: "instant" as ScrollBehavior,
      });
    };
    scrollToResume();
    requestAnimationFrame(scrollToResume);
  }, [mangaSlug, startChapterNumber]);

  useEffect(() => {
    let rafId = 0;
    const tick = () => {
      rafId = 0;
      const y = window.scrollY;
      if (!hiddenByScrollRef.current && y > 50) {
        hiddenByScrollRef.current = true;
        setShowUI(false);
      }
      // Walk only the pages currently inside the 1200px viewport margin
      // (maintained by the observer) and pick the one with the greatest
      // offsetTop still above y+80 — that's the topmost visible page.
      let bestCh = currentChapterNum;
      let bestPg = currentPageNum;
      let bestTop = -1;
      for (const { chNum, pNum, el } of intersectingPagesRef.current.values()) {
        const top = el.offsetTop;
        if (top <= y + 80 && top > bestTop) {
          bestTop = top;
          bestCh = chNum;
          bestPg = pNum;
        }
      }
      if (bestCh !== currentChapterNum) setCurrentChapterNum(bestCh);
      if (bestPg !== currentPageNum) setCurrentPageNum(bestPg);
    };
    const onScroll = () => {
      if (rafId) return;
      rafId = requestAnimationFrame(tick);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      window.removeEventListener("scroll", onScroll);
      if (rafId) cancelAnimationFrame(rafId);
    };
  }, [currentChapterNum, currentPageNum]);

  useEffect(() => {
    writeProgress(mangaSlug, {
      chapter: currentChapterNum,
      page: currentPageNum,
    });
  }, [mangaSlug, currentChapterNum, currentPageNum]);

  const router = useRouter();
  const touchMovedRef = useRef(false);
  const singleTapTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const lastTapAtRef = useRef(0);

  const onTouchStart = useCallback(() => {
    touchMovedRef.current = false;
  }, []);
  const onTouchMove = useCallback(() => {
    touchMovedRef.current = true;
  }, []);
  const onTap = useCallback(
    (e: React.MouseEvent) => {
      if (touchMovedRef.current) return;
      const now = Date.now();
      const isDoubleTap = now - lastTapAtRef.current < DOUBLE_TAP_MS;
      if (isDoubleTap) {
        if (singleTapTimerRef.current) {
          clearTimeout(singleTapTimerRef.current);
          singleTapTimerRef.current = null;
        }
        lastTapAtRef.current = 0;
        const midX = window.innerWidth / 2;
        if (e.clientX >= midX) {
          if (nextChapter)
            router.push(`/manga/${mangaSlug}/${nextChapter}`, {
              scroll: false,
            });
        } else {
          if (prevChapter)
            router.push(`/manga/${mangaSlug}/${prevChapter}`, {
              scroll: false,
            });
        }
        return;
      }
      lastTapAtRef.current = now;
      singleTapTimerRef.current = setTimeout(() => {
        setShowUI((v) => !v);
        singleTapTimerRef.current = null;
      }, DOUBLE_TAP_MS);
    },
    [router, mangaSlug, nextChapter, prevChapter]
  );

  useEffect(
    () => () => {
      if (singleTapTimerRef.current) clearTimeout(singleTapTimerRef.current);
    },
    []
  );

  useLayoutEffect(() => {
    if (!showDrawer) return;
    const scroll = drawerScrollRef.current;
    const active = drawerActiveRef.current;
    if (!scroll || !active) return;
    const scrollRect = scroll.getBoundingClientRect();
    const activeRect = active.getBoundingClientRect();
    const delta =
      activeRect.top -
      scrollRect.top -
      scroll.clientHeight / 2 +
      active.clientHeight / 2;
    scroll.scrollTop = Math.max(0, scroll.scrollTop + delta);
  }, [showDrawer]);

  const currentChapter =
    chapters.find((c) => c.number === currentChapterNum) ??
    chapters.find((c) => c.number === startChapterNumber);

  const lastChapter = chapters[chapters.length - 1];
  const atEnd =
    currentChapterNum === lastChapter?.number &&
    currentPageNum >= (chapterMetas[currentChapterNum]?.length ?? 0);

  return (
    <div className="min-h-dvh bg-background">
      <div
        className={`sticky top-0 z-50 bg-background backdrop-blur-sm shadow-sm transition-transform duration-300 pt-safe ${
          showUI ? "translate-y-0" : "-translate-y-full"
        }`}
      >
        <div className="flex items-center gap-3 px-4 h-12 max-w-4xl mx-auto">
          <Link
            href={`/manga/${mangaSlug}`}
            className="text-foreground/80 hover:text-foreground transition-colors shrink-0"
            aria-label="Back to manga"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth={2}
              className="w-6 h-6"
            >
              <polyline points="15 18 9 12 15 6" />
            </svg>
          </Link>
          <div className="min-w-0 flex-1">
            <p className="text-foreground text-sm font-medium truncate">
              {mangaTitle}
            </p>
            <p className="text-muted text-xs truncate">
              Ch. {currentChapter?.number} — {currentChapter?.title}
            </p>
          </div>
          <button
            onClick={() => setShowDrawer(true)}
            className="text-foreground/80 hover:text-foreground transition-colors shrink-0"
            aria-label="Chapter list"
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth={2}
              className="w-6 h-6"
            >
              <line x1="3" y1="6" x2="21" y2="6" />
              <line x1="3" y1="12" x2="21" y2="12" />
              <line x1="3" y1="18" x2="21" y2="18" />
            </svg>
          </button>
        </div>
      </div>

      <div
        className="max-w-4xl mx-auto leading-[0] select-none"
        onClick={onTap}
        onTouchStart={onTouchStart}
        onTouchMove={onTouchMove}
        onContextMenu={(e) => e.preventDefault()}
      >
        {loadedChapterNumbers.map((chNum, idx) => {
          const meta = chapterMetas[chNum];
          const chapter = chapters.find((c) => c.number === chNum);
          return (
            <Fragment key={chNum}>
              {idx > 0 && (
                <div className="bg-surface py-4 text-center leading-normal">
                  <p className="text-xs uppercase tracking-wider text-muted">
                    Chapter {chNum}
                  </p>
                  <p className="text-sm font-semibold text-foreground">
                    {chapter?.title}
                  </p>
                </div>
              )}
              {meta.map((p) => {
                const key = pageKey(chNum, p.number);
                const url = images[key];
                const aspect =
                  p.width > 0 && p.height > 0
                    ? `${p.width} / ${p.height}`
                    : "3 / 4";
                return (
                  <div
                    key={key}
                    ref={(el) => setPageRef(key, el)}
                    data-chapter={chNum}
                    data-page={p.number}
                    className="relative leading-[0] w-full"
                    style={{ aspectRatio: aspect }}
                  >
                    {url && (
                      <img
                        src={url}
                        alt={`Page ${p.number}`}
                        className="w-full h-auto block [-webkit-touch-callout:none]"
                        draggable={false}
                      />
                    )}
                  </div>
                );
              })}
            </Fragment>
          );
        })}
      </div>

      {atEnd && (
        <div className="min-h-dvh max-w-4xl mx-auto px-4 flex flex-col items-center justify-center text-center leading-normal gap-4">
          <p className="text-xs uppercase tracking-wider text-muted">
            End of Manga
          </p>
          <p className="text-base font-semibold">{mangaTitle}</p>
          <Link
            href="/"
            className="px-5 py-2 rounded-lg bg-accent text-white text-sm font-semibold transition-colors hover:bg-accent-hover"
          >
            Back to Home
          </Link>
        </div>
      )}

      {showDrawer && (
        <div
          className="fixed inset-0 z-[60]"
          onClick={() => setShowDrawer(false)}
        >
          <div className="absolute inset-0 bg-black/50 backdrop-blur-sm" />
          <div
            className="absolute bottom-0 left-0 right-0 max-h-[75vh] bg-background rounded-t-3xl shadow-2xl overflow-hidden"
            onClick={(e) => e.stopPropagation()}
          >
            <div className="sticky top-0 bg-background z-10 border-b border-border">
              <div className="flex justify-center pt-2.5 pb-1.5">
                <div className="w-10 h-1 rounded-full bg-muted/40" />
              </div>
              <div className="px-5 py-3 flex items-center justify-between">
                <span className="text-foreground text-base font-bold">
                  Chapters
                </span>
                <span className="text-xs text-muted tabular-nums">
                  {chapters.length} total
                </span>
              </div>
            </div>
            <div
              ref={drawerScrollRef}
              className="overflow-y-auto max-h-[calc(75vh-4rem)] pb-safe"
            >
              {chapters.map((ch) => {
                const isActive = ch.number === currentChapterNum;
                return (
                  <Link
                    key={ch.number}
                    ref={isActive ? drawerActiveRef : undefined}
                    href={`/manga/${mangaSlug}/${ch.number}`}
                    scroll={false}
                    className={`flex items-center gap-3 px-5 py-3 text-sm transition-colors ${
                      isActive
                        ? "bg-accent/10"
                        : "hover:bg-surface active:bg-surface-hover"
                    }`}
                    onClick={() => setShowDrawer(false)}
                  >
                    <span
                      className={`font-bold tabular-nums w-10 shrink-0 ${
                        isActive ? "text-accent" : "text-muted"
                      }`}
                    >
                      #{ch.number}
                    </span>
                    <span
                      className={`truncate ${
                        isActive
                          ? "text-accent font-semibold"
                          : "text-foreground"
                      }`}
                    >
                      {ch.title}
                    </span>
                    {isActive && (
                      <span className="ml-auto shrink-0 text-[10px] uppercase tracking-wider font-bold text-accent">
                        Current
                      </span>
                    )}
                  </Link>
                );
              })}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
