import { type RefObject, useCallback, useEffect, useLayoutEffect, useRef } from "react";
import { MAGNIFIER_SIZE, ZOOM_SCALE } from "./constants";
import { computeMagnifierGeometry, findPageAtPoint, findPageById } from "./magnifierUtils";

type UseMagnifierOptions = {
  enabled: boolean;
  /** مرجع مختصات برای موقعیت‌دهی لنز (معمولاً ناحیهٔ اطراف کتاب) */
  containerRef: RefObject<HTMLElement | null>;
  /** ناحیهٔ تعامل و جستجوی صفحات (wrapper کتاب) */
  interactionRef: RefObject<HTMLElement | null>;
  overlayRef: RefObject<HTMLElement | null>;
  lensRef: RefObject<HTMLElement | null>;
  /** لایهٔ داخل لنز که محتوای صفحه (BookPage با مقیاس بزرگ) داخلش رندر می‌شود */
  mirrorRef: RefObject<HTMLElement | null>;
  /** در حین انیمیشن ورق‌خوردن true است — hit-test و به‌روزرسانی لنز متوقف می‌شود */
  isFlippingRef: RefObject<boolean>;
  /** در حالت تک‌صفحه (موبایل) همیشه همین صفحه بزرگ‌نمایی می‌شود، نه نتیجهٔ hit-test */
  lockedPageId?: number | null;
  /** جلوگیری از رسیدن touch به page-flip (کتابخانه تنظیمات را بعد از mount به‌روز نمی‌کند) */
  blockFlipTouch?: boolean;
  /** با تغییر آن (ورق خوردن) وضعیت فعلی ریست می‌شود */
  pageIndex?: number;
  /** وقتی صفحهٔ زیر ماوس عوض شود صدا زده می‌شود تا محتوای مناسب رندر شود */
  onActivePageChange: (pageId: number | null) => void;
};

export function useMagnifier({
  enabled,
  containerRef,
  interactionRef,
  overlayRef,
  lensRef,
  mirrorRef,
  isFlippingRef,
  lockedPageId = null,
  blockFlipTouch = false,
  pageIndex,
  onActivePageChange,
}: UseMagnifierOptions) {
  const rafIdRef = useRef<number | null>(null);
  const pendingPointRef = useRef<{ x: number; y: number } | null>(null);
  const lastPageIdRef = useRef<number | null>(null);
  const touchActiveRef = useRef(false);
  const lockedPageIdRef = useRef(lockedPageId);

  lockedPageIdRef.current = lockedPageId;

  const hideLens = useCallback(() => {
    const lens = lensRef.current;
    if (lens) {
      lens.style.visibility = "hidden";
    }
  }, [lensRef]);

  const resetActivePage = useCallback(() => {
    if (lastPageIdRef.current !== null) {
      lastPageIdRef.current = null;
      onActivePageChange(null);
    }
  }, [onActivePageChange]);

  const applyGeometryRef = useRef<(clientX: number, clientY: number) => void>(() => {});

  useLayoutEffect(() => {
    applyGeometryRef.current = (clientX: number, clientY: number) => {
      if (isFlippingRef.current) {
        hideLens();
        resetActivePage();
        return;
      }

      const container = containerRef.current;
      const interaction = interactionRef.current;
      const lens = lensRef.current;
      if (!container || !interaction || !lens) return;

      const lockedId = lockedPageIdRef.current;
      const page =
        lockedId != null
          ? findPageById(interaction, lockedId)
          : findPageAtPoint(interaction, clientX, clientY, overlayRef.current);

      if (!page) {
        hideLens();
        resetActivePage();
        return;
      }

      const geometry = computeMagnifierGeometry(clientX, clientY, page, container, MAGNIFIER_SIZE);
      if (!geometry) {
        hideLens();
        return;
      }

      const pageIdAttr = page.dataset.pageId;
      const pageId = pageIdAttr != null ? Number(pageIdAttr) : null;
      if (pageId !== lastPageIdRef.current) {
        lastPageIdRef.current = pageId;
        onActivePageChange(pageId);
      }

      const half = MAGNIFIER_SIZE / 2;
      const { pageOffsetX, pageOffsetY, localX, localY, clampedX, clampedY } = geometry;

      lens.style.visibility = "visible";
      lens.style.transform = `translate3d(${pageOffsetX + clampedX - half}px, ${pageOffsetY + clampedY - half}px, 0)`;

      const mirror = mirrorRef.current;
      if (mirror) {
        mirror.style.transform = `translate3d(${half - localX * ZOOM_SCALE}px, ${half - localY * ZOOM_SCALE}px, 0)`;
      }
    };
  });

  // با ورق خوردن، وضعیت ذره‌بین کاملاً ریست می‌شود
  useEffect(() => {
    if (!enabled) return;
    pendingPointRef.current = null;
    touchActiveRef.current = false;
    hideLens();
    resetActivePage();
    lastPageIdRef.current = null;
  }, [pageIndex, enabled, hideLens, resetActivePage]);

  useLayoutEffect(() => {
    if (!enabled) {
      pendingPointRef.current = null;
      touchActiveRef.current = false;
      if (rafIdRef.current != null) {
        cancelAnimationFrame(rafIdRef.current);
        rafIdRef.current = null;
      }
      hideLens();
      resetActivePage();
      return;
    }

    const container = containerRef.current;
    const interaction = interactionRef.current;
    if (!container || !interaction) return;

    const flushPending = () => {
      rafIdRef.current = null;
      const point = pendingPointRef.current;
      if (!point) return;
      applyGeometryRef.current(point.x, point.y);
    };

    const scheduleUpdate = (clientX: number, clientY: number) => {
      if (isFlippingRef.current) {
        hideLens();
        resetActivePage();
        return;
      }

      pendingPointRef.current = { x: clientX, y: clientY };
      if (rafIdRef.current == null) {
        rafIdRef.current = requestAnimationFrame(flushPending);
      }
    };

    const isTouchLike = (event: PointerEvent) =>
      event.pointerType === "touch" || event.pointerType === "pen";

    const handlePointerMove = (event: PointerEvent) => {
      if (isTouchLike(event)) {
        if (!touchActiveRef.current) return;
        event.preventDefault();
        event.stopPropagation();
      }
      scheduleUpdate(event.clientX, event.clientY);
    };

    const handlePointerDown = (event: PointerEvent) => {
      if (!isTouchLike(event)) return;
      event.preventDefault();
      event.stopPropagation();
      touchActiveRef.current = true;
      scheduleUpdate(event.clientX, event.clientY);
    };

    const handlePointerUpOrCancel = (event: PointerEvent) => {
      if (!isTouchLike(event)) return;
      event.stopPropagation();
      touchActiveRef.current = false;
      pendingPointRef.current = null;
      hideLens();
      resetActivePage();
    };

    const handlePointerLeave = (event: PointerEvent) => {
      if (isTouchLike(event)) return;
      pendingPointRef.current = null;
      hideLens();
      resetActivePage();
    };

    const listenerOptions = { capture: true, passive: false } as const;

    interaction.addEventListener("pointermove", handlePointerMove, listenerOptions);
    interaction.addEventListener("pointerdown", handlePointerDown, listenerOptions);
    interaction.addEventListener("pointerup", handlePointerUpOrCancel, listenerOptions);
    interaction.addEventListener("pointercancel", handlePointerUpOrCancel, listenerOptions);
    interaction.addEventListener("pointerleave", handlePointerLeave);

    const blockTouchEvent = (event: TouchEvent) => {
      event.preventDefault();
      event.stopPropagation();
    };
    const touchBlockOptions = { capture: true, passive: false } as const;

    if (blockFlipTouch) {
      interaction.addEventListener("touchstart", blockTouchEvent, touchBlockOptions);
      interaction.addEventListener("touchmove", blockTouchEvent, touchBlockOptions);
      interaction.addEventListener("touchend", blockTouchEvent, touchBlockOptions);
      interaction.addEventListener("touchcancel", blockTouchEvent, touchBlockOptions);
    }

    return () => {
      if (blockFlipTouch) {
        interaction.removeEventListener("touchstart", blockTouchEvent, touchBlockOptions);
        interaction.removeEventListener("touchmove", blockTouchEvent, touchBlockOptions);
        interaction.removeEventListener("touchend", blockTouchEvent, touchBlockOptions);
        interaction.removeEventListener("touchcancel", blockTouchEvent, touchBlockOptions);
      }
      interaction.removeEventListener("pointermove", handlePointerMove, listenerOptions);
      interaction.removeEventListener("pointerdown", handlePointerDown, listenerOptions);
      interaction.removeEventListener("pointerup", handlePointerUpOrCancel, listenerOptions);
      interaction.removeEventListener("pointercancel", handlePointerUpOrCancel, listenerOptions);
      interaction.removeEventListener("pointerleave", handlePointerLeave);
      if (rafIdRef.current != null) {
        cancelAnimationFrame(rafIdRef.current);
        rafIdRef.current = null;
      }
      touchActiveRef.current = false;
      hideLens();
      resetActivePage();
    };
  }, [
    enabled,
    containerRef,
    interactionRef,
    overlayRef,
    lensRef,
    mirrorRef,
    isFlippingRef,
    hideLens,
    resetActivePage,
    onActivePageChange,
    blockFlipTouch,
  ]);
}
