// Direction B — "Editorial / Kinetic"
// Aesthetic: bone/ink palette (deep neutral but warmer, paper-feeling
// blacks and warm bone whites), tight grotesk, kinetic ticker headline,
// considered editorial layout. Side index. Big numerals as wayfinding.
//
// Layout:
//   - Side rail with section index + numeric labels
//   - Big kinetic marquee headline that scrolls horizontally
//   - Hero showreel inside an offset frame, asymmetric
//   - Uniform grid but with numeric counters and hairline rules
//   - About: editorial 2-col with blockquote
//   - Contact: structured table-like

const { useState: useStateB, useEffect: useEffectB, useRef: useRefB } = React;

// Hover-to-expand grid. Cells are 1:1 by default. Hovering a cell grows its
// width to the native 16:9 footprint (≈ 1.78× the default cell width); the
// other cells in the same row share the remainder. Row height is held
// constant by the un-hovered cell width × 1 (square), so only widths animate.
//
// Math, per row of N cells, row width W (after gaps):
//   default cell width        = W / N            (square, height = W/N)
//   hovered cell native width = (W / N) × 16/9   ≈ 1.78 × default
//   remaining cells share     = W − hoveredWidth, split equally
// Implemented with flex-grow ratios so it transitions cleanly:
//   default each cell flex-grow = 9
//   on hover the hovered cell   = 16
//   the other cells in that row = (9N − 16) / (N − 1)
// Sum stays at 9N, so percent layout matches the math above.
// Paginated index grid. With ~30+ clips, all-at-once made the page heavy
// (every <video> mounts and decodes immediately). Now we render
// ROWS_PER_PAGE rows per page and let the user step through with the
// nav arrows below the grid. Page swaps slide in horizontally — forward
// from the right, back from the left — with a stagger across rows so
// it feels editorial rather than a hard cut.
function IndexGridB({ clips, cols, rule, page, direction, startIdx }) {
  const [hoverIdx, setHoverIdx] = useStateB(null);
  const containerRef = useRefB(null);
  const [rowWidth, setRowWidth] = useStateB(0);
  // Re-render when any clip's native aspect ratio resolves so the hover
  // expansion math uses real dimensions instead of falling back to 16:9.
  const [, setAspectTick] = useStateB(0);
  useEffectB(() => {
    const fn = () => setAspectTick(t => t + 1);
    (window.__clipAspectListeners = window.__clipAspectListeners || new Set()).add(fn);
    return () => window.__clipAspectListeners.delete(fn);
  }, []);

  // Watch container width so we can compute a fixed row height equal to the
  // un-hovered (square) cell height. Locking row height makes the only thing
  // that animates the *width* of each cell — clean and predictable, regardless
  // of browser support for animated `aspect-ratio`.
  useEffectB(() => {
    const el = containerRef.current;
    if (!el) return;
    const update = () => setRowWidth(el.clientWidth);
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // Chunk clips into rows of `cols`.
  const rows = [];
  for (let i = 0; i < clips.length; i += cols) rows.push(clips.slice(i, i + cols));

  const GAP = 24;
  // Default square cell width (and therefore the locked row height).
  const cellSize = rowWidth > 0 ? (rowWidth - GAP * (cols - 1)) / cols : 0;

  // Look up native aspect (w/h). Default to 16/9 only as a placeholder while
  // metadata loads. Square / portrait clips return ≤ 1, which means no
  // expansion (the hover stays at 1:1 in place).
  const aspectFor = (clip) => {
    const a = (window.__clipAspect || {})[clip.src];
    return a && isFinite(a) && a > 0 ? a : 16 / 9;
  };

  return (
    <div ref={containerRef} style={{ display: 'flex', flexDirection: 'column', gap: 32, position: 'relative', overflow: 'hidden' }}>
      <style>{`
        @keyframes idxSlideInRight {
          from { transform: translateX(6%); opacity: 0; }
          to   { transform: translateX(0);    opacity: 1; }
        }
        @keyframes idxSlideInLeft {
          from { transform: translateX(-6%); opacity: 0; }
          to   { transform: translateX(0);    opacity: 1; }
        }
      `}</style>
      <div
        key={page}
        style={{
          display: 'flex', flexDirection: 'column', gap: 32,
          animation: `${direction >= 0 ? 'idxSlideInRight' : 'idxSlideInLeft'} 520ms cubic-bezier(0.22, 1, 0.36, 1) both`,
        }}
      >
      {rows.map((row, rIdx) => {
        const hoveredInRow = hoverIdx !== null && Math.floor(hoverIdx / cols) === rIdx
          ? hoverIdx % cols
          : null;
        const N = row.length;

        // Compute per-row flex ratios. The hovered cell wants width =
        // (cellSize × clampedAspect), the others share the remainder.
        // We translate that into flex-grow values whose sum stays at 9·cols
        // so total row width is preserved.
        //
        // clampedAspect = max(1, nativeAspect) — square or portrait clips
        // expand by 1× (i.e. not at all). Wider-than-16:9 clips expand
        // fully to their native aspect.
        let flexFor = (cIdx) => 9; // default before any hover
        if (hoveredInRow !== null) {
          const hoveredClip = row[hoveredInRow];
          const nativeAspect = aspectFor(hoveredClip);
          const expandAspect = Math.max(1, nativeAspect);
          // Hovered flex value is proportional to expandAspect; baseline
          // square cell contributes 9 (since 9/9 = 1). So:
          //   hoveredFlex = 9 × expandAspect
          //   sum is fixed at 9 × cols, so others share the remainder.
          const hoveredFlex = 9 * expandAspect;
          const remaining = 9 * cols - hoveredFlex;
          const otherFlex = N > 1 ? remaining / (cols - 1) : hoveredFlex;
          flexFor = (cIdx) => (cIdx === hoveredInRow ? hoveredFlex : otherFlex);
          // If the hovered clip is ≤ 1:1, expandAspect=1 and every cell ends
          // up at flex 9 — layout doesn't budge, exactly what we want.
        }

        return (
          <div key={rIdx} style={{ display: 'flex', gap: GAP, alignItems: 'flex-start' }}>
            {row.map((clip, cIdx) => {
              const localIdx = rIdx * cols + cIdx;
              const globalIdx = startIdx + localIdx;
              const isHovered = hoveredInRow === cIdx;
              const flexGrow = flexFor(cIdx);

              return (
                <div
                  key={clip.id}
                  onMouseEnter={() => setHoverIdx(localIdx)}
                  onMouseLeave={() => setHoverIdx((h) => (h === localIdx ? null : h))}
                  style={{
                    flex: `${flexGrow} ${flexGrow} 0`,
                    minWidth: 0,
                    transition: 'flex-grow 480ms cubic-bezier(0.22, 1, 0.36, 1)',
                  }}
                >
                  {/* Tile. Height locked to the default 1:1 cell size; only
                      the cell *width* changes on hover, so the video
                      uncrops horizontally up to its native aspect ratio. */}
                  <div style={{
                    height: cellSize || undefined,
                    width: '100%',
                    position: 'relative',
                    overflow: 'hidden',
                    background: 'rgba(22,20,15,0.06)',
                  }}>
                    <ClipCanvas clip={clip} hover style={{ position: 'absolute', inset: 0 }} />
                  </div>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginTop: 8, fontSize: 11, letterSpacing: 0.6, textTransform: 'uppercase', minWidth: 0 }}>
                    <span style={{ fontVariantNumeric: 'tabular-nums', opacity: 0.5 }}>{String(globalIdx + 1).padStart(3, '0')}</span>
                    <span style={{ opacity: 0.85, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', marginLeft: 8 }}>{clip.label}</span>
                  </div>
                </div>
              );
            })}
            {/* Pad short trailing rows so cells don't stretch to fill width. */}
            {N < cols && Array.from({ length: cols - N }).map((_, i) => (
              <div key={`pad-${i}`} style={{ flex: '9 9 0', minWidth: 0 }} aria-hidden />
            ))}
          </div>
        );
      })}
      </div>
    </div>
  );
}

function DirB({ width = 1280, height = 800, gridCols = 3 }) {
  const scrollRef = useRefB(null);
  const [activeNav, setActiveNav] = useStateB('reel');
  // Pagination — 4 rows per page so the layout stays consistent across
  // the 3up/4up/5up densities (12 / 16 / 20 clips per page).
  const ROWS_PER_PAGE = 4;
  const pageSize = gridCols * ROWS_PER_PAGE;
  const allClips = window.CLIPS;
  const totalPages = Math.max(1, Math.ceil(allClips.length / pageSize));
  const [page, setPage] = useStateB(0);
  const [direction, setDirection] = useStateB(1);
  // Clamp when density changes (5up may produce fewer pages than 3up).
  useEffectB(() => {
    if (page > totalPages - 1) setPage(totalPages - 1);
  }, [totalPages, page]);
  const clampedPage = Math.min(page, totalPages - 1);
  const startIdx = clampedPage * pageSize;
  const pageClips = allClips.slice(startIdx, startIdx + pageSize);
  const goPage = (next) => {
    if (next === clampedPage) return;
    setDirection(next > clampedPage ? 1 : -1);
    setPage(next);
  };

  useEffectB(() => {
    const root = scrollRef.current;
    if (!root) return;
    const ids = ['work', 'about', 'contact'];
    const onScroll = () => {
      const top = root.scrollTop + 120;
      let cur = ids[0];
      for (const id of ids) {
        const el = root.querySelector(`#b-${id}`);
        if (el && el.offsetTop <= top) cur = id;
      }
      setActiveNav(cur);
    };
    root.addEventListener('scroll', onScroll, { passive: true });
    return () => root.removeEventListener('scroll', onScroll);
  }, []);

  const goto = (id) => {
    const root = scrollRef.current;
    const el = root && root.querySelector(`#b-${id}`);
    if (el) root.scrollTo({ top: el.offsetTop - 24, behavior: 'smooth' });
  };

  // Color tokens
  const bg = '#e8e3d8';   // warm bone
  const ink = '#16140f';  // warm ink
  const muted = 'rgba(22,20,15,0.55)';
  const rule = 'rgba(22,20,15,0.18)';

  const gridClips = allClips;
  const sections = [
    { id: 'work',    n: '01', t: 'Index' },
    { id: 'about',   n: '02', t: 'About' },
    { id: 'contact', n: '03', t: 'Contact' },
  ];

  return (
    <div ref={scrollRef} style={{
      width, height, overflow: 'auto', background: bg, color: ink,
      fontFamily: '"Helvetica Neue", "Inter", Helvetica, Arial, sans-serif',
      position: 'relative',
    }}>
      {/* Side rail */}
      <div style={{
        position: 'sticky', top: 0, float: 'left', width: 80, height: 0, zIndex: 10,
      }}>
        <div style={{ position: 'absolute', top: 32, left: 24, display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div style={{ fontSize: 10, fontWeight: 600, letterSpacing: 1.5, textTransform: 'uppercase', writingMode: 'vertical-rl', transform: 'rotate(180deg)', marginBottom: 24 }}>
            Spencer Russell / Motion
          </div>
        </div>
        <div style={{ position: 'absolute', bottom: -height + 80, left: 24, display: 'flex', flexDirection: 'column', gap: 14 }}>
          {sections.map((s) => (
            <button key={s.id} onClick={() => goto(s.id)} style={{
              background: 'transparent', border: 'none', padding: 0, cursor: 'pointer', textAlign: 'left',
              fontFamily: 'inherit', color: activeNav === s.id ? ink : muted,
              display: 'flex', flexDirection: 'column', gap: 2,
            }}>
              <span style={{ fontSize: 10, fontVariantNumeric: 'tabular-nums', opacity: 0.7 }}>{s.n}</span>
              <span style={{ fontSize: 12, fontWeight: 500, letterSpacing: 0.2 }}>{s.t}</span>
            </button>
          ))}
        </div>
      </div>

      <div style={{ marginLeft: 80 }}>
        {/* Kinetic marquee headline */}
        <div style={{ paddingTop: 64, paddingBottom: 24, borderBottom: `1px solid ${rule}`, overflow: 'hidden' }}>
          <div style={{ display: 'flex', whiteSpace: 'nowrap', animation: 'b-marquee 38s linear infinite' }}>
            {[0, 1].map((k) => (
              <div key={k} style={{ display: 'flex', alignItems: 'baseline', gap: 24, paddingRight: 24 }}>
                <span style={{ fontSize: 140, fontWeight: 700, letterSpacing: -5, lineHeight: 1.2 }}>Motion Design</span>
                <span style={{ fontSize: 140, fontWeight: 200, letterSpacing: -5, lineHeight: 1.2, fontStyle: 'italic', opacity: 0.5 }}>/</span>
                <span style={{ fontSize: 140, fontWeight: 700, letterSpacing: -5, lineHeight: 1.2 }}>Cinema 4D</span>
                <span style={{ fontSize: 140, fontWeight: 200, letterSpacing: -5, lineHeight: 1.2, fontStyle: 'italic', opacity: 0.5 }}>/</span>
                <span style={{ fontSize: 140, fontWeight: 700, letterSpacing: -5, lineHeight: 1.2 }}>Redshift</span>
                <span style={{ fontSize: 140, fontWeight: 200, letterSpacing: -5, lineHeight: 1.2, fontStyle: 'italic', opacity: 0.5 }}>/</span>
              </div>
            ))}
          </div>
          <style>{`@keyframes b-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }`}</style>
        </div>

        {/* Index grid — rows of N cells. Each cell is 1:1 by default; on hover
            the hovered cell expands to its native 16:9 width and its row-mates
            shrink to share the remainder. Row height stays locked so only
            widths animate. */}
        <div id="b-work" style={{ padding: '80px 56px 80px 40px' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 32, paddingBottom: 16, borderBottom: `1px solid ${rule}` }}>
            <div style={{ fontSize: 32, fontWeight: 700, letterSpacing: -1 }}>Index</div>
            <div style={{ fontSize: 11, letterSpacing: 0.6, textTransform: 'uppercase', opacity: 0.6, fontVariantNumeric: 'tabular-nums' }}>{gridClips.length} works · 2024 / 2026</div>
          </div>
          <IndexGridB clips={pageClips} cols={gridCols} rule={rule} page={clampedPage} direction={direction} startIdx={startIdx} />
          {/* Pagination — minimal arrows + counter. Hidden when only one page. */}
          {totalPages > 1 && (
            <div style={{
              display: 'flex', justifyContent: 'space-between', alignItems: 'center',
              marginTop: 40, paddingTop: 20, borderTop: `1px solid ${rule}`,
              fontSize: 11, letterSpacing: 0.6, textTransform: 'uppercase',
              fontVariantNumeric: 'tabular-nums',
            }}>
              <div style={{ opacity: 0.55 }}>
                {String(startIdx + 1).padStart(3, '0')} – {String(Math.min(startIdx + pageSize, gridClips.length)).padStart(3, '0')} / {String(gridClips.length).padStart(3, '0')}
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 28 }}>
                <button
                  onClick={() => goPage(clampedPage - 1)}
                  disabled={clampedPage === 0}
                  aria-label="Previous page"
                  style={{
                    background: 'transparent', border: 'none', padding: 0, cursor: clampedPage === 0 ? 'default' : 'pointer',
                    fontFamily: 'inherit', color: ink, opacity: clampedPage === 0 ? 0.2 : 0.85,
                    fontSize: 18, lineHeight: 1, transition: 'opacity 200ms',
                  }}
                >←</button>
                <span style={{ opacity: 0.7, minWidth: 56, textAlign: 'center' }}>
                  {String(clampedPage + 1).padStart(2, '0')} / {String(totalPages).padStart(2, '0')}
                </span>
                <button
                  onClick={() => goPage(clampedPage + 1)}
                  disabled={clampedPage >= totalPages - 1}
                  aria-label="Next page"
                  style={{
                    background: 'transparent', border: 'none', padding: 0, cursor: clampedPage >= totalPages - 1 ? 'default' : 'pointer',
                    fontFamily: 'inherit', color: ink, opacity: clampedPage >= totalPages - 1 ? 0.2 : 0.85,
                    fontSize: 18, lineHeight: 1, transition: 'opacity 200ms',
                  }}
                >→</button>
              </div>
            </div>
          )}
        </div>

        {/* About */}
        <div id="b-about" style={{ padding: '120px 56px 120px 40px', borderTop: `1px solid ${rule}` }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 60 }}>
            <div style={{ fontSize: 11, letterSpacing: 1, textTransform: 'uppercase', opacity: 0.6 }}>02 / About</div>
            <div>
              <div style={{ fontSize: 44, fontWeight: 700, letterSpacing: -1.4, lineHeight: 1.1, marginBottom: 40 }}>
                I build abstract studies, VJ clips, and the occasional commission / using
                <span style={{ opacity: 0.5 }}> Cinema 4D, Redshift, xParticles, Octane, and After Effects.</span>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 32, fontSize: 14, lineHeight: 1.65, opacity: 0.85 }}>
                {window.PORTFOLIO.bio.map((p, i) => <p key={i} style={{ margin: 0 }}>{p}</p>)}
              </div>
            </div>
          </div>
        </div>

        {/* Contact (table-like) */}
        <div id="b-contact" style={{ padding: '80px 56px 60px 40px', borderTop: `1px solid ${rule}` }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 60, alignItems: 'baseline' }}>
            <div style={{ fontSize: 11, letterSpacing: 1, textTransform: 'uppercase', opacity: 0.6 }}>03 / Contact</div>
            <div>
              {[
                ['Email', window.PORTFOLIO.contact.email],
                ['Instagram', window.PORTFOLIO.contact.instagram],
                ['Are.na', window.PORTFOLIO.contact.arena],
                ['Location', 'Portland, OR'],
              ].map(([k, v]) => (
                <div key={k} style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 24, padding: '14px 0', borderBottom: `1px solid ${rule}` }}>
                  <span style={{ fontSize: 11, letterSpacing: 0.6, textTransform: 'uppercase', opacity: 0.55 }}>{k}</span>
                  <span style={{ fontSize: 18, fontWeight: 500, letterSpacing: -0.2 }}>{v}</span>
                </div>
              ))}
            </div>
          </div>
          <div style={{ marginTop: 60, fontSize: 10, letterSpacing: 0.6, textTransform: 'uppercase', opacity: 0.4 }}>
            © 2026 Spencer Russell
          </div>
        </div>
      </div>
    </div>
  );
}

window.DirB = DirB;
