// Shared-element scroll morphs between sections.
// Real elements (e.g. 6 bento cards in section 02) literally fly out and land
// as the real destination elements (3 pillars in section 03). Each zone uses
// a <MorphSpacer/> for scroll runway; <MorphLayer/> is one fixed overlay that
// renders twin elements interpolated from src bounding rects to dst rects.
//
//   Zone 23  →  6 bento cards     →  3 OS pillars
//   Zone 34  →  6 mini rungs      →  6 ladder rungs
//   Zone 45  →  6 ladder rungs    →  6 capability wedges (out of 8)
//
// CSS variables --p23 / --p34 / --p45 are driven continuously by scroll so the
// surrounding sections can fade + blur their content in/out around the morph.

function MorphSpacer({ id, vh = 90 }) {
  return (
    <div
      id={id}
      aria-hidden="true"
      style={{
        position: 'relative',
        width: '100%',
        height: `${vh}vh`,
        pointerEvents: 'none',
      }}
    ></div>
  );
}

/* ---------- math ---------- */
const _clamp01 = v => Math.max(0, Math.min(1, v));
const _lerp = (a, b, t) => a + (b - a) * t;
const _easeInOut = t => t < 0.5 ? 2*t*t : 1 - Math.pow(-2*t + 2, 2) / 2;
const _easeOut = t => 1 - Math.pow(1 - t, 3);
const _easeIn  = t => t * t;

/* ---------- 02 → 03 mapping ----------
   6 bento cards consolidate into 3 slabs, paired by colour family.
   Each pair has a "lead" (takes on the slab clip + gradient at the end)
   and a "follower" (fades out mid-flight as it converges on the lead). */
const T23_CARDS = [
  { lead: true,  dst: 0, bg: 'linear-gradient(155deg, #0D903A 0%, #074c2a 100%)' },
  { lead: true,  dst: 1, bg: 'linear-gradient(135deg, #1a3552 0%, #06192e 100%)' },
  { lead: false, dst: 0, bg: 'linear-gradient(155deg, #d8efde 0%, #b3e2c0 100%)' },
  { lead: false, dst: 1, bg: 'linear-gradient(155deg, #dde9f7 0%, #b8cfe9 100%)' },
  { lead: true,  dst: 2, bg: 'linear-gradient(155deg, #ffe28a 0%, #ffbb40 100%)' },
  { lead: false, dst: 2, bg: 'linear-gradient(135deg, #0f1620 0%, #1e2433 100%)' },
];
const T23_SLAB_CLIPS = [
  'polygon(0% 0%, 100% 0%, 86% 100%, 0% 100%)',
  'polygon(14% 0%, 100% 0%, 86% 100%, 0% 100%)',
  'polygon(14% 0%, 100% 0%, 100% 100%, 0% 100%)',
];
const T23_SLAB_GRADS = [
  ['#0D903A', '#074c2a'],
  ['#246BB2', '#164A97'],
  ['#FFBA00', '#8C6100'],
];

/* ---------- 03 → 04 mapping ----------
   6 mini rungs inside pillar 01 (labels: Case / Sim / Mimic / Shadow /
   Supervise / Independent) → 6 actual ladder rungs in section 04 by index.
   Pillars 02 + 03 fade out via CSS — they were context, the ladder is the act. */

/* ---------- 04 → 05 mapping ----------
   6 ladder rungs orbit through the wheel centre and settle as 6 of the
   8 capability wedges. The 2 remaining wedges (Value, Reactivation) appear
   independently via the section's --p45 fade-in. */
const T45_CAPS = [
  { idx: 0, color1: '#0D903A', color2: '#074c2a' }, // Finance         (Case)
  { idx: 1, color1: '#0F8E42', color2: '#063e1f' }, // Business        (Sim)
  { idx: 2, color1: '#246BB2', color2: '#164A97' }, // Macro           (Mimic)
  { idx: 3, color1: '#1A549F', color2: '#0e3669' }, // Products        (Shadow)
  { idx: 5, color1: '#19519C', color2: '#0e3669' }, // Acquisition     (Supervise)
  { idx: 7, color1: '#057430', color2: '#03461e' }, // Execution       (Independent)
];

function MorphLayer() {
  const [, setTick] = React.useState(0);
  const stateRef = React.useRef({
    z23: { p: 0, src: [], dst: [] },
    z34: { p: 0, src: [], dst: [] },
    z45: { p: 0, src: [], dst: [] },
  });

  // useLayoutEffect → variables set before first paint so users landing
  // mid-page don't see a flash of mis-staged sections.
  React.useLayoutEffect(() => {
    let raf = 0;
    const zones = [
      { key: 'z23', spacer: 'morph-23', cssVar: '--p23',
        srcSel: '.bento > .bento-card',
        dstSel: '.os-slabs .os-slab',
        srcN: 6, dstN: 3 },
      { key: 'z34', spacer: 'morph-34', cssVar: '--p34',
        srcSel: '.os-slab--ladder .os-slab-rung',
        dstSel: '.ladder-3d .rung',
        srcN: 6, dstN: 6 },
      { key: 'z45', spacer: 'morph-45', cssVar: '--p45',
        srcSel: '.ladder-3d .rung',
        dstSel: '.caprich-wedge',
        srcN: 6, dstN: 8 },
    ];
    function measure() {
      raf = 0;
      const viewH = window.innerHeight;
      const next = {};
      for (const z of zones) {
        const spacer = document.getElementById(z.spacer);
        if (!spacer) { next[z.key] = { p: 0, src: [], dst: [] }; continue; }
        const sr = spacer.getBoundingClientRect();
        const total = sr.height + viewH;
        const passed = viewH - sr.top;
        const p = _clamp01(passed / total);
        document.documentElement.style.setProperty(z.cssVar, p.toFixed(4));

        const srcEls = document.querySelectorAll(z.srcSel);
        const dstEls = document.querySelectorAll(z.dstSel);
        if (srcEls.length !== z.srcN || dstEls.length !== z.dstN) {
          next[z.key] = { p, src: [], dst: [] };
          continue;
        }
        const src = [], dst = [];
        for (let i = 0; i < srcEls.length; i++) src.push(srcEls[i].getBoundingClientRect());
        for (let i = 0; i < dstEls.length; i++) dst.push(dstEls[i].getBoundingClientRect());
        next[z.key] = { p, src, dst };
      }
      stateRef.current = next;
      setTick(t => t + 1);
    }
    function schedule() { if (!raf) raf = requestAnimationFrame(measure); }
    measure();
    window.addEventListener('scroll', schedule, { passive: true });
    window.addEventListener('resize', schedule);
    return () => {
      window.removeEventListener('scroll', schedule);
      window.removeEventListener('resize', schedule);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  const { z23, z34, z45 } = stateRef.current;

  return (
    <div className="morph-layer" aria-hidden="true">
      <MorphZone show={z23.src.length === 6 && z23.dst.length === 3} p={z23.p}>
        <T23Twins p={z23.p} src={z23.src} dst={z23.dst}/>
      </MorphZone>
      <MorphZone show={z34.src.length === 6 && z34.dst.length === 6} p={z34.p}>
        <T34Twins p={z34.p} src={z34.src} dst={z34.dst}/>
      </MorphZone>
      <MorphZone show={z45.src.length === 6 && z45.dst.length === 8} p={z45.p}>
        <T45Twins p={z45.p} src={z45.src} dst={z45.dst}/>
      </MorphZone>
    </div>
  );
}

function MorphZone({ show, p, children }) {
  if (!show || p <= 0.001 || p >= 0.999) return null;
  // Crossfade against real source/dest at the ends of the zone.
  // Wider in/out windows so twins overlap longer with the real elements
  // and there's no visible pop on either side.
  const fade = _easeInOut(_clamp01((p - 0.06) / 0.18))
             * (1 - _easeInOut(_clamp01((p - 0.84) / 0.14)));
  return (
    <div style={{ position: 'absolute', inset: 0, opacity: fade, willChange: 'opacity' }}>
      {children}
    </div>
  );
}

/* ============================================================
   02 → 03 — 6 bento cards consolidate into 3 pillars
   ============================================================ */
function T23Twins({ p, src, dst }) {
  const tLead     = _easeInOut(p);
  const tFollower = _easeInOut(_clamp01(p / 0.65));

  // Compute every lead's interpolated rect so followers chase the moving lead.
  const leadRects = {};
  for (let i = 0; i < T23_CARDS.length; i++) {
    const c = T23_CARDS[i];
    if (!c.lead) continue;
    const sr = src[i], dr = dst[c.dst];
    leadRects[c.dst] = {
      x: _lerp(sr.left,  dr.left,  tLead),
      y: _lerp(sr.top,   dr.top,   tLead),
      w: _lerp(sr.width, dr.width, tLead),
      h: _lerp(sr.height,dr.height,tLead),
    };
  }

  const ordered = T23_CARDS
    .map((c, i) => ({ ...c, i }))
    .sort((a, b) => (a.lead ? 1 : 0) - (b.lead ? 1 : 0));

  return ordered.map((c) => {
    const i = c.i;
    const sr = src[i];
    const dr = dst[c.dst];
    const slabClip = T23_SLAB_CLIPS[c.dst];
    const [g1, g2] = T23_SLAB_GRADS[c.dst];

    let x, y, w, h, op, clip, bg, radius, shadow;

    if (c.lead) {
      x = _lerp(sr.left,  dr.left,  tLead);
      y = _lerp(sr.top,   dr.top,   tLead);
      w = _lerp(sr.width, dr.width, tLead);
      h = _lerp(sr.height,dr.height,tLead);

      const blend = _easeOut(_clamp01((tLead - 0.30) / 0.55));
      bg = blend < 0.02
        ? c.bg
        : `linear-gradient(155deg,
             color-mix(in oklab, ${g1} ${blend*100}%, ${_extractStop(c.bg, 0)}) 0%,
             color-mix(in oklab, ${g2} ${blend*100}%, ${_extractStop(c.bg, 1)}) 100%)`;

      const clipBlend = _clamp01((tLead - 0.55) / 0.40);
      clip = clipBlend < 0.02
        ? 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)'
        : _interpolateClip(
            'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)',
            slabClip,
            _easeInOut(clipBlend)
          );

      radius = _lerp(24, 0, _easeOut(tLead));
      shadow = `0 ${_lerp(20, 50, tLead)}px ${_lerp(40, 120, tLead)}px rgba(5,10,20,${_lerp(0.18, 0.55, tLead)})`;
      op = 1;
    } else {
      const leadR = leadRects[c.dst];
      x = _lerp(sr.left,  leadR.x,  tFollower);
      y = _lerp(sr.top,   leadR.y,  tFollower);
      w = _lerp(sr.width, leadR.w,  tFollower);
      h = _lerp(sr.height,leadR.h,  tFollower);
      bg = c.bg;
      clip = 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)';
      radius = _lerp(24, 18, tFollower);
      op = Math.max(0, 1 - _easeIn(tFollower) * 1.15);
      shadow = `0 ${_lerp(14, 30, tFollower)}px ${_lerp(28, 60, tFollower)}px rgba(5,10,20,${_lerp(0.14, 0.0, tFollower)})`;
    }

    return (
      <div
        key={i}
        className="morph-twin"
        style={{
          left: x, top: y,
          width: Math.max(0, w), height: Math.max(0, h),
          background: bg, opacity: op,
          clipPath: clip, borderRadius: radius,
          boxShadow: shadow,
        }}
      />
    );
  });
}

/* ============================================================
   03 → 04 — 6 mini rungs → 6 ladder rungs
   ============================================================ */
function T34Twins({ p, src, dst }) {
  const tBase = _easeInOut(p);

  return src.map((sr, i) => {
    const dr = dst[i];
    // Subtle stagger — top mini rung leaves slightly later so the stack
    // unfurls "from the bottom up" into the ladder.
    const stagger = i * 0.05;
    const t = _easeInOut(_clamp01((p - stagger) / (1 - stagger * 1.2)));

    const x = _lerp(sr.left,  dr.left,  t);
    const y = _lerp(sr.top,   dr.top,   t);
    const w = _lerp(sr.width, dr.width, t);
    const h = _lerp(sr.height,dr.height,t);
    const radius = _lerp(8, 14, t);

    // Source mini-rung look (white tile over green slab) →
    // destination ladder plate (deep dark with gold trim).
    const blend = _easeOut(_clamp01((t - 0.20) / 0.55));
    const bg = `linear-gradient(155deg,
      color-mix(in oklab, #0c1424 ${blend*100}%, rgba(255,255,255,0.10)) 0%,
      color-mix(in oklab, #1d2940 ${blend*100}%, rgba(255,255,255,0.04)) 100%)`;
    const borderC = `rgba(255,186,0, ${_lerp(0.0, 0.35, blend).toFixed(2)})`;
    const shadow = `0 ${_lerp(2, 20, t).toFixed(1)}px ${_lerp(8, 50, t).toFixed(1)}px rgba(5,10,20,${_lerp(0.10, 0.45, t).toFixed(2)}),
                    inset 0 1px 0 rgba(255,255,255,${_lerp(0.10, 0.20, t).toFixed(2)})`;

    // Small accent line at the bottom — mirrors the ::after gold line on
    // the real ladder rungs — appears toward the end of the morph.
    const accentOp = _easeOut(_clamp01((t - 0.55) / 0.40));

    return (
      <div
        key={i}
        className="morph-twin morph-twin--rung"
        style={{
          left: x, top: y,
          width: Math.max(0, w), height: Math.max(0, h),
          background: bg,
          border: `1px solid ${borderC}`,
          boxSizing: 'border-box',
          borderRadius: radius,
          boxShadow: shadow,
          overflow: 'hidden',
        }}
      >
        <div style={{
          position: 'absolute', left: 0, right: 0, bottom: 0,
          height: 2,
          background: 'linear-gradient(90deg, transparent, #FFBA00, transparent)',
          opacity: accentOp,
        }}/>
      </div>
    );
  });
}

/* ============================================================
   04 → 05 — 6 ladder rungs orbit into 6 capability wedges
   ============================================================ */
function T45Twins({ p, src, dst }) {
  const t = _easeInOut(p);

  // Wheel centre, derived from the average of all wedge bboxes.
  let cx = 0, cy = 0, n = 0;
  for (const dr of dst) {
    cx += dr.left + dr.width / 2;
    cy += dr.top + dr.height / 2;
    n++;
  }
  cx /= n; cy /= n;

  return src.map((sr, i) => {
    const cap = T45_CAPS[i];
    const dr = dst[cap.idx];

    // Stagger so the bottom rung leads — feels like an unfurling vortex.
    const stagger = i * 0.04;
    const u = _easeInOut(_clamp01((p - stagger) / (1 - stagger * 1.2)));

    const startX = sr.left + sr.width / 2;
    const startY = sr.top  + sr.height / 2;
    const endX   = dr.left + dr.width / 2;
    const endY   = dr.top  + dr.height / 2;

    // Quadratic bezier with control point pulled toward wheel centre —
    // gives every rung a curved orbital trajectory.
    const ctrlX = _lerp((startX + endX) / 2, cx, 0.45);
    const ctrlY = _lerp((startY + endY) / 2, cy, 0.45);
    const px = (1-u)*(1-u)*startX + 2*(1-u)*u*ctrlX + u*u*endX;
    const py = (1-u)*(1-u)*startY + 2*(1-u)*u*ctrlY + u*u*endY;

    // Size lerp from rung dims (horizontal plate) → wedge dims (small square-ish).
    const w = _lerp(sr.width, Math.min(dr.width, dr.height), u);
    const h = _lerp(sr.height,Math.min(dr.width, dr.height), u);
    const x = px - w / 2;
    const y = py - h / 2;

    const radius = _lerp(14, 200, _easeOut(u)); // rectangle → almost-circle
    const blend = _easeOut(_clamp01((u - 0.25) / 0.55));
    const bg = `linear-gradient(135deg,
      color-mix(in oklab, ${cap.color1} ${blend*100}%, #FFBA00) 0%,
      color-mix(in oklab, ${cap.color2} ${blend*100}%, #8C6100) 100%)`;

    // Slight rotation toward the wedge's outward direction
    const dx = endX - cx, dy = endY - cy;
    const angle = Math.atan2(dy, dx) * 180 / Math.PI + 90;
    const rot = _lerp(0, angle, u);

    return (
      <div
        key={i}
        className="morph-twin morph-twin--wedge"
        style={{
          left: x, top: y,
          width: Math.max(0, w), height: Math.max(0, h),
          background: bg,
          borderRadius: radius,
          transform: `rotate(${rot.toFixed(2)}deg)`,
          transformOrigin: 'center center',
          boxShadow: `0 ${_lerp(8, 28, u)}px ${_lerp(20, 60, u)}px rgba(5,10,20,${_lerp(0.20, 0.45, u)})`,
          opacity: Math.max(0, 1 - _easeIn(Math.max(0, u - 0.85) / 0.15) * 0.4),
        }}
      />
    );
  });
}

/* ---------- helpers ---------- */

// Extract a representative colour stop from a CSS gradient string.
function _extractStop(grad, idx) {
  const m = grad.match(/#[0-9a-fA-F]{3,8}|rgba?\([^)]+\)/g);
  if (!m || !m.length) return grad;
  return m[Math.min(idx, m.length - 1)];
}

// Interpolate two polygon() clip-paths point-by-point.
function _interpolateClip(a, b, t) {
  const parse = s => {
    const inner = s.match(/polygon\(([^)]+)\)/);
    if (!inner) return null;
    return inner[1].split(',').map(pt => {
      const [x, y] = pt.trim().split(/\s+/);
      return [parseFloat(x), parseFloat(y)];
    });
  };
  const pa = parse(a), pb = parse(b);
  if (!pa || !pb || pa.length !== pb.length) return b;
  const pts = pa.map((p, i) => [
    _lerp(p[0], pb[i][0], t),
    _lerp(p[1], pb[i][1], t),
  ]);
  return 'polygon(' + pts.map(p => `${p[0]}% ${p[1]}%`).join(', ') + ')';
}

Object.assign(window, { MorphSpacer, MorphLayer });
