// MotionGrid — the visual → motion index. A hard-edged grid of clip tiles that
// each play on hover (desktop) or when scrolled into view (mobile), mirroring
// the dedicated motion site's "Index" grid but rendered in THIS portfolio's
// vocabulary (square cover tiles, hairline rules, mono captions — not the motion
// site's bone/ink flex-expand layout). Clicking a tile opens the shared
// lightbox to play the clip large + uncropped.
//
// Reads window.MOTION_CLIPS (motion-clips.jsx) and opens via
// window.openImageLightbox. Loads AFTER motion-clips.jsx, responsive.jsx, and
// lightbox.jsx; consumed by subcategory-page.jsx.
//
// Performance: 31 videos can't all decode at once (esp. iOS). Each tile attaches
// its <video> src only when it scrolls within ~1.5 viewports (IntersectionObserver,
// load-once) and only PLAYS while hovered (desktop) or centered in view (mobile);
// it pauses in place otherwise. crossOrigin is set so the CDN's CORS headers let
// the frame paint without tainting.

const MG_RULE = 'var(--rule)', MG_INK = 'var(--ink)', MG_INK2 = 'var(--ink-2)',
      MG_INK3 = 'var(--ink-3)', MG_PAPER = 'var(--paper)', MG_PAPER2 = 'var(--paper-2)';

// Inject the loading shimmer keyframes once.
(function mgInjectStyle() {
  if (typeof document === 'undefined' || document.getElementById('mg-style')) return;
  const s = document.createElement('style');
  s.id = 'mg-style';
  s.textContent =
    '@keyframes mgShimmer{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}' +
    '@keyframes mgPulse{0%,100%{opacity:.35}50%{opacity:1}}';
  document.head.appendChild(s);
})();

function MotionTile({ clip, mobile }) {
  const wrapRef = React.useRef(null);
  const vidRef = React.useRef(null);
  const [attached, setAttached] = React.useState(false);   // src wired up (lazy)
  const [ready, setReady] = React.useState(false);          // first frame decoded
  const [errored, setErrored] = React.useState(false);
  const [hover, setHover] = React.useState(false);
  const [inView, setInView] = React.useState(false);        // mobile autoplay gate
  const reduce = typeof window.matchMedia === 'function'
    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  // Lazy attach: load the video once it nears the viewport, then stop observing.
  React.useEffect(() => {
    const el = wrapRef.current;
    if (!el || typeof IntersectionObserver === 'undefined') { setAttached(true); return; }
    const io = new IntersectionObserver((entries) => {
      if (entries.some((e) => e.isIntersecting)) { setAttached(true); io.disconnect(); }
    }, { rootMargin: '150% 0% 150% 0%', threshold: 0.01 });
    io.observe(el);
    return () => io.disconnect();
  }, []);

  // Mobile-only: a tighter observer drives autoplay for whatever's centered.
  React.useEffect(() => {
    if (!mobile) return;
    const el = wrapRef.current;
    if (!el || typeof IntersectionObserver === 'undefined') return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => setInView(e.isIntersecting && e.intersectionRatio > 0.55));
    }, { threshold: [0, 0.55, 0.9] });
    io.observe(el);
    return () => io.disconnect();
  }, [mobile]);

  const active = mobile ? inView : hover;

  // Play while active, pause (in place) otherwise.
  React.useEffect(() => {
    const v = vidRef.current;
    if (!v || !attached) return;
    if (active && !reduce) {
      const p = v.play();
      if (p && p.catch) p.catch(() => {});
    } else {
      try { v.pause(); } catch (e) {}
    }
  }, [active, attached, ready, reduce]);

  const onMeta = () => {
    const v = vidRef.current;
    if (!v) return;
    // De-sync adjacent tiles: seed the playhead from the clip number.
    try {
      const seed = parseInt(String(clip.id).replace(/\D/g, ''), 10) || clip.n || 1;
      if (v.duration && isFinite(v.duration)) v.currentTime = (seed * 0.31) % v.duration;
    } catch (e) {}
  };

  const open = () => {
    if (!window.openImageLightbox) return;
    window.openImageLightbox([{ video: clip.url, src: clip.poster, title: clip.label, tag: 'motion' }], 0);
  };

  const idx = String(clip.n).padStart(3, '0');

  return (
    <figure style={{ margin: 0, display: 'flex', flexDirection: 'column', gap: 9 }}>
      <div ref={wrapRef} onClick={open}
        onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
        style={{ position: 'relative', overflow: 'hidden', aspectRatio: '1 / 1',
          background: clip.bg, border: `1px solid ${active ? MG_INK : MG_RULE}`,
          cursor: 'zoom-in', transition: 'border-color .18s' }}>

        {/* Loading shimmer — visible until the first frame decodes. */}
        {attached && !ready && !errored && (
          <div aria-hidden="true" style={{ position: 'absolute', inset: 0, overflow: 'hidden' }}>
            <div style={{ position: 'absolute', top: 0, bottom: 0, width: '60%',
              background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.06), transparent)',
              mixBlendMode: 'screen', animation: reduce ? 'none' : 'mgShimmer 1.6s linear infinite' }} />
          </div>
        )}

        {/* The clip. src only attached once near-viewport (lazy). */}
        {attached && !errored && (
          <video ref={vidRef} src={clip.url} poster={clip.poster || undefined}
            muted loop playsInline crossOrigin="anonymous" preload="metadata" tabIndex={-1}
            onLoadedMetadata={onMeta} onLoadedData={() => setReady(true)} onError={() => setErrored(true)}
            style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover',
              opacity: ready ? 1 : 0, transition: reduce ? 'none' : 'opacity .32s ease' }} />
        )}

        {/* Encode/host failure fallback. */}
        {errored && (
          <div className="mono" style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
            alignItems: 'center', justifyContent: 'center', gap: 6, padding: 12, textAlign: 'center',
            color: 'rgba(255,255,255,0.7)', fontSize: 9, letterSpacing: 0.4,
            backgroundImage: 'repeating-linear-gradient(135deg, transparent 0 7px, rgba(255,255,255,0.05) 7px 8px)' }}>
            <span style={{ fontSize: 14 }}>⚠</span>
            <span>re-encode required</span>
            <span style={{ opacity: 0.6, fontSize: 8, wordBreak: 'break-all' }}>{clip.file}</span>
          </div>
        )}

        {/* ▶ play affordance — idle only. */}
        <div className="mono" aria-hidden="true" style={{ position: 'absolute', top: 8, left: 8,
          display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 8.5, letterSpacing: 0.6,
          textTransform: 'uppercase', padding: '3px 6px', lineHeight: 1, color: '#fff',
          background: 'rgba(0,0,0,0.42)', backdropFilter: 'blur(3px)', WebkitBackdropFilter: 'blur(3px)',
          opacity: (active || errored) ? 0 : 1, transition: 'opacity .2s ease' }}>
          <span style={{ fontSize: 7 }}>▶</span>{mobile ? 'in view' : 'hover'}
        </div>

        {/* Enlarge glyph — on active. */}
        <div className="mono" style={{ position: 'absolute', top: 6, right: 6, width: 22, height: 22,
          display: 'flex', alignItems: 'center', justifyContent: 'center', background: MG_INK, color: MG_PAPER,
          fontSize: 12, lineHeight: 1, opacity: active && !errored ? 1 : 0,
          transform: active ? 'scale(1)' : 'scale(.8)', transition: 'opacity .15s, transform .15s' }}>⤢</div>
      </div>

      {/* Caption: zero-padded global index + label. */}
      <figcaption className="mono" style={{ display: 'flex', gap: 10, alignItems: 'baseline',
        fontSize: 11, letterSpacing: 0.4 }}>
        <span className="tnum" style={{ color: MG_INK3, flexShrink: 0 }}>{idx}</span>
        <span style={{ color: MG_INK2, textTransform: 'uppercase', letterSpacing: 0.5, overflow: 'hidden',
          textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{clip.label}</span>
      </figcaption>
    </figure>
  );
}

function MotionGrid({ mobile }) {
  const clips = window.MOTION_CLIPS || [];
  return (
    <div style={{ display: 'grid',
      gridTemplateColumns: mobile ? 'repeat(2, 1fr)' : 'repeat(auto-fill, minmax(232px, 1fr))',
      gap: mobile ? 14 : 20 }}>
      {clips.map((clip) => <MotionTile key={clip.id} clip={clip} mobile={mobile} />)}
    </div>
  );
}

window.MotionTile = MotionTile;
window.MotionGrid = MotionGrid;
