// Lightbox — backdrop click-to-nav, looping, cursor variants, layout variants,
// fade in/out, slow-load spinner, adjacent-image preloading
const { useState: useStateL, useEffect: useEffectL, useRef: useRefL } = React;

const LB_EXIT_MS = 260;
const LB_SLOW_MS = 175; // show spinner only after this delay

// Module-level cache of URLs that have successfully loaded in this session.
// Survives component unmount/remount — reopening the lightbox re-uses it.
const LB_LOADED = new Set();
// Strong refs to preloaded images so the browser doesn't evict them.
const LB_PRELOAD_REFS = new Map();

function lbPreload(src) {
  if (LB_LOADED.has(src) || LB_PRELOAD_REFS.has(src)) return;
  const img = new Image();
  img.onload = () => LB_LOADED.add(src);
  img.onerror = () => LB_LOADED.add(src); // count errors as "resolved" too
  img.src = src;
  LB_PRELOAD_REFS.set(src, img);
}

function Lightbox({ state, onClose }) {
  const [i, setI] = useStateL(0);
  const [cursorSide, setCursorSide] = useStateL(null); // 'prev' | 'next' | null
  const [cursorPos, setCursorPos] = useStateL({ x: -100, y: -100 });
  const [visible, setVisible] = useStateL(false);
  const [snap, setSnap] = useStateL(null); // retained state during exit
  const [loaded, setLoaded] = useStateL(false);
  const [slowLoad, setSlowLoad] = useStateL(false);
  const exitTimer = useRefL(null);
  const slowTimer = useRefL(null);

  // Open / close — keep mounted during exit fade
  useEffectL(() => {
    if (state) {
      clearTimeout(exitTimer.current);
      setSnap(state);
      setI(state.index);
      setVisible(false);
      // Aggressively preload every image in this gallery in the background.
      state.items.forEach((it) => lbPreload(it.src));
      // Double rAF so initial opacity:0 gets a paint before flipping to 1
      requestAnimationFrame(() => {
        requestAnimationFrame(() => setVisible(true));
      });
    } else if (snap) {
      setVisible(false);
      exitTimer.current = setTimeout(() => setSnap(null), LB_EXIT_MS);
    }
    return () => clearTimeout(exitTimer.current);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [state]);

  // When index or snap changes: reset load state, schedule slow-load spinner,
  // instantly mark loaded if image is already in our cache, and preload neighbours.
  useEffectL(() => {
    if (!snap) return;
    const src = snap.items[i].src;

    // Already loaded this session? Skip the loading state entirely.
    if (LB_LOADED.has(src)) {
      setLoaded(true);
      setSlowLoad(false);
      clearTimeout(slowTimer.current);
    } else {
      setLoaded(false);
      setSlowLoad(false);
      clearTimeout(slowTimer.current);
      slowTimer.current = setTimeout(() => setSlowLoad(true), LB_SLOW_MS);

      // Probe for browser-level cache hit (e.g. strip thumbnail already fetched).
      const probe = new Image();
      probe.src = src;
      if (probe.complete && probe.naturalWidth > 0) {
        LB_LOADED.add(src);
        clearTimeout(slowTimer.current);
        setLoaded(true);
        setSlowLoad(false);
      }
    }

    // preload prev + next (and keep refs so browser can't evict)
    const n = snap.items.length;
    [(i + 1) % n, (i - 1 + n) % n].forEach((k) => lbPreload(snap.items[k].src));

    return () => clearTimeout(slowTimer.current);
  }, [snap, i]);

  // Keyboard controls
  useEffectL(() => {
    if (!snap || !visible) return;
    const n = snap.items.length;
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      if (e.key === "ArrowRight") setI((x) => (x + 1) % n);
      if (e.key === "ArrowLeft") setI((x) => (x - 1 + n) % n);
    };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
    };
  }, [snap, visible, onClose]);

  if (!snap) return null;
  const it = snap.items[i];
  const pad = (v) => String(v).padStart(2, "0");
  const n = snap.items.length;

  const isInteractive = (target) =>
    !!target.closest(
      ".lightbox__stage img, .lightbox__caption, .lightbox__close, .lightbox__nav, .lightbox__counter"
    );

  const onBackdropMove = (e) => {
    if (isInteractive(e.target)) {
      if (cursorSide !== null) setCursorSide(null);
      return;
    }
    setCursorPos({ x: e.clientX, y: e.clientY });
    const half = window.innerWidth / 2;
    setCursorSide(e.clientX < half ? "prev" : "next");
  };

  const onBackdropClick = (e) => {
    if (isInteractive(e.target)) return;
    const half = window.innerWidth / 2;
    if (e.clientX < half) setI((x) => (x - 1 + n) % n);
    else setI((x) => (x + 1) % n);
  };

  const onImgLoad = () => {
    LB_LOADED.add(it.src);
    clearTimeout(slowTimer.current);
    setLoaded(true);
    setSlowLoad(false);
  };

  return (
    <div
      className={"lightbox" + (visible ? " is-visible" : "")}
      onMouseMove={onBackdropMove}
      onMouseLeave={() => setCursorSide(null)}
      onClick={onBackdropClick}
      data-cursor={cursorSide || "none"}
    >
      <span className="lightbox__counter">{snap.label} · {pad(i + 1)} / {pad(n)}</span>

      <button
        className="lightbox__close"
        onClick={(e) => { e.stopPropagation(); onClose(); }}
        aria-label="cerrar"
      >
        <span className="lightbox__close-label">CERRAR</span>
        <span className="lightbox__close-glyph" aria-hidden="true">✕</span>
        <span className="lightbox__close-esc">ESC</span>
      </button>

      <div
        className={
          "lightbox__stage" +
          (loaded ? " is-loaded" : " is-loading") +
          (slowLoad && !loaded ? " is-slow" : "")
        }
      >
        <button
          className="lightbox__nav lightbox__nav--prev"
          onClick={(e) => { e.stopPropagation(); setI((x) => (x - 1 + n) % n); }}
          aria-label="anterior"
        >←</button>

        {/* Spinner only appears after LB_SLOW_MS of unresolved loading */}
        <div className="lightbox__spinner" aria-hidden="true">
          <div className="lightbox__spinner-ring"></div>
          <div className="lightbox__spinner-label mono">CARGANDO</div>
        </div>

        <img
          key={it.src /* force remount so onLoad fires for every new src */}
          src={it.src}
          alt={it.cap}
          draggable="false"
          onClick={(e) => e.stopPropagation()}
          onLoad={onImgLoad}
          onError={onImgLoad}
        />

        <button
          className="lightbox__nav lightbox__nav--next"
          onClick={(e) => { e.stopPropagation(); setI((x) => (x + 1) % n); }}
          aria-label="siguiente"
        >→</button>
      </div>

      <div className="lightbox__caption" onClick={(e) => e.stopPropagation()}>
        <div className="lightbox__caption-index mono">{pad(i + 1)} / {pad(n)}</div>
        <div className="lightbox__caption-title serif">{it.cap}</div>
        <div className="lightbox__caption-meta mono">
          <span>{it.loc}</span>
          <span className="lightbox__caption-sep">·</span>
          <span>MLZ_{pad(i + 1)}_{snap.label.replace(/\s/g, "")}</span>
        </div>
      </div>

      {cursorSide && (
        <div
          className="lightbox__cursor"
          style={{ transform: `translate(${cursorPos.x}px, ${cursorPos.y}px)` }}
          data-side={cursorSide}
        >
          <span className="lightbox__cursor-arrow">{cursorSide === "prev" ? "←" : "→"}</span>
          <span className="lightbox__cursor-label mono">{cursorSide === "prev" ? "ANT" : "SIG"}</span>
        </div>
      )}
    </div>
  );
}

window.Lightbox = Lightbox;
