/* DotMorph.jsx — el corazón de la sección "quién está detrás · cómo trabajamos".
   Un campo de PUNTOS (el elemento de la marca) que MORFA en loop entre el rostro
   de Rubén en dot-matrix y los iconos del proceso: elige → cotiza → ejecuta →
   entrega → (rostro). Motion graphics real: las partículas viajan de una forma a
   otra, tiñéndose del color de cada categoría. La sección ES la pieza animada. */
(function () {
  function DotMorph(props) {
    const cvRef = React.useRef(null);

    React.useEffect(() => {
      const cv = cvRef.current;
      if (!cv) return;
      const ctx = cv.getContext('2d');
      const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      const driven = !!(props && props.driven);   // true = el scroll maneja el morph (guiado)
      const N = 1000;
      const lerp = (a, b, t) => a + (b - a) * t;
      const easeIO = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
      const RGB = { cream: [245, 244, 239], azure: [71, 122, 179], verde: [97, 142, 72], sienna: [186, 104, 73], yellow: [210, 180, 64] };
      // GLYPHS sólo para el decode de la etiqueta (modo auto); las partículas son PUNTOS
      const GLYPHS = '.\u00b7:\u2022oO*#@';

      // ── muestreo de una forma → N puntos normalizados centrados ──
      function buildShape(rawPts, color, label) {
        if (!rawPts.length) rawPts = [[0, 0]];
        let minX = 1e9, minY = 1e9, maxX = -1e9, maxY = -1e9;
        for (const [x, y] of rawPts) { if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y; }
        const bw = Math.max(1, maxX - minX), bh = Math.max(1, maxY - minY);
        const s = 0.9 / Math.max(bw, bh), cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;
        // shuffle + resample to N
        const pool = rawPts.slice();
        for (let i = pool.length - 1; i > 0; i--) { const j = (Math.random() * (i + 1)) | 0; const t = pool[i]; pool[i] = pool[j]; pool[j] = t; }
        const out = new Float32Array(N * 2);
        for (let i = 0; i < N; i++) {
          const p = pool[i % pool.length];
          out[i * 2] = (p[0] - cx) * s;
          out[i * 2 + 1] = (p[1] - cy) * s;
        }
        return { pts: out, color: RGB[color], label };
      }

      // ── iconos dibujados como shapes y muestreados a puntos ──
      function sampleDraw(drawFn) {
        const S = 220;
        const tc = document.createElement('canvas'); tc.width = S; tc.height = S;
        const t = tc.getContext('2d');
        t.fillStyle = '#000'; t.fillRect(0, 0, S, S);
        t.fillStyle = '#fff'; t.strokeStyle = '#fff'; t.lineJoin = 'round'; t.lineCap = 'round';
        drawFn(t, S);
        const d = t.getImageData(0, 0, S, S).data;
        const pts = [];
        for (let y = 0; y < S; y += 2) for (let x = 0; x < S; x += 2) { if (d[(y * S + x) * 4] > 128) pts.push([x, y]); }
        return pts;
      }
      const icoCursor = (t, S) => { const u = S / 100; t.beginPath(); const P = [[34, 18], [34, 80], [48, 66], [58, 86], [67, 82], [56, 62], [72, 60]]; t.moveTo(P[0][0] * u, P[0][1] * u); for (let i = 1; i < P.length; i++) t.lineTo(P[i][0] * u, P[i][1] * u); t.closePath(); t.fill(); };
      const icoHand = (t, S) => {
        const u = S / 100;
        t.beginPath(); t.roundRect(30 * u, 44 * u, 40 * u, 36 * u, 11 * u); t.fill();                 // palma
        const fx = [31, 41, 51, 61];
        for (let i = 0; i < 4; i++) { t.beginPath(); t.roundRect(fx[i] * u, 16 * u, 8 * u, 34 * u, 4 * u); t.fill(); }  // dedos
        t.save(); t.translate(32 * u, 56 * u); t.rotate(-0.95); t.beginPath(); t.roundRect(-4 * u, 0, 8 * u, 24 * u, 4 * u); t.fill(); t.restore();  // pulgar
      };
      const icoForm = (t, S) => {
        const u = S / 100;
        t.beginPath(); t.roundRect(28 * u, 22 * u, 44 * u, 60 * u, 5 * u); t.fill();   // hoja
        t.beginPath(); t.roundRect(42 * u, 15 * u, 16 * u, 11 * u, 3 * u); t.fill();   // clip
        t.globalCompositeOperation = 'destination-out';
        [36, 50, 64].forEach((y) => { t.beginPath(); t.roundRect(36 * u, y * u, 28 * u, 6 * u, 3 * u); t.fill(); });   // campos
        t.globalCompositeOperation = 'source-over';
      };
      const icoGear = (t, S) => {
        const u = S / 100, cx = S / 2, cy = S / 2, rOut = 44 * u, rIn = 30 * u, teeth = 7, tw = 0.34;
        t.beginPath();
        for (let i = 0; i < teeth; i++) {
          const a = (i / teeth) * Math.PI * 2, a0 = a - tw, a1 = a + tw;
          t.lineTo(cx + Math.cos(a0) * rIn, cy + Math.sin(a0) * rIn);
          t.lineTo(cx + Math.cos(a0) * rOut, cy + Math.sin(a0) * rOut);
          t.lineTo(cx + Math.cos(a1) * rOut, cy + Math.sin(a1) * rOut);
          t.lineTo(cx + Math.cos(a1) * rIn, cy + Math.sin(a1) * rIn);
        }
        t.closePath(); t.fill();
        t.beginPath(); t.arc(cx, cy, rIn, 0, 7); t.fill();
        t.globalCompositeOperation = 'destination-out'; t.beginPath(); t.arc(cx, cy, 15 * u, 0, 7); t.fill(); t.globalCompositeOperation = 'source-over';
      };
      const icoFolder = (t, S) => {
        const u = S / 100;
        t.beginPath(); t.roundRect(24 * u, 28 * u, 30 * u, 12 * u, 4 * u); t.fill();   // pestaña
        t.beginPath(); t.roundRect(22 * u, 34 * u, 56 * u, 42 * u, 6 * u); t.fill();   // cuerpo
      };

      const shapes = [];
      let positions = null, prevPos = null;
      let cur = 0, next = 1, phase = 'hold', phaseT = 0, lastEmit = -1;
      const HOLD = [3.0, 2.1, 2.1, 2.1, 2.1], MORPH = 0.9;

      // 5 formas dot-matrix construidas en SYNC (sin imagen):
      // mano (te llevo de la mano) → cursor (elige) → formulario (cotiza) → engranaje (ejecuta) → folder (entrega)
      shapes.push(buildShape(sampleDraw(icoHand), 'sienna', 'te llevo de la mano'));
      shapes.push(buildShape(sampleDraw(icoCursor), 'azure', 'elige'));
      shapes.push(buildShape(sampleDraw(icoForm), 'verde', 'cotiza'));
      shapes.push(buildShape(sampleDraw(icoGear), 'sienna', 'ejecuta'));
      shapes.push(buildShape(sampleDraw(icoFolder), 'yellow', 'entrega'));
      positions = new Float32Array(shapes[0].pts);
      prevPos = new Float32Array(shapes[0].pts);
      let started = true;

      let W = 0, H = 0, dpr = 1, CX = 0, CY = 0, SCALE = 1;
      function resize() {
        dpr = Math.min(2, window.devicePixelRatio || 1);
        const r = cv.getBoundingClientRect();
        W = cv.width = Math.max(1, Math.round(r.width * dpr));
        H = cv.height = Math.max(1, Math.round(r.height * dpr));
        CX = W / 2; CY = H * (driven ? 0.5 : 0.46); SCALE = Math.min(W, H) * 0.94;
      }
      resize();

      function blend(c1, c2, t) { return [lerp(c1[0], c2[0], t) | 0, lerp(c1[1], c2[1], t) | 0, lerp(c1[2], c2[2], t) | 0]; }

      let raf = 0, last = performance.now();
      function frame(now) {
        const dt = Math.min(0.05, (now - last) / 1000); last = now;
        ctx.clearRect(0, 0, W, H);
        if (started && positions) {
          let morphT = 0, col;
          if (driven) {
            // ── el scroll maneja la forma: 0=rostro → 1..4 = pasos del proceso ──
            const sp = Math.max(0, Math.min(1, window.__processScroll || 0));
            const sf = sp * (shapes.length - 1);
            let base = Math.floor(sf);
            if (base > shapes.length - 2) base = shapes.length - 2;
            if (base < 0) base = 0;
            const frac = sf - base;
            const hold = 0.30;                                  // descanso al inicio/fin de cada paso
            morphT = Math.max(0, Math.min(1, (frac - hold) / (1 - 2 * hold)));
            const e = easeIO(morphT);
            const a = shapes[base].pts, b = shapes[base + 1].pts;
            const wob = reduce ? 0 : 0.0014;
            for (let i = 0; i < N; i++) {
              const j = i * 2;
              positions[j]     = lerp(a[j], b[j], e)         + (reduce ? 0 : Math.sin(now * 0.001 + i) * wob);
              positions[j + 1] = lerp(a[j + 1], b[j + 1], e) + (reduce ? 0 : Math.cos(now * 0.0012 + i * 1.3) * wob);
            }
            col = blend(shapes[base].color, shapes[base + 1].color, e);
          } else {
            phaseT += dt;
            if (phase === 'hold') {
              if (phaseT >= HOLD[cur]) { phase = 'morph'; phaseT = 0; prevPos.set(positions); }
              col = shapes[cur].color;
            }
            if (phase === 'morph') {
              morphT = reduce ? 1 : Math.min(1, phaseT / MORPH);
              const e = easeIO(morphT);
              const tp = shapes[next].pts;
              for (let i = 0; i < N * 2; i++) positions[i] = lerp(prevPos[i], tp[i], e);
              col = blend(shapes[cur].color, shapes[next].color, e);
              if (morphT >= 1) { cur = next; next = (next + 1) % shapes.length; phase = 'hold'; phaseT = 0; }
            } else {
              const tp = shapes[cur].pts;
              for (let i = 0; i < N; i++) {
                const j = i * 2;
                const wob = reduce ? 0 : 0.0016;
                positions[j] = tp[j] + (reduce ? 0 : Math.sin(now * 0.001 + i) * wob);
                positions[j + 1] = tp[j + 1] + (reduce ? 0 : Math.cos(now * 0.0012 + i * 1.3) * wob);
              }
              col = shapes[cur].color;
            }
            if (cur !== lastEmit) { lastEmit = cur; try { window.dispatchEvent(new CustomEvent('dotmorph:step', { detail: { idx: cur } })); } catch (e) {} }
          }
          const rgb = 'rgb(' + col[0] + ',' + col[1] + ',' + col[2] + ')';
          const rad = Math.max(2, SCALE * 0.0095);
          // ── aura / glow externo en el color de la categoría (como el planeta) ──
          const auraR = SCALE * 0.66;
          const ag = ctx.createRadialGradient(CX, CY, 0, CX, CY, auraR);
          ag.addColorStop(0, 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',0.17)');
          ag.addColorStop(0.5, 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',0.05)');
          ag.addColorStop(1, 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',0)');
          ctx.fillStyle = ag; ctx.fillRect(CX - auraR, CY - auraR, auraR * 2, auraR * 2);
          // ── trinkets: pequeños orbes orbitando en el plano (como los satélites del planeta) ──
          if (!reduce) {
            ctx.fillStyle = rgb;
            for (let k = 0; k < 3; k++) {
              const a = now * 0.00035 * (k % 2 ? 1 : -1) + k * 2.1;
              const rr = SCALE * (0.52 + 0.07 * k);
              const tx = CX + Math.cos(a) * rr, ty = CY + Math.sin(a) * rr * 0.4;
              ctx.globalAlpha = 0.42; ctx.beginPath(); ctx.arc(tx, ty, rad * 0.7, 0, 7); ctx.fill();
            }
            ctx.globalAlpha = 1;
          }
          // ── los puntos del icono, con glow propio ──
          ctx.fillStyle = rgb;
          ctx.shadowColor = rgb; ctx.shadowBlur = rad * 1.4;
          for (let i = 0; i < N; i++) {
            const x = CX + positions[i * 2] * SCALE, y = CY + positions[i * 2 + 1] * SCALE;
            ctx.beginPath(); ctx.arc(x, y, rad, 0, 7); ctx.fill();
          }
          ctx.shadowBlur = 0;
          // etiqueta interna SOLO en modo auto; en guiado la narración de la sección es la voz
          if (!driven) {
            let lbl, lc, reveal;
            if (phase === 'morph' && morphT < 0.5) { lbl = shapes[cur].label; lc = shapes[cur].color; reveal = 1 - morphT / 0.5; }
            else if (phase === 'morph') { lbl = shapes[next].label; lc = shapes[next].color; reveal = (morphT - 0.5) / 0.5; }
            else { lbl = shapes[cur].label; lc = shapes[cur].color; reveal = 1; }
            let outS = '';
            for (let k = 0; k < lbl.length; k++) {
              const ch = lbl[k];
              if (ch === ' ' || ch === '\u00b7') { outS += ch; continue; }
              outS += (k < reveal * lbl.length) ? ch : GLYPHS[(Math.random() * GLYPHS.length) | 0];
            }
            ctx.font = '700 ' + (13 * dpr) + 'px "Cera Pro", system-ui, sans-serif';
            if (ctx.letterSpacing !== undefined) ctx.letterSpacing = (2.5 * dpr) + 'px';
            ctx.fillStyle = 'rgba(' + lc[0] + ',' + lc[1] + ',' + lc[2] + ',0.92)';
            ctx.fillText(outS.toUpperCase(), CX, H - 26 * dpr);
            if (ctx.letterSpacing !== undefined) ctx.letterSpacing = '0px';
          }
        }
        raf = requestAnimationFrame(frame);
      }
      raf = requestAnimationFrame(frame);

      let rt; const onResize = () => { clearTimeout(rt); rt = setTimeout(resize, 150); };
      window.addEventListener('resize', onResize);
      return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', onResize); };
    }, []);

    return React.createElement('canvas', { className: 'person-morph person-slot', ref: cvRef, 'aria-label': 'Rubén — del punto al proceso' });
  }

  window.DotMorph = DotMorph;
})();
