import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState,
  type PropsWithChildren,
} from "react";
import { createPortal } from "react-dom";
import { ChevronLeft, ChevronRight, X } from "lucide-react";
import type { Attachment } from "../../../types/post";
import { AttachmentDownloadPill } from "../AttachmentDownloadPill";
import { BubbleImage } from "../BubbleImage";
import { autolink } from "../utils/autolink";

type LightboxState = {
  images: Attachment[];
  index: number;
  caption?: string;
  postId?: string;
} | null;

type Ctx = {
  openLightbox: (
    images: Attachment[],
    startIndex?: number,
    caption?: string,
    postId?: string,
  ) => void;
  closeLightbox: () => void;
};

const LightboxContext = createContext<Ctx | null>(null);

export function useLightbox(): Ctx {
  const ctx = useContext(LightboxContext);
  if (!ctx)
    throw new Error("useLightbox must be used inside ImageLightboxProvider");
  return ctx;
}

export function ImageLightboxProvider({ children }: PropsWithChildren) {
  const [state, setState] = useState<LightboxState>(null);

  const openLightbox = useCallback(
    (
      images: Attachment[],
      startIndex = 0,
      caption?: string,
      postId?: string,
    ) => {
      if (!images.length) return;
      const i = Math.min(Math.max(0, startIndex), images.length - 1);
      setState({ images, index: i, caption, postId });
    },
    [],
  );

  const closeLightbox = useCallback(() => setState(null), []);

  return (
    <LightboxContext.Provider value={{ openLightbox, closeLightbox }}>
      {children}
      {state ? (
        <LightboxView
          images={state.images}
          startIndex={state.index}
          caption={state.caption}
          postId={state.postId}
          onClose={closeLightbox}
        />
      ) : null}
    </LightboxContext.Provider>
  );
}

function Filmstrip({
  images,
  index,
  onSelect,
}: {
  images: Attachment[];
  index: number;
  onSelect: (i: number) => void;
}) {
  const activeRef = useRef<HTMLButtonElement | null>(null);

  useEffect(() => {
    activeRef.current?.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }, [index]);

  return (
    <div
      className="shrink-0 bg-gradient-to-t from-black/90 to-transparent pb-[max(env(safe-area-inset-bottom),0.75rem)] pt-3"
      onClick={(e) => e.stopPropagation()}
    >
      <div className="overflow-x-auto px-4 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
        <div className="mx-auto flex w-fit max-w-[920px] gap-2">
          {images.map((image, i) => {
            const active = i === index;
            return (
              <button
                key={image.id}
                ref={active ? activeRef : null}
                type="button"
                onClick={(e) => {
                  e.stopPropagation();
                  onSelect(i);
                }}
                className={`relative h-14 w-14 shrink-0 overflow-hidden rounded-lg bg-black transition duration-200 md:h-16 md:w-16 ${
                  active
                    ? "opacity-100 ring-2 ring-white"
                    : "opacity-45 hover:opacity-80"
                }`}
                aria-label={`Image ${i + 1}`}
                aria-current={active}
              >
                <BubbleImage
                  src={image.thumbnailUrl ?? image.thumbUrl ?? image.url}
                  fallbackSrc={[image.thumbUrl, image.url]}
                  className="h-full w-full object-cover"
                />
              </button>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function LightboxView({
  images,
  startIndex,
  caption: captionText,
  postId,
  onClose,
}: {
  images: Attachment[];
  startIndex: number;
  caption?: string;
  postId?: string;
  onClose: () => void;
}) {
  const [index, setIndex] = useState(startIndex);
  const [isCaptionVisible, setIsCaptionVisible] = useState(true);
  const touchStartX = useRef<number | null>(null);

  // Clamp at the ends instead of wrapping; the nav arrows / swipe / arrow
  // keys should all behave like a linear gallery, not a carousel.
  const goPrev = useCallback(() => setIndex((i) => Math.max(0, i - 1)), []);
  const goNext = useCallback(
    () => setIndex((i) => Math.min(images.length - 1, i + 1)),
    [images.length],
  );

  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") onClose();
      if (e.key === "ArrowLeft") goPrev();
      if (e.key === "ArrowRight") goNext();
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [goPrev, goNext, onClose]);

  // Lock background scroll while the lightbox is open, then restore the exact
  // scroll position on close — otherwise dismissing the overlay leaves the page
  // scrolled back to the top instead of where the user was reading.
  useEffect(() => {
    const scrollY = window.scrollY;
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = prevOverflow;
      window.scrollTo(0, scrollY);
    };
  }, []);

  useEffect(() => {
    setIsCaptionVisible(true);
  }, [captionText, index]);

  const current = images[index];
  const caption = captionText?.trim();
  const showCaption = !!caption && isCaptionVisible;
  const hasMany = images.length > 1;
  if (!current) return null;

  return createPortal(
    <div
      className="fixed inset-0 z-[100] flex flex-col bg-black/95 backdrop-blur-sm"
      onClick={onClose}
      role="dialog"
      aria-modal="true"
    >
      {/* Top bar: counter + actions */}
      <div
        className="relative z-20 flex shrink-0 items-center justify-between px-4 pb-2 pt-[max(env(safe-area-inset-top),1rem)]"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex h-10 items-center">
          {hasMany ? (
            <span className="rounded-full bg-white/10 px-3 py-1 text-xs font-medium text-white">
              {index + 1} / {images.length}
            </span>
          ) : null}
        </div>
        <div className="flex items-center gap-2">
          {postId ? (
            <AttachmentDownloadPill
              postId={postId}
              attachment={current}
              size="lg"
              className=""
            />
          ) : null}
          <button
            type="button"
            onClick={onClose}
            className="flex h-11 w-11 items-center justify-center rounded-full bg-white/10 text-white shadow-lg ring-1 ring-white/15 backdrop-blur-md transition hover:bg-white/20"
            aria-label="Close"
          >
            <X className="h-6 w-6" />
          </button>
        </div>
      </div>

      {/* Image stage */}
      <div className="relative flex min-h-0 w-full flex-1 items-center justify-center">
        {hasMany && index > 0 ? (
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation();
              goPrev();
            }}
            className="absolute left-2 top-1/2 z-10 flex h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-black/60 text-white shadow-lg ring-1 ring-white/25 transition hover:bg-black/80 md:left-6"
            aria-label="Previous"
          >
            <ChevronLeft className="h-6 w-6" />
          </button>
        ) : null}
        {hasMany && index < images.length - 1 ? (
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation();
              goNext();
            }}
            className="absolute right-2 top-1/2 z-10 flex h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full bg-black/60 text-white shadow-lg ring-1 ring-white/25 transition hover:bg-black/80 md:right-6"
            aria-label="Next"
          >
            <ChevronRight className="h-6 w-6" />
          </button>
        ) : null}

        <div
          className="flex h-full w-full items-center justify-center px-4 py-2"
          onClick={(e) => {
            e.stopPropagation();
            if (caption) setIsCaptionVisible((visible) => !visible);
          }}
          onTouchStart={(e) => {
            touchStartX.current = e.touches[0].clientX;
          }}
          onTouchEnd={(e) => {
            if (touchStartX.current == null) return;
            const dx = e.changedTouches[0].clientX - touchStartX.current;
            if (Math.abs(dx) > 40) {
              if (dx > 0) goPrev();
              else goNext();
            }
            touchStartX.current = null;
          }}
        >
          {/* No select-none / touch-callout:none here so iOS Safari's native
              long-press menu ("Save in Photos") works on the full-size image. */}
          <img
            src={current.url}
            alt={current.filename}
            className="max-h-full max-w-full object-contain [-webkit-touch-callout:default]"
          />
        </div>

        {showCaption ? (
          <div
            className="absolute inset-x-0 bottom-0 z-20 bg-gradient-to-t from-black via-black/90 to-black/70 px-4 py-4 text-sm leading-snug text-white sm:px-6"
            onClick={(e) => e.stopPropagation()}
          >
            <div className="message-stream-copyable-text mx-auto max-h-[32vh] max-w-[920px] overflow-y-auto whitespace-pre-wrap break-words [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
              {autolink(caption)}
            </div>
          </div>
        ) : null}
      </div>

      {/* Bottom thumbnail filmstrip */}
      {hasMany ? (
        <Filmstrip images={images} index={index} onSelect={setIndex} />
      ) : null}
    </div>,
    document.body,
  );
}
