// Hero — premium physics-driven photo specimens (jitter-free rewrite).
//
//  Physical model:
//   • Each specimen has a CACHED home center (homeCx, homeCy) measured ONCE
//     after first layout, in hero-local coordinates.
//   • State per card: ox, oy (force-driven offset from home), vx, vy.
//   • Display transform layered ON TOP of state: parallax + ambient float +
//     scroll. These are decorative and never feed back into physics.
//   • Forces: spring toward home, damped magnetic pull toward cursor, drag
//     follows cursor 1:1, collisions push pairs apart with light bounce.
//
//  Interactions:
//   • Cinematic swirl-in on first load
//   • 3D rotateX/rotateY tied to cursor (real perspective)
//   • Grab any specimen → release with momentum
//   • Hover pulses (CSS ken-burns) to simulate motion
//   • Click without drag → burst animation

const { useEffect: useEffectH, useRef: useRefH, useState: useStateH } = React;

// Kinetic word swap — cycles through a list of italic display words with a
// vertical wipe + skew. Used in the hero title.
function KineticWord({ words, interval = 2600 }) {
  const [i, setI] = useStateH(0);
  const [phase, setPhase] = useStateH("in"); // "in" | "out"
  useEffectH(() => {
    const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduced) return;
    let t1, t2;
    const loop = () => {
      t1 = setTimeout(() => {
        setPhase("out");
        t2 = setTimeout(() => {
          setI((x) => (x + 1) % words.length);
          setPhase("in");
          loop();
        }, 550);
      }, interval);
    };
    loop();
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [words, interval]);
  return (
    <span className="swap">
      <span key={i} className={`swap__word is-${phase}`}>{words[i]}</span>
    </span>
  );
}

const SPECIMENS = [
  { x: 11, y: 22, z: -240, r: -8,  w: 200, h: 260, tone: "rose",  cap: "TAIPEI · 05:42",
    case: { brand: "Hoshinoya Guguan", note: "3 nights · feature" } },
  { x: 79, y: 17, z: -300, r:  6,  w: 220, h: 280, tone: "gold",  cap: "ULURU · 19:08",
    case: { brand: "Tourism Australia", note: "Editorial · 2022" } },
  { x: 18, y: 70, z: -120, r:  4,  w: 230, h: 300, tone: "sage",  cap: "DOLOMITES",
    case: { brand: "Black Diamond",    note: "Apparel · 2024" } },
  { x: 84, y: 64, z: -180, r: -5,  w: 210, h: 270, tone: "terra", cap: "PAMIR HWY",
    case: { brand: "Open slot",        note: "Outdoor partner · 2026" } },
  { x: 50, y: 88, z: -340, r:  2,  w: 190, h: 240, tone: "ink",   cap: "OSAKA · NIGHT",
    case: { brand: "DJI",              note: "Camera launch · 2024" } },
  { x: 6,  y: 46, z: -420, r:  10, w: 170, h: 220, tone: "cream", cap: "SIWA OASIS",
    case: { brand: "Aman",             note: "Hospitality · 2022" } },
  { x: 92, y: 38, z: -380, r: -3,  w: 180, h: 240, tone: "terra", cap: "ANTIGUA",
    case: { brand: "Patagonia",        note: "Provisions · 2023" } },
  { x: 38, y: 12, z: -200, r:  7,  w: 160, h: 210, tone: "sage",  cap: "KISO VALLEY",
    case: { brand: "Open slot",        note: "Tohoku · summer 26" } },
  { x: 62, y: 92, z: -260, r: -7,  w: 200, h: 260, tone: "rose",  cap: "SUMATRA",
    case: { brand: "Casetify",         note: "Lifestyle · 2023" } },
];

function Hero({ onInquire }) {
  const fieldRef = useRefH(null);

  useEffectH(() => {
    const field = fieldRef.current;
    if (!field) return;
    const els = Array.from(field.querySelectorAll(".specimen"));

    // ────────────────────────────────────────────────────────────────
    // measure CACHED home positions (hero-local), once after layout
    // ────────────────────────────────────────────────────────────────
    let heroW = field.clientWidth;
    let heroH = field.clientHeight;
    const home = els.map((el) => {
      // pre-transform: reset transform briefly to read raw layout coords
      const prev = el.style.transform;
      el.style.transform = "none";
      const r = el.getBoundingClientRect();
      const hr = field.getBoundingClientRect();
      const cx = r.left + r.width / 2 - hr.left;
      const cy = r.top + r.height / 2 - hr.top;
      el.style.transform = prev;
      return { cx, cy, w: r.width, h: r.height };
    });

    // observe resizes — update cached home & viewport
    const ro = new ResizeObserver(() => {
      const hr = field.getBoundingClientRect();
      heroW = hr.width; heroH = hr.height;
      els.forEach((el, i) => {
        const prev = el.style.transform;
        el.style.transform = "none";
        const r = el.getBoundingClientRect();
        home[i].cx = r.left + r.width / 2 - hr.left;
        home[i].cy = r.top + r.height / 2 - hr.top;
        home[i].w = r.width;
        home[i].h = r.height;
        el.style.transform = prev;
      });
    });
    ro.observe(field);

    // ────────────────────────────────────────────────────────────────
    // state: per-specimen physical offsets + velocity
    // ────────────────────────────────────────────────────────────────
    const state = els.map((_, i) => {
      const a = (i / els.length) * Math.PI * 2 + Math.random() * 0.4;
      const r = 600 + Math.random() * 400;
      return {
        ox: Math.cos(a) * r,
        oy: Math.sin(a) * r,
        vx: 0, vy: 0,
        rZ: SPECIMENS[i].r + (Math.random() - .5) * 60,
      };
    });

    let mAbsX = window.innerWidth / 2, mAbsY = window.innerHeight / 2;
    let nx = 0, ny = 0;     // normalized cursor in hero (-1..1)
    let cx = 0, cy = 0;     // smoothed
    let scroll = window.scrollY;
    let drag = null;
    const t0 = performance.now();
    let heroRectTop = 0, heroRectLeft = 0;
    let inHero = true;

    const updateHeroRect = () => {
      const r = field.getBoundingClientRect();
      heroRectTop = r.top; heroRectLeft = r.left;
    };
    updateHeroRect();

    // ────────────────────────────────────────────────────────────────
    // input handlers
    // ────────────────────────────────────────────────────────────────
    const onMove = (e) => {
      mAbsX = e.clientX; mAbsY = e.clientY;
      // local
      const lx = mAbsX - heroRectLeft;
      const ly = mAbsY - heroRectTop;
      nx = (lx / heroW - 0.5) * 2;
      ny = (ly / heroH - 0.5) * 2;
      inHero = lx >= 0 && lx <= heroW && ly >= 0 && ly <= heroH;
      if (drag) {
        drag.hist.push({ x: e.clientX, y: e.clientY, t: performance.now() });
        if (drag.hist.length > 6) drag.hist.shift();
      }
    };

    const onScroll = () => {
      scroll = window.scrollY;
      updateHeroRect();
    };
    const onResize = () => { updateHeroRect(); };

    window.addEventListener("mousemove", onMove);
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onResize);

    const onDown = (e) => {
      const t = e.target.closest(".specimen");
      if (!t) return;
      const i = els.indexOf(t);
      if (i < 0) return;
      e.preventDefault();
      t.classList.add("is-dragging");
      drag = {
        i,
        sx: e.clientX, sy: e.clientY,
        ox0: state[i].ox, oy0: state[i].oy,
        hist: [{ x: e.clientX, y: e.clientY, t: performance.now() }],
        moved: 0,
      };
    };
    const onUp = () => {
      if (!drag) return;
      const t = els[drag.i];
      t.classList.remove("is-dragging");
      const h = drag.hist;
      let vx = 0, vy = 0;
      if (h.length >= 2) {
        const a = h[0], b = h[h.length - 1];
        const dt = Math.max(0.001, (b.t - a.t) / 16);
        vx = (b.x - a.x) / dt;
        vy = (b.y - a.y) / dt;
      }
      state[drag.i].vx = Math.max(-35, Math.min(35, vx * 0.6));
      state[drag.i].vy = Math.max(-35, Math.min(35, vy * 0.6));
      if (drag.moved < 6) {
        t.classList.remove("is-burst");
        void t.offsetWidth;
        t.classList.add("is-burst");
        setTimeout(() => t.classList.remove("is-burst"), 1200);
        // Gentle click (not a drag) → open the postcard for this specimen
        const sp = SPECIMENS[drag.i];
        if (sp) {
          window.dispatchEvent(new CustomEvent("postcard:open", { detail: sp }));
        }
      }
      drag = null;
    };
    field.addEventListener("mousedown", onDown);
    window.addEventListener("mouseup", onUp);

    // Hover state — flag the specimen the cursor is over
    const onOver = (e) => {
      const t = e.target.closest(".specimen");
      els.forEach((el) => el.classList.toggle("is-hover", el === t));
    };
    const onLeave = () => els.forEach((el) => el.classList.remove("is-hover"));
    field.addEventListener("mouseover", onOver);
    field.addEventListener("mouseleave", onLeave);

    // ────────────────────────────────────────────────────────────────
    // main loop
    // ────────────────────────────────────────────────────────────────
    let raf;
    let now = performance.now();
    const tick = (t) => {
      now = t || performance.now();
      const introT = Math.min(1, (now - t0) / 2400);
      const introEase = 1 - Math.pow(1 - introT, 3);
      const intro = introEase; // opacity factor

      // smoothed cursor (only updates while cursor is over hero)
      const targetX = inHero ? nx : 0;
      const targetY = inHero ? ny : 0;
      cx += (targetX - cx) * 0.06;
      cy += (targetY - cy) * 0.06;

      // 1) integrate per-specimen physics
      for (let i = 0; i < els.length; i++) {
        const s = SPECIMENS[i];
        const st = state[i];

        if (drag && drag.i === i) {
          // dragging — follow cursor in hero coords
          const targetOx = drag.ox0 + (mAbsX - drag.sx);
          const targetOy = drag.oy0 + (mAbsY - drag.sy);
          const newVx = (targetOx - st.ox);
          const newVy = (targetOy - st.oy);
          st.vx = newVx;
          st.vy = newVy;
          st.ox = targetOx;
          st.oy = targetOy;
          drag.moved += Math.abs(newVx) + Math.abs(newVy);
          // tilt while dragging
          const targetR = s.r + (newVx * 0.5);
          st.rZ += (targetR - st.rZ) * 0.2;
          continue;
        }

        // intro: drift home with strong damping
        if (introT < 1) {
          st.vx *= 0.85;
          st.vy *= 0.85;
          st.vx += (-st.ox) * 0.06;
          st.vy += (-st.oy) * 0.06;
        } else {
          // magnetic pull toward cursor (gentle — only when cursor inside hero)
          if (inHero) {
            const cardX = home[i].cx + st.ox;
            const cardY = home[i].cy + st.oy;
            const localMx = mAbsX - heroRectLeft;
            const localMy = mAbsY - heroRectTop;
            const dx = localMx - cardX;
            const dy = localMy - cardY;
            const dist = Math.hypot(dx, dy);
            const radius = 380;
            if (dist < radius && dist > 12) {
              const force = (1 - dist / radius);
              const f = force * force * 0.6;   // gentle
              st.vx += (dx / dist) * f;
              st.vy += (dy / dist) * f;
            }
          }
          // spring toward home
          st.vx += (-st.ox) * 0.018;
          st.vy += (-st.oy) * 0.018;
          // damping (heavy → calm)
          st.vx *= 0.86;
          st.vy *= 0.86;
          // velocity cap (avoids runaway after a big throw)
          const vmag = Math.hypot(st.vx, st.vy);
          const VMAX = 30;
          if (vmag > VMAX) { st.vx *= VMAX / vmag; st.vy *= VMAX / vmag; }
        }
        st.ox += st.vx;
        st.oy += st.vy;
        // rotation eases to base
        const targetR = s.r + cx * 0.6;
        st.rZ += (targetR - st.rZ) * 0.08;
      }

      // 2) collisions — use cached home + state offsets, NOT live DOM rects
      for (let i = 0; i < els.length; i++) {
        for (let j = i + 1; j < els.length; j++) {
          const stA = state[i], stB = state[j];
          const ax = home[i].cx + stA.ox;
          const ay = home[i].cy + stA.oy;
          const bx = home[j].cx + stB.ox;
          const by = home[j].cy + stB.oy;
          const dx = bx - ax, dy = by - ay;
          const rA = (home[i].w + home[i].h) * 0.24;
          const rB = (home[j].w + home[j].h) * 0.24;
          const minDist = rA + rB;
          const dist = Math.hypot(dx, dy);
          if (dist > 0.5 && dist < minDist) {
            const overlap = (minDist - dist) * 0.5;
            const nxc = dx / dist;
            const nyc = dy / dist;
            const aDrag = drag && drag.i === i;
            const bDrag = drag && drag.i === j;
            if (!aDrag) {
              stA.ox -= nxc * overlap;
              stA.oy -= nyc * overlap;
              stA.vx -= nxc * 0.6;
              stA.vy -= nyc * 0.6;
            }
            if (!bDrag) {
              stB.ox += nxc * overlap;
              stB.oy += nyc * overlap;
              stB.vx += nxc * 0.6;
              stB.vy += nyc * 0.6;
            }
          }
        }
      }

      // 3) render
      for (let i = 0; i < els.length; i++) {
        const el = els[i];
        const s = SPECIMENS[i];
        const st = state[i];

        // decorative (don't feed back into physics)
        const tFloat = Math.sin(now / 2400 + i * 1.7) * 3.5;
        const scrollPush = scroll * (s.z / 1000);
        const parallaxX = cx * s.z * 0.10;
        const parallaxY = cy * s.z * 0.07;

        // 3D rotation
        const rotY = -cx * 12 + (drag && drag.i === i ? st.vx * 0.4 : 0);
        const rotX =  cy * 7;

        // intro scale 0.78 → 1
        const sc = 0.78 + 0.22 * introEase;

        const tx = st.ox + parallaxX;
        const ty = st.oy + parallaxY + tFloat + scrollPush;

        el.style.transform =
          `translate3d(${tx}px, ${ty}px, 0) ` +
          `rotateY(${rotY}deg) rotateX(${rotX}deg) rotateZ(${st.rZ}deg) ` +
          `scale(${sc})`;
        el.style.opacity = intro;
      }

      const title = field.parentElement.querySelector(".hero__title");
      if (title) {
        title.style.transform =
          `translate(-50%, calc(-52% + ${scroll * -0.18}px)) translate3d(${cx * -6}px, ${cy * -5}px, 0)`;
        title.style.opacity = Math.max(0, introT * 1.6 - 0.6);
      }

      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      field.removeEventListener("mousedown", onDown);
      window.removeEventListener("mouseup", onUp);
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onResize);
      field.removeEventListener("mouseover", onOver);
      field.removeEventListener("mouseleave", onLeave);
      ro.disconnect();
      cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <section className="hero" id="top" data-screen-label="01 Hero">
      <i className="crop tl" aria-hidden="true"></i>
      <i className="crop tr" aria-hidden="true"></i>
      <i className="crop bl" aria-hidden="true"></i>
      <i className="crop br" aria-hidden="true"></i>
      <div className="hero__edition">
        <b>I.</b> Field journal<br/>
        <em>Volume IV</em><br/>
        Hualien · {new Date().toLocaleDateString("en-GB", { day: "2-digit", month: "short" })}
      </div>
      <div className="hero__seal">
        Independent studio<br/>
        Est. <em>2022</em><br/>
        50.11°N · 8.68°E
      </div>
      <div className="hero__field" ref={fieldRef}>
        {SPECIMENS.map((s, i) => (
          <div
            key={i}
            className="specimen"
            data-depth={s.z}
            data-rot={s.r}
            data-cursor="click · drag · throw"
            style={{
              left: `${s.x}%`,
              top: `${s.y}%`,
              width: s.w,
              height: s.h,
              marginLeft: -s.w / 2,
              marginTop: -s.h / 2,
              opacity: 0,
              transformStyle: "preserve-3d",
              willChange: "transform",
            }}
          >
            <Photo tone={s.tone} cap={s.cap} />
            {s.case ? (
              <div className="specimen__case" aria-hidden="true">
                <b>{s.case.brand}</b>
                {s.case.note}
              </div>
            ) : null}
          </div>
        ))}
      </div>

      <div className="hero__title">
        <div className="kicker kicker--ruled" style={{ marginBottom: 32 }}>
          Field journal · vol. IV · Taiwan
        </div>
        <h1>
          Two travelers,<br/>
          one <KineticWord words={["ongoing", "unfolding", "living", "open-ended"]} /> story.
        </h1>
        <p className="sub">
          Somewhere between Hualien and the next train.<br/>
          The journal — chapters, places, the brands we travel with.
        </p>
      </div>

      <div className="hero__pill">
        Drag a frame · scroll to read <span className="arr">↓</span>
      </div>

      <aside className="hero__sticky" aria-hidden="true">
        <span className="hero__sticky-arrow">↰</span>
        <p>
          tap any of these — they're<br/>
          <em>postcards from the road.</em>
        </p>
        <small>— g &amp; m</small>
      </aside>
    </section>
  );
}

window.Hero = Hero;
