"use client";

import {
  useCallback,
  useEffect,
  useRef,
  useState,
} from "react";
import type { PointerEvent } from "react";
import { createPortal } from "react-dom";
import Image from "next/image";
import { Minimize, ZoomIn, ZoomOut, X } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
import type { Swiper as SwiperInstance } from "swiper";
import "swiper/css";

import { withCDN } from "@/src/lib/utils";

interface ImageLightboxProps {
  images: string[];
  index: number | null;
  onClose: () => void;
  className?: string;
}

interface ImagePosition {
  x: number;
  y: number;
}

const MIN_ZOOM = 1;
const MAX_ZOOM = 4;
const ZOOM_STEP = 0.5;

export default function ImageLightbox({
  images,
  index,
  onClose,
  className = "",
}: ImageLightboxProps) {
  const [mounted, setMounted] = useState(false);
  const [zoomLevel, setZoomLevel] = useState(MIN_ZOOM);
  const [imagePosition, setImagePosition] = useState<ImagePosition>({
    x: 0,
    y: 0,
  });

  const swiperRef = useRef<SwiperInstance | null>(null);
  const activeImageRef = useRef<HTMLImageElement | null>(null);

  const dragStartRef = useRef({
    pointerX: 0,
    pointerY: 0,
    imageX: 0,
    imageY: 0,
  });

  const isDraggingRef = useRef(false);
  const hasDraggedRef = useRef(false);
  const suppressClickRef = useRef(false);

  const isOpen = index !== null && images.length > 0;

  useEffect(() => {
    setMounted(true);
  }, []);

  const resetZoom = useCallback(() => {
    setZoomLevel(MIN_ZOOM);
    setImagePosition({ x: 0, y: 0 });

    isDraggingRef.current = false;
    hasDraggedRef.current = false;
    suppressClickRef.current = false;
  }, []);

  const getActiveImage = useCallback(() => {
    const swiper = swiperRef.current;

    if (!swiper || swiper.destroyed || !swiper.slides) {
      return null;
    }

    const activeSlide = swiper.slides[swiper.activeIndex];

    return activeSlide?.querySelector<HTMLImageElement>("img") ?? null;
  }, []);

  const clampPosition = useCallback(
    (position: ImagePosition, zoom: number): ImagePosition => {
      const image = activeImageRef.current ?? getActiveImage();

      if (!image || zoom <= MIN_ZOOM) {
        return { x: 0, y: 0 };
      }

      const maxX = Math.max(0, (image.offsetWidth * (zoom - 1)) / 2);
      const maxY = Math.max(0, (image.offsetHeight * (zoom - 1)) / 2);

      return {
        x: Math.max(-maxX, Math.min(maxX, position.x)),
        y: Math.max(-maxY, Math.min(maxY, position.y)),
      };
    },
    [getActiveImage]
  );

  const applyZoom = useCallback(
    (nextZoom: number) => {
      const safeZoom = Math.max(
        MIN_ZOOM,
        Math.min(MAX_ZOOM, nextZoom)
      );

      if (safeZoom === MIN_ZOOM) {
        setZoomLevel(MIN_ZOOM);
        setImagePosition({ x: 0, y: 0 });
        return;
      }

      setImagePosition((current) => clampPosition(current, safeZoom));
      setZoomLevel(safeZoom);
    },
    [clampPosition]
  );

  const handleZoomIn = useCallback(() => {
    applyZoom(zoomLevel + ZOOM_STEP);
  }, [applyZoom, zoomLevel]);

  const handleZoomOut = useCallback(() => {
    applyZoom(zoomLevel - ZOOM_STEP);
  }, [applyZoom, zoomLevel]);

  useEffect(() => {
    const swiper = swiperRef.current;

    if (!swiper || swiper.destroyed) return;

    swiper.allowTouchMove = zoomLevel <= MIN_ZOOM;
  }, [zoomLevel]);

  const handleImageClick = useCallback(() => {
    if (suppressClickRef.current) {
      suppressClickRef.current = false;
      return;
    }

    if (zoomLevel > MIN_ZOOM) {
      resetZoom();
    } else {
      setImagePosition({ x: 0, y: 0 });
      setZoomLevel(2);
    }
  }, [resetZoom, zoomLevel]);

  const handlePointerDown = useCallback(
    (event: PointerEvent<HTMLImageElement>) => {
      if (zoomLevel <= MIN_ZOOM) return;

      event.preventDefault();
      event.stopPropagation();

      isDraggingRef.current = true;
      hasDraggedRef.current = false;

      dragStartRef.current = {
        pointerX: event.clientX,
        pointerY: event.clientY,
        imageX: imagePosition.x,
        imageY: imagePosition.y,
      };

      try {
        event.currentTarget.setPointerCapture(event.pointerId);
      } catch {
        // Pointer capture may be unavailable.
      }
    },
    [imagePosition, zoomLevel]
  );

  const handlePointerMove = useCallback(
    (event: PointerEvent<HTMLImageElement>) => {
      if (!isDraggingRef.current || zoomLevel <= MIN_ZOOM) {
        return;
      }

      event.preventDefault();
      event.stopPropagation();

      const deltaX = event.clientX - dragStartRef.current.pointerX;
      const deltaY = event.clientY - dragStartRef.current.pointerY;

      if (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3) {
        hasDraggedRef.current = true;
      }

      const nextPosition = {
        x: dragStartRef.current.imageX + deltaX,
        y: dragStartRef.current.imageY + deltaY,
      };

      setImagePosition(clampPosition(nextPosition, zoomLevel));
    },
    [clampPosition, zoomLevel]
  );

  const finishDragging = useCallback(
    (event: PointerEvent<HTMLImageElement>) => {
      if (!isDraggingRef.current) return;

      isDraggingRef.current = false;

      try {
        if (event.currentTarget.hasPointerCapture(event.pointerId)) {
          event.currentTarget.releasePointerCapture(event.pointerId);
        }
      } catch {
        // Pointer capture may already be released.
      }

      if (hasDraggedRef.current) {
        suppressClickRef.current = true;

        window.setTimeout(() => {
          suppressClickRef.current = false;
        }, 0);
      }
    },
    []
  );

  const handleSlideChange = useCallback(
    (swiper: SwiperInstance) => {
      resetZoom();

      if (swiper.destroyed || !swiper.slides) {
        activeImageRef.current = null;
        return;
      }

      const activeSlide = swiper.slides[swiper.activeIndex];

      activeImageRef.current =
        activeSlide?.querySelector<HTMLImageElement>("img") ?? null;
    },
    [resetZoom]
  );

  const handlePrevious = useCallback(() => {
    resetZoom();

    const swiper = swiperRef.current;

    if (swiper && !swiper.destroyed) {
      swiper.slidePrev();
    }
  }, [resetZoom]);

  const handleNext = useCallback(() => {
    resetZoom();

    const swiper = swiperRef.current;

    if (swiper && !swiper.destroyed) {
      swiper.slideNext();
    }
  }, [resetZoom]);

  useEffect(() => {
    if (!isOpen) return;

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        onClose();
      } else if (event.key === "ArrowLeft") {
        handlePrevious();
      } else if (event.key === "ArrowRight") {
        handleNext();
      } else if (event.key === "+" || event.key === "=") {
        handleZoomIn();
      } else if (event.key === "-") {
        handleZoomOut();
      } else if (event.key === "0") {
        resetZoom();
      }
    };

    window.addEventListener("keydown", handleKeyDown);

    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [
    isOpen,
    onClose,
    handlePrevious,
    handleNext,
    handleZoomIn,
    handleZoomOut,
    resetZoom,
  ]);

  useEffect(() => {
    if (!isOpen) return;

    const previousOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";

    return () => {
      document.body.style.overflow = previousOverflow;
    };
  }, [isOpen]);

  useEffect(() => {
    if (!isOpen || index === null) return;

    resetZoom();

    const swiper = swiperRef.current;

    if (swiper && !swiper.destroyed) {
      if (images.length > 1) {
        swiper.slideToLoop(index, 0);
      } else {
        swiper.slideTo(index, 0);
      }
    }
  }, [index, isOpen, images.length, resetZoom]);

  if (!mounted || !isOpen) return null;

  return createPortal(
    <div
      className={`fixed inset-0 z-[99999] flex items-center justify-center overflow-hidden bg-black ${className}`}
      role="dialog"
      aria-modal="true"
      aria-label="Image lightbox"
    >
      <button
        type="button"
        onClick={onClose}
        aria-label="Close image viewer"
        className="absolute right-4 top-4 z-[100010] flex h-11 w-11 items-center justify-center rounded-full bg-black/60 text-white transition hover:bg-white/20 sm:right-6 sm:top-6"
      >
        <X size={28} />
      </button>

      {images.length > 1 && (
        <button
          type="button"
          onClick={handlePrevious}
          aria-label="Previous image"
          className="absolute left-2 top-1/2 z-[100010] -translate-y-1/2 sm:left-5"
        >
          <Image
            src={withCDN("/arrow-left-white.svg")}
            alt="Previous"
            width={40}
            height={40}
            className="h-5 w-5 md:h-10 md:w-10"
            unoptimized
          />
        </button>
      )}

      {images.length > 1 && (
        <button
          type="button"
          onClick={handleNext}
          aria-label="Next image"
          className="absolute right-2 top-1/2 z-[100010] -translate-y-1/2 sm:right-5"
        >
          <Image
            src={withCDN("/arrow-left-white.svg")}
            alt="Next"
            width={40}
            height={40}
            className="h-5 w-5 rotate-180 md:h-10 md:w-10"
            unoptimized
          />
        </button>
      )}

      {/* Reduced mobile padding gives the image more horizontal room. */}
      <div className="h-full w-full overflow-hidden px-9 sm:px-14 md:px-24 lg:px-32">
        <Swiper
          key={images.join("|")}
          initialSlide={index ?? 0}
          loop={images.length > 1}
          speed={350}
          slidesPerView={1}
          spaceBetween={0}
          allowTouchMove={zoomLevel <= MIN_ZOOM}
          touchStartPreventDefault={false}
          threshold={5}
          onSwiper={(swiper) => {
            swiperRef.current = swiper;
            swiper.allowTouchMove = zoomLevel <= MIN_ZOOM;

            if (index !== null) {
              if (images.length > 1) {
                swiper.slideToLoop(index, 0);
              } else {
                swiper.slideTo(index, 0);
              }
            }

            const activeSlide = swiper.slides?.[swiper.activeIndex];

            activeImageRef.current =
              activeSlide?.querySelector<HTMLImageElement>("img") ?? null;
          }}
          onSlideChange={handleSlideChange}
          className="h-full w-full overflow-hidden"
        >
          {images.map((image, imageIndex) => (
            <SwiperSlide
              key={`${image}-${imageIndex}`}
              className="flex h-full items-center justify-center overflow-hidden"
            >
              <div className="flex h-full w-full items-center justify-center overflow-hidden">
                {/* Shared viewport size for every image; image stays uncropped. */}
                <div
                  className="relative h-[82dvh] w-full max-w-[1600px] shrink-0 overflow-visible lightbox-image-container"
                  style={{
                    transform: `translate(${imagePosition.x}px, ${imagePosition.y}px) scale(${zoomLevel})`,
                    transformOrigin: "center center",
                    transition: isDraggingRef.current
                      ? "none"
                      : "transform 250ms ease",
                    touchAction:
                      zoomLevel > MIN_ZOOM ? "none" : "pan-y",
                  }}
                >
                  <Image
                    src={withCDN(image)}
                    alt={`Lightbox image ${imageIndex + 1}`}
                    width={1920}
                    height={1080}
                    unoptimized
                    draggable={false}
                    onLoad={(event) => {
                      if (
                        event.currentTarget.closest(".swiper-slide-active")
                      ) {
                        activeImageRef.current = event.currentTarget;
                      }
                    }}
                    onClick={handleImageClick}
                    onPointerDown={handlePointerDown}
                    onPointerMove={handlePointerMove}
                    onPointerUp={finishDragging}
                    onPointerCancel={finishDragging}
                    className="absolute inset-0 h-full w-full select-none object-contain"
                    style={{
                      userSelect: "none",
                      WebkitUserSelect: "none",
                      cursor:
                        zoomLevel > MIN_ZOOM
                          ? isDraggingRef.current
                            ? "grabbing"
                            : "grab"
                          : "zoom-in",
                    }}
                  />
                </div>
              </div>
            </SwiperSlide>
          ))}
        </Swiper>
      </div>

      <div className="absolute bottom-5 left-1/2 z-[100010] flex -translate-x-1/2 items-center gap-3 rounded-full bg-black/75 px-4 py-2 text-white shadow-lg">
        <button
          type="button"
          onClick={handleZoomOut}
          disabled={zoomLevel <= MIN_ZOOM}
          aria-label="Zoom out"
          className="flex h-9 w-9 items-center justify-center rounded-full transition hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-40"
        >
          <ZoomOut size={22} />
        </button>

        <span className="min-w-[54px] text-center text-sm font-medium tabular-nums">
          {Math.round(zoomLevel * 100)}%
        </span>

        <button
          type="button"
          onClick={handleZoomIn}
          disabled={zoomLevel >= MAX_ZOOM}
          aria-label="Zoom in"
          className="flex h-9 w-9 items-center justify-center rounded-full transition hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-40"
        >
          <ZoomIn size={22} />
        </button>
      </div>

      <button
        type="button"
        onClick={() => {
          if (zoomLevel > MIN_ZOOM) {
            resetZoom();
          } else {
            onClose();
          }
        }}
        aria-label={zoomLevel > MIN_ZOOM ? "Reset zoom" : "Close image viewer"}
        title={zoomLevel > MIN_ZOOM ? "Reset zoom" : "Close"}
        className="absolute bottom-5 right-4 z-[100010] flex h-11 w-11 items-center justify-center rounded-full bg-black/60 text-white transition hover:bg-white/20 sm:right-6"
      >
        <Minimize size={22} />
      </button>
    </div>,
    document.body
  );
}