/* Mesh website — scroll-driven "scattered → unified" transition.
   Mirrors the Ramp "systems that never spoke → now sing together" beat:
   as you scroll through a tall pinned section, the heading swaps and the
   scattered accrual artefacts gather behind a single clean Mesh panel. */

const TX_CARDS = [
  // each: start (scattered) + end (organized behind) offsets from stage center, px
  { key: 'sheet',  start: { x: -290, y: -90,  r: -9 }, end: { x: -470, y: -150, r: -2 } },
  { key: 'gmail',  start: { x:  280, y: -120, r:  7 }, end: { x:  470, y: -150, r:  2 } },
  { key: 'slack',  start: { x: -320, y:  110, r: -6 }, end: { x: -470, y:  150, r: -2 } },
  { key: 'pdf',    start: { x: -110, y:  150, r:  5 }, end: { x: -250, y:  185, r:  0 } },
  { key: 'po',     start: { x:  110, y: -155, r: -8 }, end: { x:  250, y: -185, r:  0 } },
  { key: 'teams',  start: { x:  315, y:  95,  r:  8 }, end: { x:  470, y:  150, r:  2 } },
  { key: 'db',     start: { x:  140, y:  150, r:  6 }, end: { x:  250, y:  185, r:  0 } },
];

// Mobile scatter: a loose 2-3-2 "diamond" that sits BELOW the heading with a
// consistent gap (cards never overlap the text). cx is the horizontal slot
// (-1/0/+1) and row is 0/1/2 (top/middle/bottom); the middle row spreads wider
// than the two-card rows, giving the diamond silhouette. dx/dy jitter each card
// off the grid (px) and sc varies its size, so the rows look scattered by hand
// rather than perfectly aligned. Same order as TX_CARDS.
const TX_MOBILE = [
  { cx: -1, row: 0, r: -8, dx:  -8, dy: -16, sc: 1.05 },  // sheet — top-left, sits higher
  { cx:  1, row: 0, r:  6, dx:  10, dy:  28, sc: 0.97 },  // gmail — top-right, dips lower
  { cx: -1, row: 1, r: -6, dx:  -6, dy: -28, sc: 0.99 },  // slack — mid-left, rides up
  { cx:  0, row: 1, r:  5, dx:   6, dy:  18, sc: 1.06 },  // pdf   — mid-centre, lower + bigger
  { cx:  1, row: 1, r: -4, dx:   4, dy:  -6, sc: 0.98 },  // po    — mid-right
  { cx: -1, row: 2, r:  8, dx:  -4, dy:  24, sc: 1.02 },  // teams — bottom-left, drops
  { cx:  1, row: 2, r: -7, dx:  14, dy: -20, sc: 0.96 },  // db    — bottom-right, tucks up
];

function lerp(a, b, t) { return a + (b - a) * t; }
function clamp01(v) { return v < 0 ? 0 : v > 1 ? 1 : v; }
function easeInOut(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }
function easeOut(t) { return 1 - Math.pow(1 - t, 3); }

function TxCardBody({ k }) {
  switch (k) {
    case 'sheet':
      return (
        <div className="txc txc-sheet">
          <div className="txc-xl">accruals_FY26.xlsx</div>
          <div className="txc-grid">
            <div>Ramp</div><div className="n">$24,500</div>
            <div>AWS</div><div className="n">$88,240</div>
            <div>Gusto</div><div className="n">$16,080</div>
            <div className="muted">Notion</div><div className="n muted">??</div>
          </div>
        </div>
      );
    case 'gmail':
      return (
        <div className="txc txc-msg">
          <div className="txc-msg-top"><img src={window.__assets("brands/email_gmail.png")} alt="" /><span>Gmail</span></div>
          <div className="txc-msg-sub">Re: Q2 vendor invoice, still outstanding?</div>
          <div className="txc-msg-body">Hi, can you confirm the May figure before we close?</div>
        </div>
      );
    case 'slack':
      return (
        <div className="txc txc-msg">
          <div className="txc-msg-top"><img src={window.__assets("brands/msg_slack.png")} alt="" /><span>#finance</span></div>
          <div className="txc-msg-body">did we ever accrue for the AWS commit? 😬</div>
        </div>
      );
    case 'pdf':
      return (
        <div className="txc txc-pdf">
          <div className="txc-pdf-tag">invoice_4821.pdf</div>
          <div className="txc-pdf-amt">$24,500.00</div>
          <div className="txc-pdf-row">Net 30 · received 5 days ago</div>
        </div>
      );
    case 'po':
      return (
        <div className="txc txc-msg">
          <div className="txc-msg-top"><img src={window.__assets("brands/proc_coupa.png")} alt="" /><span>Procurement</span></div>
          <div className="txc-msg-sub">PO #4821 · approved</div>
          <div className="txc-msg-body">Awaiting matching invoice…</div>
        </div>
      );
    case 'teams':
      return (
        <div className="txc txc-msg">
          <div className="txc-msg-top"><img src={window.__assets("brands/msg_teams.png")} alt="" /><span>Teams</span></div>
          <div className="txc-msg-body">Where are we on the prepaid schedule?</div>
        </div>
      );
    case 'db':
      return (
        <div className="txc txc-db">
          <div className="txc-db-top"><img src={window.__assets("brands/db_snowflake.png")} alt="" /><span>GL_2026_05</span></div>
          <div className="txc-db-row"><span>6200 · Cloud</span><span className="n">88,240</span></div>
          <div className="txc-db-row"><span>6010 · SaaS</span><span className="n">16,080</span></div>
          <div className="txc-db-row muted"><span>6300 · ???</span><span className="n">—</span></div>
        </div>
      );
    default:
      return null;
  }
}

function ScatterToUnified() {
  const sectionRef = React.useRef(null);
  const cardRefs = React.useRef([]);
  const h1Ref = React.useRef(null);
  const h2Ref = React.useRef(null);
  const sub1Ref = React.useRef(null);
  const sub2Ref = React.useRef(null);
  const panelRef = React.useRef(null);
  const [rvRef, rvIn] = useReveal();

  React.useEffect(() => {
    const section = sectionRef.current;
    if (!section) return;
    let raf = 0;
    const MOBILE_MQ = window.matchMedia('(max-width: 880px)');

    const apply = () => {
      raf = 0;
      const vh = window.innerHeight || 800;
      const total = section.offsetHeight - vh;
      const top = section.getBoundingClientRect().top;
      const p = clamp01(total > 0 ? -top / total : 0);

      // Phones (≤880px) run the SAME pinned scatter→gather as desktop; only the
      // heading transform differs (mobile headings are grid-stacked in normal
      // flow, so no translateX(-50%)). A tighter offset scale keeps the scattered
      // artefacts spread across — but inside — the narrow viewport.
      const mobile = MOBILE_MQ.matches;

      // offset scale so it fits narrower screens
      const k = mobile
        ? Math.min(0.62, (window.innerWidth || 380) / 820)
        : Math.min(1, (window.innerWidth || 1200) / 1280);

      // cards gather over the first ~65% of the scroll
      const g = easeInOut(clamp01((p - 0.08) / 0.62));
      const vw = window.innerWidth || 390;   // vh already declared above (scroll calc)
      for (let i = 0; i < TX_CARDS.length; i++) {
        const el = cardRefs.current[i];
        if (!el) continue;
        const c = TX_CARDS[i];
        let x, y, r, s, op;
        if (mobile) {
          // 2-3-2 diamond grid below the heading. Each card REVEALS with a
          // zoom-in + fade (slightly staggered, so they pop in one after
          // another) as you scroll into the section; the grid then holds, and
          // finally converges to centre + dims so the unified panel takes its
          // place. Cards never overlap the heading text.
          const m = TX_MOBILE[i] || { cx: 0, row: 1, r: 0 };
          const midHalf = vw * 0.285;      // middle row (3 cards) half-spread
          const narrowHalf = vw * 0.195;   // top/bottom rows (2 cards) → narrower = diamond
          const rowGap = vh * 0.205;       // vertical distance between rows (fills the stage evenly)
          const gridCY = vh * 0.10;        // grid centre, just below the text (even top/bottom margins)
          const homeX = m.cx * (m.row === 1 ? midHalf : narrowHalf) + (m.dx || 0);
          const homeY = gridCY + (m.row - 1) * rowGap + (m.dy || 0);

          const rev = easeOut(clamp01((p - i * 0.022) / 0.15));  // staggered reveal
          const gth = easeInOut(clamp01((p - 0.40) / 0.26));     // converge + dim
          const BASE = 0.56;               // mobile card scale (slightly bigger)

          x = lerp(homeX, 0, gth);
          y = lerp(homeY, 0, gth);
          r = lerp(m.r, 0, gth);
          s = BASE * (m.sc || 1) * lerp(0.66, 1, rev) * lerp(1, 0.72, gth);
          op = rev * (1 - clamp01((p - 0.44) / 0.16));
        } else {
          x = lerp(c.start.x, c.end.x, g) * k;
          y = lerp(c.start.y, c.end.y, g) * k;
          r = lerp(c.start.r, c.end.r, g);
          s = lerp(1, 0.82, g);
          op = lerp(1, 0.16, clamp01((p - 0.3) / 0.4));
        }
        el.style.transform = `translate(-50%,-50%) translate(${x.toFixed(1)}px,${y.toFixed(1)}px) rotate(${r.toFixed(2)}deg) scale(${s.toFixed(3)})`;
        el.style.opacity = op.toFixed(3);
      }

      // headings crossfade around the midpoint (desktop centres them absolutely
      // with translateX(-50%); mobile leaves them in flow → translateY only)
      const headTX = mobile ? '' : 'translateX(-50%) ';
      // Non-overlapping windows: the scattered heading fully fades out BEFORE
      // the seamless one fades in (with a tiny gap), so the two never paint at
      // once — otherwise the different texts overlap into an unreadable ghost.
      const fadeOut = 1 - clamp01((p - 0.30) / 0.12);   // 1 → 0 over p 0.30–0.42
      const fadeIn = clamp01((p - 0.44) / 0.12);         // 0 → 1 over p 0.44–0.56
      if (h1Ref.current) { h1Ref.current.style.opacity = fadeOut; h1Ref.current.style.transform = `${headTX}translateY(${(-18 * (1 - fadeOut)).toFixed(1)}px)`; }
      if (sub1Ref.current) sub1Ref.current.style.opacity = fadeOut;
      if (h2Ref.current) { h2Ref.current.style.opacity = fadeIn; h2Ref.current.style.transform = `${headTX}translateY(${(18 * (1 - fadeIn)).toFixed(1)}px)`; }
      if (sub2Ref.current) sub2Ref.current.style.opacity = fadeIn;

      // central Mesh panel scales in late
      if (panelRef.current) {
        const pin = clamp01((p - 0.5) / 0.28);
        const e = easeInOut(pin);
        const scl = lerp(0.9, 1, e).toFixed(3);
        panelRef.current.style.opacity = e.toFixed(3);
        panelRef.current.style.transform = `translate(-50%,-50%) scale(${scl})`;
        if (mobile) {
          // Centre the panel in the space BELOW the heading/subtitle — NOT the
          // whole viewport. Centring a tall panel on a short phone (Safari's
          // toolbars shrink the dvh) pushes its top bar up into the subtitle.
          // A min-gap floor guarantees clearance; if the panel is taller than the
          // space it overflows the bottom rather than jamming into the text.
          const stage = section.querySelector('.tx-stage');
          const anchor = sub2Ref.current || section.querySelector('.tx-headings');  // visible subtitle bottom
          if (stage && anchor) {
            const panH = panelRef.current.offsetHeight;
            const anchorBottom = anchor.getBoundingClientRect().bottom - stage.getBoundingClientRect().top;
            let cy = (anchorBottom + vh) / 2;              // centre in the space below the subtitle
            const floor = anchorBottom + 28 + panH / 2;    // guarantee a top gap on short phones
            if (cy < floor) cy = floor;
            panelRef.current.style.top = cy.toFixed(1) + 'px';
          }
        } else {
          panelRef.current.style.top = '';   // desktop keeps the CSS top:50%
        }
      }
    };

    const onScroll = () => { if (!raf) raf = requestAnimationFrame(apply); };
    apply();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    if (MOBILE_MQ.addEventListener) MOBILE_MQ.addEventListener('change', onScroll);
    else if (MOBILE_MQ.addListener) MOBILE_MQ.addListener(onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      if (MOBILE_MQ.removeEventListener) MOBILE_MQ.removeEventListener('change', onScroll);
      else if (MOBILE_MQ.removeListener) MOBILE_MQ.removeListener(onScroll);
      cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <section className={'tx' + (rvIn ? ' tx-in' : '')} ref={el => { sectionRef.current = el; rvRef.current = el; }}>
      <div className="tx-stage">
        <div className="tx-headings">
          <div className="tx-head-wrap">
            <h2 className="tx-h tx-h1" ref={h1Ref}>From scattered information</h2>
            <h2 className="tx-h tx-h2" ref={h2Ref}>To one seamless workflow</h2>
          </div>
          <div className="tx-sub-wrap">
            <p className="tx-sub tx-sub1" ref={sub1Ref}>Spreadsheets, inboxes, chats, POs, and your GL, none of them talk to each other.</p>
            <p className="tx-sub tx-sub2" ref={sub2Ref}>Mesh pulls every signal into one auditable close.</p>
          </div>
        </div>

        <div className="tx-field">
          {TX_CARDS.map((c, i) => (
            <div className="tx-card" key={c.key} ref={el => (cardRefs.current[i] = el)}>
              <TxCardBody k={c.key} />
            </div>
          ))}

          <div className="tx-panel" ref={panelRef}>
            <div className="tx-panel-bar">
              <img className="tx-panel-mark" src={window.__assets("mesh-mark-blue.png")} alt="Mesh" />
              <span className="tx-panel-title">May close · reconciled</span>
              <span className="tx-panel-pill"><span className="tx-dot"></span> Closed</span>
            </div>
            <div className="tx-panel-body">
              {[
                ['credit-card', 'Ramp', '$24,500'],
                ['cloud', 'AWS', '$88,240'],
                ['users', 'Gusto', '$16,080'],
                ['activity', 'Datadog', '$12,600'],
                ['database', 'Snowflake', '$31,900'],
                ['message-square', 'Slack', '$9,400'],
                ['pen-tool', 'Figma', '$6,800'],
                ['file-text', 'Notion', '$4,200'],
              ].map(([ic, v, amt]) => (
                <div className="tx-panel-row" key={v}>
                  <span className="tx-src"><Icon name={ic} className="ic" /></span>
                  <span className="tx-vn">{v}</span>
                  <span className="tx-amt">{amt}</span>
                  <span className="tx-chk"><Icon name="check" className="ic" /></span>
                </div>
              ))}
              <div className="tx-panel-total"><span>Total accrued</span><span className="tx-amt">$193,720</span></div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { ScatterToUnified });
