// Shared animation components + section primitives
const { motion, useInView, useScroll, useTransform } = window.Motion;
const { useRef, useState, useEffect, useMemo } = React;

// ---- WordsPullUp -------------------------------------------------
function WordsPullUp({ text, className = "", style = {}, delay = 0, showAsterisk = false }) {
  const ref = useRef(null);
  const inView = useInView(ref, { once: true, margin: "-10%" });
  const words = text.split(" ");
  return (
    <span ref={ref} className={className} style={style}>
      {words.map((word, i) => {
        const isLast = i === words.length - 1;
        return (
          <span key={i} className="inline-block overflow-hidden align-bottom relative" style={{ marginRight: "0.22em" }}>
            <motion.span
              className="inline-block"
              initial={{ y: 24, opacity: 0 }}
              animate={inView ? { y: 0, opacity: 1 } : { y: 24, opacity: 0 }}
              transition={{ duration: 0.9, delay: delay + i * 0.08, ease: [0.16, 1, 0.3, 1] }}
            >
              {word}
            </motion.span>
            {showAsterisk && isLast && (
              <span className="absolute" style={{ top: "0.55em", right: "-0.28em", fontSize: "0.31em", fontWeight: 300 }}>*</span>
            )}
          </span>
        );
      })}
    </span>
  );
}

// ---- WordsPullUpMultiStyle ---------------------------------------
function WordsPullUpMultiStyle({ segments, className = "", baseDelay = 0 }) {
  const ref = useRef(null);
  const inView = useInView(ref, { once: true, margin: "-10%" });
  const flat = [];
  segments.forEach((seg, si) => {
    const words = seg.text.split(" ");
    words.forEach((w, wi) => flat.push({ word: w, className: seg.className || "", style: seg.style || null, segmentIdx: si, wordIdx: wi, isFirstOfSeg: wi === 0 && si > 0 }));
  });
  return (
    <span ref={ref} className={"inline-flex flex-wrap " + className}>
      {flat.map((f, i) => (
        <React.Fragment key={i}>
          {f.isFirstOfSeg && <span className="block w-full" style={{ height: "0.5em" }} />}
          <span className="inline-block overflow-hidden align-bottom" style={{ marginRight: "0.22em" }}>
            <motion.span
              className={"inline-block " + f.className}
              style={f.style || undefined}
              initial={{ y: 24, opacity: 0 }}
              animate={inView ? { y: 0, opacity: 1 } : { y: 24, opacity: 0 }}
              transition={{ duration: 0.9, delay: baseDelay + i * 0.06, ease: [0.16, 1, 0.3, 1] }}
            >
              {f.word}
            </motion.span>
          </span>
        </React.Fragment>
      ))}
    </span>
  );
}

// ---- ScrollRevealParagraph --------------------------------------
function ScrollRevealParagraph({ text, className = "", style = {} }) {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({ target: ref, offset: ["start 0.85", "end 0.35"] });
  const chars = Array.from(text);
  return (
    <p ref={ref} className={className} style={style}>
      {chars.map((c, i) => (
        <AnimatedLetter key={i} progress={scrollYProgress} index={i} total={chars.length} char={c} />
      ))}
    </p>
  );
}
function AnimatedLetter({ progress, index, total, char }) {
  const cp = index / total;
  const opacity = useTransform(progress, [Math.max(0, cp - 0.08), Math.min(1, cp + 0.05)], [0.18, 1]);
  return <motion.span style={{ opacity }}>{char}</motion.span>;
}

// ---- Media tile (image / video / placeholder + description) ----
function PlaceholderTile({ label, ratio = "4/5", tone = "base", caption, tag, image, video, poster, description, href, youtubeId, ctaLabel }) {
  const [hover, setHover] = useState(false);
  const [imgFallback, setImgFallback] = useState(false);
  const [needsTap, setNeedsTap] = useState(false);
  const [generatedPoster, setGeneratedPoster] = useState(null);
  const videoRef = useRef(null);
  const tileRef = useRef(null);
  const isVideo = !!video || (image && /\.(mp4|webm|mov)(\?|$)/i.test(image));
  const rawSrc = video || image;

  // Capture the first frame of a video as a poster so users see a still image
  // instead of a black box during the metadata→canplay gap. Runs once per tile;
  // result is cached in sessionStorage keyed by URL so revisits are instant.
  useEffect(() => {
    if (!isVideo || !rawSrc || generatedPoster) return;
    const cacheKey = "poster:" + rawSrc;
    try {
      const cached = sessionStorage.getItem(cacheKey);
      if (cached) { setGeneratedPoster(cached); return; }
    } catch (_) {}
    const v = document.createElement("video");
    v.crossOrigin = "anonymous";
    v.muted = true;
    v.playsInline = true;
    v.preload = "metadata";
    v.src = rawSrc;
    let done = false;
    const grab = () => {
      if (done) return;
      done = true;
      try {
        const c = document.createElement("canvas");
        c.width = v.videoWidth || 640;
        c.height = v.videoHeight || 800;
        const ctx = c.getContext("2d");
        ctx.drawImage(v, 0, 0, c.width, c.height);
        const dataUrl = c.toDataURL("image/jpeg", 0.7);
        setGeneratedPoster(dataUrl);
        try { sessionStorage.setItem(cacheKey, dataUrl); } catch (_) {}
      } catch (_) { /* CORS-tainted canvas — silently skip */ }
      v.src = "";
    };
    v.addEventListener("loadeddata", grab);
    v.addEventListener("seeked", grab);
    v.addEventListener("loadedmetadata", () => { try { v.currentTime = 0.1; } catch (_) {} });
    return () => { done = true; v.src = ""; };
  }, [isVideo, rawSrc, generatedPoster]);
  // Upgrade SmugMug image URLs to higher resolution. Source images are well
  // above 2× — bandwidth is not a concern. Tiles use /X2/ for crisp render
  // on retina/4K, lightbox uses /X3/ for the enlarged view. Videos pass
  // through untouched (already capped at /1280/).
  const upgradeSize = (url, size) => {
    if (!url || isVideo || !/photos\.smugmug\.com/.test(url)) return url;
    return url.replace(/\/(L|XL|X2|X3|M|S|Th|Ti)\/(i-[^/]+)-\1\./, `/${size}/$2-${size}.`);
  };
  const upgradedSrc = upgradeSize(rawSrc, "X2");
  const mediaSrc = imgFallback ? rawSrc : upgradedSrc;
  const lightboxSrc = (imgFallback ? rawSrc : upgradeSize(rawSrc, "X3")) || rawSrc;
  const toneBg = {
    base: "linear-gradient(135deg, #1a1613 0%, #141210 100%)",
    warm: "linear-gradient(135deg, #221c15 0%, #171310 100%)",
    deep: "linear-gradient(135deg, #0f0d0b 0%, #1c1814 100%)",
  }[tone] || tone;

  // Click behavior:
  //   - has media → open lightbox (regardless of href, on desktop and mobile)
  //   - no media but has href → navigate
  //   - neither → no-op
  const handleClick = (e) => {
    if (mediaSrc) {
      e.preventDefault();
      window.dispatchEvent(new CustomEvent("openLightbox", {
        detail: {
          src: lightboxSrc,
          isVideo,
          title: caption?.title,
          meta: caption?.meta,
          description,
          href,
          youtubeId,
          ctaLabel,
        },
      }));
    }
  };

  const Wrapper = href ? "a" : "div";
  const wrapperProps = href
    ? { href, target: href.startsWith("http") ? "_blank" : undefined, rel: "noopener", onClick: handleClick }
    : { onClick: handleClick, role: mediaSrc ? "button" : undefined, tabIndex: mediaSrc ? 0 : undefined };

  return (
    <figure className="group" ref={tileRef}>
      <Wrapper
        {...wrapperProps}
        className="block relative overflow-hidden rounded-sm w-full"
        style={{ aspectRatio: ratio, background: toneBg, cursor: href ? "pointer" : "default" }}
        onMouseEnter={() => setHover(true)}
        onMouseLeave={() => setHover(false)}
      >
        {/* media */}
        {mediaSrc && isVideo && (
          <>
            <video
              ref={videoRef}
              src={mediaSrc}
              poster={generatedPoster || undefined}
              autoPlay
              loop
              muted
              playsInline
              preload="metadata"
              onCanPlay={(e) => {
                const p = e.currentTarget.play();
                if (p && typeof p.catch === "function") p.catch(() => setNeedsTap(true));
              }}
              className="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-[1.03]"
            />
            {needsTap && (
              <button
                type="button"
                aria-label="Play video"
                onClick={(e) => {
                  e.stopPropagation();
                  e.preventDefault();
                  const v = videoRef.current;
                  if (!v) return;
                  v.muted = true;
                  v.play().then(() => setNeedsTap(false)).catch(() => {});
                }}
                className="absolute inset-0 flex items-center justify-center pointer-events-auto"
                style={{ background: "rgba(8,7,6,0.25)" }}
              >
                <span
                  className="flex items-center justify-center w-14 h-14 rounded-full"
                  style={{ background: "rgba(8,7,6,0.7)", border: "1px solid rgba(232,226,208,0.3)", backdropFilter: "blur(4px)" }}
                >
                  <svg width="20" height="20" viewBox="0 0 24 24" fill="#E8E2D0">
                    <path d="M8 5v14l11-7z" />
                  </svg>
                </span>
              </button>
            )}
          </>
        )}
        {mediaSrc && !isVideo && (
          <img
            src={mediaSrc}
            alt={caption?.title || label || ""}
            loading="lazy"
            onError={() => { if (!imgFallback) setImgFallback(true); }}
            className="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-[1.03]"
          />
        )}

        {/* placeholder overlay if no media */}
        {!mediaSrc && (
          <>
            <div
              className="absolute inset-0 opacity-[0.18] pointer-events-none"
              style={{
                backgroundImage:
                  "repeating-linear-gradient(135deg, rgba(232,226,208,0.06) 0px, rgba(232,226,208,0.06) 1px, transparent 1px, transparent 14px)",
              }}
            />
            <div className="absolute inset-0 flex items-center justify-center">
              <span className="font-mono text-[10px] sm:text-[11px] tracking-[0.2em] uppercase text-[#e8e2d0]/30 px-3 text-center">
                {label}
              </span>
            </div>
          </>
        )}

        {/* description overlay on hover */}
        {description && !isVideo && (
          <div
            className="absolute inset-0 flex items-end p-4 sm:p-5 transition-opacity duration-500 pointer-events-none"
            style={{
              background: "linear-gradient(to top, rgba(8,7,6,0.92) 0%, rgba(8,7,6,0.55) 45%, transparent 75%)",
              opacity: hover ? 1 : 0,
            }}
          >
            <p className="text-[#E8E2D0] text-xs sm:text-sm leading-snug" style={{ textWrap: "pretty" }}>
              {description}
            </p>
          </div>
        )}

        {tag && (
          <div className="absolute top-2 left-2 font-mono text-[9px] tracking-[0.15em] uppercase text-[#e8e2d0]/70 bg-black/40 px-1.5 py-0.5 rounded-sm">
            {tag}
          </div>
        )}

        {/* expand hint on hover when there is media */}
        {mediaSrc && (
          <div
            className="absolute top-2 right-2 w-7 h-7 rounded-full flex items-center justify-center transition-opacity duration-300 pointer-events-none"
            style={{
              background: "rgba(8,7,6,0.65)",
              backdropFilter: "blur(4px)",
              opacity: hover ? 1 : 0,
            }}
          >
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#E8E2D0" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7" />
            </svg>
          </div>
        )}
      </Wrapper>
      {caption && (
        <figcaption className="mt-2 sm:mt-3 flex items-baseline justify-between gap-3">
          <span className="text-[#e8e2d0]/85 text-xs sm:text-sm">{caption.title}</span>
          {caption.meta && (
            <span className="font-mono text-[10px] text-[#e8e2d0]/40 tracking-wider whitespace-nowrap">{caption.meta}</span>
          )}
        </figcaption>
      )}
    </figure>
  );
}

// ---- Section header ---------------------------------------------
function SectionHeader({ number, title, tagline, italic }) {
  return (
    <div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-3 sm:gap-8 mb-8 sm:mb-12 md:mb-16">
      <div className="flex items-baseline gap-4 sm:gap-6">
        <span className="font-mono text-[10px] sm:text-xs tracking-[0.25em] text-[#e8e2d0]/45">{number}</span>
        <h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl tracking-[-0.02em] leading-[0.95]" style={{ color: "#E8E2D0" }}>
          {italic ? <em className="font-serif font-normal">{title}</em> : title}
        </h2>
      </div>
      {tagline && (
        <p className="text-[#e8e2d0]/55 text-xs sm:text-sm max-w-xs sm:max-w-sm sm:text-right leading-snug">
          {tagline}
        </p>
      )}
    </div>
  );
}

// ---- Lightbox (click-to-enlarge for image/video tiles) ----------
function Lightbox() {
  const [item, setItem] = useState(null);
  const [showVideo, setShowVideo] = useState(false);
  useEffect(() => {
    function open(e) { setItem(e.detail); setShowVideo(false); }
    function onKey(e) { if (e.key === "Escape") setItem(null); }
    window.addEventListener("openLightbox", open);
    window.addEventListener("keydown", onKey);
    return () => {
      window.removeEventListener("openLightbox", open);
      window.removeEventListener("keydown", onKey);
    };
  }, []);
  useEffect(() => {
    if (item) document.body.style.overflow = "hidden";
    else document.body.style.overflow = "";
  }, [item]);
  if (!item) return null;
  const close = () => setItem(null);
  return (
    <div
      className="fixed inset-0 z-[100] flex items-center justify-center p-4 sm:p-8 md:p-12"
      style={{ background: "rgba(8,7,6,0.95)", backdropFilter: "blur(8px)" }}
      onClick={close}
      role="dialog"
      aria-modal="true"
    >
      {/* close button */}
      <button
        onClick={close}
        className="absolute top-4 right-4 sm:top-6 sm:right-6 w-10 h-10 sm:w-12 sm:h-12 rounded-full flex items-center justify-center transition-colors"
        style={{ background: "rgba(232,226,208,0.1)", border: "1px solid rgba(232,226,208,0.2)" }}
        aria-label="Close"
      >
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#E8E2D0" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M18 6 6 18M6 6l12 12" />
        </svg>
      </button>

      {/* media + caption */}
      <div className="relative max-w-full max-h-full flex flex-col items-center gap-4" onClick={(e) => e.stopPropagation()}>
        <div className="relative flex items-center justify-center" style={{ maxHeight: "80vh", maxWidth: "92vw" }}>
          {showVideo && item.youtubeId ? (
            <div
              className="relative"
              style={{ width: "min(92vw, calc(80vh * 16 / 9))", aspectRatio: "16 / 9", background: "#0e0c0a" }}
            >
              <iframe
                src={`https://www.youtube.com/embed/${item.youtubeId}?autoplay=1&rel=0`}
                title="YouTube video player"
                frameBorder="0"
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
                referrerPolicy="strict-origin-when-cross-origin"
                allowFullScreen
                className="absolute inset-0 w-full h-full rounded-sm"
              />
            </div>
          ) : item.isVideo ? (
            <video
              src={item.src}
              autoPlay
              loop
              controls
              playsInline
              className="block max-h-[80vh] max-w-[92vw] object-contain rounded-sm"
              style={{ background: "#0e0c0a" }}
            />
          ) : (
            <img
              src={item.src}
              alt={item.title || ""}
              className="block max-h-[80vh] max-w-[92vw] object-contain rounded-sm"
              style={{ background: "#0e0c0a" }}
            />
          )}
        </div>
        {(item.title || item.description) && (
          <div className="max-w-2xl text-center px-4">
            {item.title && (
              <div className="flex items-baseline justify-center gap-3 flex-wrap">
                <span className="text-[#E8E2D0] text-sm sm:text-base">{item.title}</span>
                {item.meta && (
                  <span className="font-mono text-[10px] text-[#e8e2d0]/45 tracking-wider">{item.meta}</span>
                )}
              </div>
            )}
            {item.description && (
              <p className="mt-2 text-[#e8e2d0]/65 text-xs sm:text-sm leading-snug" style={{ textWrap: "pretty" }}>
                {item.description}
              </p>
            )}
            {item.youtubeId && !showVideo && (
              <button
                type="button"
                onClick={() => setShowVideo(true)}
                className="inline-flex items-center gap-2 mt-3 text-xs font-mono tracking-[0.2em] uppercase text-[#E8E2D0] underline underline-offset-4 decoration-[#e8e2d0]/30 hover:decoration-[#E8E2D0]"
              >
                {item.ctaLabel || "See the Grow Lab"} <span aria-hidden="true">→</span>
              </button>
            )}
            {item.href && !item.youtubeId && (
              <a
                href={item.href}
                className="inline-block mt-3 text-xs font-mono tracking-[0.2em] uppercase text-[#E8E2D0] underline underline-offset-4 decoration-[#e8e2d0]/30 hover:decoration-[#E8E2D0]"
              >
                view project →
              </a>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

Object.assign(window, {
  WordsPullUp,
  WordsPullUpMultiStyle,
  ScrollRevealParagraph,
  PlaceholderTile,
  SectionHeader,
  Lightbox,
});
