// Horizontal scroll gallery strip — pointer-capture drag with inertia,
// rAF-throttled progress, click opens lightbox, cursor-preview handled by parent.
const { useState: useStateG, useRef: useRefG, useEffect: useEffectG } = React;
function GalleryStrip({ id, label, items, onOpen, onHover, onLeave }) {
  const trackRef = useRefG(null);
  const dragRef = useRefG({
    active: false,
    startX: 0,
    startLeft: 0,
    moved: 0,
    pointerId: null,
    lastX: 0,
    lastT: 0,
    velocity: 0,
  });
  const inertiaRef = useRefG(0);
  const rafRef = useRefG(0);
  const [progress, setProgress] = useStateG(0);

  // rAF-throttled progress
  useEffectG(() => {
    const el = trackRef.current;
    if (!el) return;
    const update = () => {
      rafRef.current = 0;
      const max = el.scrollWidth - el.clientWidth;
      setProgress(max > 0 ? el.scrollLeft / max : 0);
    };
    const schedule = () => {
      if (!rafRef.current) rafRef.current = requestAnimationFrame(update);
    };
    update();
    el.addEventListener("scroll", schedule, { passive: true });
    return () => {
      el.removeEventListener("scroll", schedule);
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
    };
  }, []);

  // Drag + inertia
  useEffectG(() => {
    const el = trackRef.current;
    if (!el) return;

    const cancelInertia = () => {
      if (inertiaRef.current) {
        cancelAnimationFrame(inertiaRef.current);
        inertiaRef.current = 0;
      }
    };

    const startInertia = () => {
      const d = dragRef.current;
      let v = d.velocity; // px per ms
      if (Math.abs(v) < 0.05) return;
      const friction = 0.94; // per frame (~16ms)
      const step = () => {
        // at 60fps each frame is ~16ms → translate v(px/ms) to px/frame
        const delta = v * 16;
        el.scrollLeft -= delta;
        v *= friction;
        if (Math.abs(v) > 0.02 &&
            el.scrollLeft > 0 &&
            el.scrollLeft < el.scrollWidth - el.clientWidth) {
          inertiaRef.current = requestAnimationFrame(step);
        } else {
          inertiaRef.current = 0;
        }
      };
      inertiaRef.current = requestAnimationFrame(step);
    };

    const onPointerDown = (e) => {
      if (e.button !== undefined && e.button !== 0) return;
      // Skip JS drag for touch / pen — let the browser handle scrolling natively
      // so vertical page scroll isn't hijacked. Native overflow-x:auto still
      // gives smooth horizontal scrolling on touch devices.
      if (e.pointerType && e.pointerType !== "mouse") return;
      cancelInertia();
      const now = performance.now();
      dragRef.current = {
        active: true,
        startX: e.clientX,
        startLeft: el.scrollLeft,
        moved: 0,
        pointerId: e.pointerId,
        lastX: e.clientX,
        lastT: now,
        velocity: 0,
      };
      el.classList.add("strip--dragging");
      try { el.setPointerCapture(e.pointerId); } catch (_) {}
    };

    const onPointerMove = (e) => {
      const d = dragRef.current;
      if (!d.active) return;
      const dx = e.clientX - d.startX;
      d.moved = Math.max(d.moved, Math.abs(dx));
      el.scrollLeft = d.startLeft - dx;
      // track velocity
      const now = performance.now();
      const dt = Math.max(1, now - d.lastT);
      // velocity = px/ms, smoothed
      const instant = (e.clientX - d.lastX) / dt;
      d.velocity = d.velocity * 0.6 + instant * 0.4;
      d.lastX = e.clientX;
      d.lastT = now;
    };

    const endDrag = () => {
      const d = dragRef.current;
      if (!d.active) return;
      d.active = false;
      el.classList.remove("strip--dragging");
      try { if (d.pointerId !== null) el.releasePointerCapture(d.pointerId); } catch (_) {}
      startInertia();
    };

    const onWheel = () => cancelInertia();

    el.addEventListener("pointerdown", onPointerDown);
    el.addEventListener("pointermove", onPointerMove);
    el.addEventListener("pointerup", endDrag);
    el.addEventListener("pointercancel", endDrag);
    el.addEventListener("lostpointercapture", endDrag);
    el.addEventListener("wheel", onWheel, { passive: true });
    const prevent = (e) => e.preventDefault();
    el.addEventListener("dragstart", prevent);

    return () => {
      el.removeEventListener("pointerdown", onPointerDown);
      el.removeEventListener("pointermove", onPointerMove);
      el.removeEventListener("pointerup", endDrag);
      el.removeEventListener("pointercancel", endDrag);
      el.removeEventListener("lostpointercapture", endDrag);
      el.removeEventListener("wheel", onWheel);
      el.removeEventListener("dragstart", prevent);
      cancelInertia();
    };
  }, []);

  const offsets = [28, 0, 72, 14, 44, 0, 58, 22, 36, 8, 66, 14, 50];
  const heights = [150, 195, 120, 225, 165, 180, 143, 210, 173, 150, 195, 165, 135];

  const pad = (n) => String(n).padStart(2, "0");

  return (
    <section className="strip" id={id} data-screen-label={`${label}`}>
      <header className="strip__head">
        <div className="strip__meta mono">
          <span className="strip__label">{label}</span>
        </div>
        <div className="strip__progress mono muted">
          <div className="strip__progress-track">
            <div className="strip__progress-fill" style={{ transform: `scaleX(${progress})` }} />
          </div>
          <span>{pad(Math.round(progress * (items.length - 1)) + 1)} / {pad(items.length)}</span>
        </div>
      </header>

      <div
        ref={trackRef}
        className="strip__track"
        onMouseLeave={() => onLeave && onLeave()}
      >
        <div className="strip__inner">
          {items.map((it, i) => {
            const top = offsets[i % offsets.length];
            const h = heights[i % heights.length];
            const isText = it.type === "text";

            // Text tiles render as editorial pull-quotes; they aren't openable
            // in the lightbox and are skipped from its navigation. They use an
            // explicit pixel `w` for width and let height be content-driven.
            if (isText) {
              const textWidth = it.w || 360;
              return (
                <figure
                  key={i}
                  className={"tile tile--text" + (it.tone ? ` tile--text-${it.tone}` : "")}
                  style={{ marginTop: `${top}px`, width: `${textWidth}px` }}
                  onMouseEnter={() => onLeave && onLeave()}
                >
                  {it.kicker && (
                    <span className="tile__text-kicker mono">{it.kicker}</span>
                  )}
                  <p className={"tile__text-body" + (it.font === "mono" ? " mono" : " serif")}>
                    {it.text}
                  </p>
                  {it.attribution && (
                    <span className="tile__text-attr mono">— {it.attribution}</span>
                  )}
                </figure>
              );
            }

            const aspect = it.w / it.h;
            const w = Math.round(h * aspect);

            // Pre-compute the lightbox-only items list and the click target's
            // position within it. Text tiles are filtered out so the lightbox
            // never has to render one.
            const lbItems = items.filter((x) => x.type !== "text");
            const lbIndex = lbItems.indexOf(it);

            return (
              <figure
                key={i}
                className="tile"
                style={{ marginTop: `${top}px`, width: `${w}px`, height: `${h}px` }}

                onPointerDown={(e) => {
                  // Defer the decision to pointerup: only open the lightbox if
                  // the pointer barely moved between down and up (a click/tap,
                  // not a drag/scroll). Works for both mouse and touch.
                  // Listen on document so pointer-capture on the strip (mouse
                  // drag) doesn't swallow the up event.
                  const startX = e.clientX;
                  const startY = e.clientY;
                  const onUp = (ev) => {
                    document.removeEventListener("pointerup", onUp);
                    document.removeEventListener("pointercancel", onUp);
                    if (ev.type === "pointercancel") return;
                    const dx = Math.abs(ev.clientX - startX);
                    const dy = Math.abs(ev.clientY - startY);
                    if (dx < 6 && dy < 6) {
                      onOpen && onOpen({ items: lbItems, index: lbIndex, label });
                    }
                  };
                  document.addEventListener("pointerup", onUp);
                  document.addEventListener("pointercancel", onUp);
                }}
              >
                <Img
                  src={it.src}
                  alt={it.caption?.title || ""}
                  aspectRatio={aspect}
                  fallbackLabel={(it.caption?.title || "IMAGEN").toUpperCase()}
                />
                <figcaption className="tile__cap mono">
                  <span>{pad(i + 1)}</span>
                  <span>{it.caption?.title}</span>
                </figcaption>
              </figure>
            );
          })}
        </div>
      </div>
    </section>
  );
}

window.GalleryStrip = GalleryStrip;
