// Shared utilities + small primitives.
// Exports onto window so other Babel files can use them.

const { useState, useEffect, useRef, useCallback, useMemo } = React;

// IntersectionObserver-driven reveal.
function useInView(opts = { threshold: 0.15, once: true }) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            setInView(true);
            if (opts.once !== false) io.unobserve(e.target);
          } else if (opts.once === false) {
            setInView(false);
          }
        });
      },
      { threshold: opts.threshold ?? 0.15, rootMargin: opts.rootMargin || "0px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, [opts.threshold, opts.rootMargin, opts.once]);
  return [ref, inView];
}

// Number that counts up when its container becomes visible.
function CountUp({ to, duration = 1400, decimals = 0, format = (v) => v }) {
  const [val, setVal] = useState(0);
  const [ref, inView] = useInView({ threshold: 0.4 });
  useEffect(() => {
    if (!inView) return;
    let start = null;
    const step = (t) => {
      if (start === null) start = t;
      const p = Math.min(1, (t - start) / duration);
      // ease-out cubic
      const eased = 1 - Math.pow(1 - p, 3);
      const v = eased * to;
      setVal(decimals ? +v.toFixed(decimals) : Math.round(v));
      if (p < 1) requestAnimationFrame(step);
    };
    const id = requestAnimationFrame(step);
    return () => cancelAnimationFrame(id);
  }, [inView, to, duration, decimals]);
  return <span ref={ref}>{format(val)}</span>;
}

// Reveal wrapper.
function Reveal({ as: As = "div", stagger = false, className = "", children, ...rest }) {
  const [ref, inView] = useInView({ threshold: 0.15 });
  const cls = (stagger ? "reveal-stagger " : "reveal ") + (inView ? "in " : "") + className;
  return (
    <As ref={ref} className={cls} {...rest}>
      {children}
    </As>
  );
}

// Photo block — renders a real image when `src` is provided, else a labelled placeholder.
function Photo({ label, src, alt, ratio = "3 / 2", style = {}, position = "center", className = "" }) {
  if (src) {
    return (
      <div
        className={"ph ph--img " + className}
        style={{ aspectRatio: ratio, width: "100%", ...style }}
      >
        <img
          src={src}
          alt={alt || label || ""}
          loading="lazy"
          style={{
            width: "100%",
            height: "100%",
            objectFit: "cover",
            objectPosition: position,
            display: "block",
          }}
        />
      </div>
    );
  }
  return (
    <div
      className={"ph " + className}
      style={{ aspectRatio: ratio, width: "100%", ...style }}
      aria-label={`Photo placeholder: ${label}`}
    >
      <div className="ph-label">{label}</div>
    </div>
  );
}

// Shared lightbox hook + component.
// Pass an array of photos: { src, alt, caption?, sub? }
// Returns { openIdx, open, close, Lightbox }.
function usePhotoLightbox(photos) {
  const [openIdx, setOpenIdx] = useState(-1);
  const open = useCallback((i) => setOpenIdx(i), []);
  const close = useCallback(() => setOpenIdx(-1), []);
  const next = useCallback(() => setOpenIdx((i) => (i + 1) % photos.length), [photos.length]);
  const prev = useCallback(() => setOpenIdx((i) => (i - 1 + photos.length) % photos.length), [photos.length]);

  useEffect(() => {
    if (openIdx < 0) return;
    const onKey = (e) => {
      if (e.key === "Escape") close();
      else if (e.key === "ArrowRight") next();
      else if (e.key === "ArrowLeft") prev();
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [openIdx, next, prev, close]);

  function Lightbox() {
    if (openIdx < 0) return null;
    const p = photos[openIdx];
    return (
      <div className="gal-lightbox" onClick={close}>
        <div className="gal-lightbox-inner" onClick={(e) => e.stopPropagation()}>
          <div className="gal-lightbox-imgwrap">
            <img src={p.src} alt={p.alt} className="gal-lightbox-img" />
          </div>
          <div className="gal-lightbox-meta">
            <span className="mono-up">
              {p.caption || `${String(openIdx + 1).padStart(2, "0")} / ${String(photos.length).padStart(2, "0")}`}
            </span>
            <span className="mono-up">{p.sub || p.alt}</span>
          </div>
        </div>
        <button className="gal-lightbox-close" onClick={close} aria-label="Close">×</button>
        <button className="gal-arrow gal-arrow--l"
          onClick={(e) => { e.stopPropagation(); prev(); }}
          aria-label="Previous">‹</button>
        <button className="gal-arrow gal-arrow--r"
          onClick={(e) => { e.stopPropagation(); next(); }}
          aria-label="Next">›</button>
      </div>
    );
  }

  return { openIdx, open, close, Lightbox };
}

Object.assign(window, { useInView, CountUp, Reveal, Photo, usePhotoLightbox });
