// dr-tech.jsx — network graph canvas + scroll/telemetry effects
// Aesthetic: infrastructure / signal. Sober, slow, low-opacity.

// ------------------------------------------------------------------
// NetworkCanvas — animated node graph background
// ------------------------------------------------------------------
function NetworkCanvas({ density = 1, opacity = 1 }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2);
    let nodes = [];
    let raf = 0;
    let mouse = { x: -9999, y: -9999 };
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    const SIGNAL = '52,224,196';   // teal
    const DATA   = '110,168,254';  // blue

    function resize() {
      const rect = canvas.getBoundingClientRect();
      w = rect.width; h = rect.height;
      canvas.width = w * dpr; canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      const count = Math.round((w * h) / 26000 * density);
      nodes = new Array(Math.max(18, Math.min(count, 90))).fill(0).map(() => ({
        x: Math.random() * w,
        y: Math.random() * h,
        vx: (Math.random() - 0.5) * 0.18,
        vy: (Math.random() - 0.5) * 0.18,
        r: Math.random() * 1.6 + 0.8,
        data: Math.random() > 0.82, // a few "data" nodes glow blue
      }));
    }

    function step() {
      ctx.clearRect(0, 0, w, h);
      const maxDist = 132;
      // edges
      for (let i = 0; i < nodes.length; i++) {
        const a = nodes[i];
        for (let j = i + 1; j < nodes.length; j++) {
          const b = nodes[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d = Math.sqrt(dx * dx + dy * dy);
          if (d < maxDist) {
            const alpha = (1 - d / maxDist) * 0.5 * opacity;
            ctx.strokeStyle = `rgba(${SIGNAL},${alpha.toFixed(3)})`;
            ctx.lineWidth = 0.6;
            ctx.beginPath();
            ctx.moveTo(a.x, a.y);
            ctx.lineTo(b.x, b.y);
            ctx.stroke();
          }
        }
      }
      // nodes
      for (const n of nodes) {
        if (!reduced) {
          n.x += n.vx; n.y += n.vy;
          if (n.x < 0 || n.x > w) n.vx *= -1;
          if (n.y < 0 || n.y > h) n.vy *= -1;
          // gentle mouse attraction
          const mdx = mouse.x - n.x, mdy = mouse.y - n.y;
          const md = Math.sqrt(mdx * mdx + mdy * mdy);
          if (md < 160) {
            n.x += (mdx / md) * 0.25;
            n.y += (mdy / md) * 0.25;
          }
        }
        const col = n.data ? DATA : SIGNAL;
        ctx.fillStyle = `rgba(${col},${(n.data ? 0.95 : 0.6) * opacity})`;
        ctx.beginPath();
        ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
        ctx.fill();
        if (n.data) {
          ctx.fillStyle = `rgba(${col},${0.12 * opacity})`;
          ctx.beginPath();
          ctx.arc(n.x, n.y, n.r * 4, 0, Math.PI * 2);
          ctx.fill();
        }
      }
      raf = requestAnimationFrame(step);
    }

    const onMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      mouse.x = e.clientX - rect.left;
      mouse.y = e.clientY - rect.top;
    };
    const onLeave = () => { mouse.x = -9999; mouse.y = -9999; };

    resize();
    step();
    window.addEventListener('resize', resize);
    window.addEventListener('mousemove', onMove);
    canvas.addEventListener('mouseleave', onLeave);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', resize);
      window.removeEventListener('mousemove', onMove);
      canvas.removeEventListener('mouseleave', onLeave);
    };
  }, [density, opacity]);

  return (
    <canvas ref={ref} style={{
      position: 'absolute', inset: 0,
      width: '100%', height: '100%',
      pointerEvents: 'none',
    }} />
  );
}

// ------------------------------------------------------------------
// CustomCursor — dot + lagging ring (solo pointer: fine)
// ------------------------------------------------------------------
function CustomCursor() {
  const dot  = React.useRef(null);
  const ring = React.useRef(null);

  React.useEffect(() => {
    if (window.matchMedia('(pointer: coarse)').matches) return;
    let mx = -200, my = -200, rx = -200, ry = -200, raf;
    let isHover = false;

    const onMove = (e) => { mx = e.clientX; my = e.clientY; };
    const onOver  = (e) => { isHover = !!e.target.closest('a, button, [role="button"], label, .dr-btn'); };
    const onOut   = ()  => { isHover = false; };

    document.addEventListener('mousemove', onMove, { passive: true });
    document.addEventListener('mouseover',  onOver,  { passive: true });
    document.addEventListener('mouseout',   onOut,   { passive: true });

    const tick = () => {
      if (dot.current) {
        dot.current.style.transform = `translate(${mx}px, ${my}px)`;
      }
      rx += (mx - rx) * 0.11;
      ry += (my - ry) * 0.11;
      if (ring.current) {
        const sc = isHover ? 'scale(1.6)' : 'scale(1)';
        ring.current.style.transform = `translate(${rx}px, ${ry}px) ${sc}`;
        ring.current.style.borderColor = isHover ? 'rgba(45,212,191,0.7)' : 'rgba(45,212,191,0.35)';
      }
      raf = requestAnimationFrame(tick);
    };
    tick();

    return () => {
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseover',  onOver);
      document.removeEventListener('mouseout',   onOut);
      cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <>
      <div ref={dot} style={{
        position:'fixed', top:'-2.5px', left:'-2.5px',
        width:5, height:5, borderRadius:'50%',
        background:'var(--signal)', pointerEvents:'none',
        zIndex:9999, willChange:'transform',
      }} />
      <div ref={ring} style={{
        position:'fixed', top:'-15px', left:'-15px',
        width:30, height:30, borderRadius:'50%',
        border:'1px solid rgba(45,212,191,0.35)',
        pointerEvents:'none', zIndex:9998,
        willChange:'transform', transition:'border-color 0.2s, transform 0.08s linear',
      }} />
    </>
  );
}

// ------------------------------------------------------------------
// ScrollProgress — signal teal bar
// ------------------------------------------------------------------
function ScrollProgress() {
  const [p, setP] = React.useState(0);
  React.useEffect(() => {
    const onScroll = () => {
      const h = document.documentElement;
      const max = h.scrollHeight - h.clientHeight;
      setP(max > 0 ? (h.scrollTop / max) * 100 : 0);
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return (
    <div style={{
      position: 'fixed', top: 0, left: 0,
      height: 2, width: p + '%',
      background: 'linear-gradient(90deg, var(--signal) 0%, var(--pulse) 100%)',
      zIndex: 100, transition: 'width 0.05s linear',
    }} />
  );
}

// ------------------------------------------------------------------
// TelemetryTicker — Pilares reales de la firma (sin datos ficticios)
// ------------------------------------------------------------------
function TelemetryTicker() {
  const pillars = [
    { label: 'Diagnóstico sin costo',  desc: 'Primera sesión técnica gratuita'    },
    { label: 'Código es suyo',         desc: 'Sin lock-in ni dependencia oculta'  },
    { label: 'SLA documentado',        desc: 'Tiempos de respuesta por escrito'   },
    { label: 'Arquitectura primero',   desc: 'Diseño aprobado antes de construir' },
    { label: 'Ingeniería directa',     desc: 'Con un ingeniero, no un vendedor'   },
  ];
  return (
    <div style={{ background: 'var(--void-2)', borderTop: '1px solid var(--line)', borderBottom: '1px solid var(--line)' }}>
      <div className="container">
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 0 }} className="dr-pillars-strip">
          {pillars.map((p, i) => (
            <div key={p.label} style={{ padding: '15px 22px', borderRight: i < pillars.length - 1 ? '1px solid var(--line)' : 'none' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
                <span style={{ width: 5, height: 5, borderRadius: '50%', background: 'var(--signal)', flexShrink: 0, display: 'inline-block' }}></span>
                <span style={{ fontFamily: "'Space Grotesk', sans-serif", fontSize: 13, fontWeight: 500, color: 'var(--text)' }}>{p.label}</span>
              </div>
              <div style={{ fontFamily: "'IBM Plex Sans', sans-serif", fontSize: 11.5, color: 'var(--muted)', lineHeight: 1.4, paddingLeft: 13 }}>{p.desc}</div>
            </div>
          ))}
        </div>
      </div>
      <style>{`
        @keyframes dr-pulse { 0%,100% { opacity:1; } 50% { opacity:0.3; } }
        @media (max-width: 980px) { .dr-pillars-strip { grid-template-columns: 1fr 1fr !important; } }
        @media (max-width: 520px)  { .dr-pillars-strip { grid-template-columns: 1fr !important; } }
      `}</style>
    </div>
  );
}

// ------------------------------------------------------------------
// CountUp
// ------------------------------------------------------------------
function CountUp({ value, duration = 1600, formatter = (n) => n.toString() }) {
  const [n, setN] = React.useState(0);
  const ref = React.useRef(null);
  const fired = React.useRef(false);
  React.useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !fired.current) {
          fired.current = true;
          const start = performance.now();
          const tick = (t) => {
            const p = Math.min((t - start) / duration, 1);
            const ease = 1 - Math.pow(1 - p, 3);
            setN(value * ease);
            if (p < 1) requestAnimationFrame(tick);
            else setN(value);
          };
          requestAnimationFrame(tick);
        }
      });
    }, { threshold: 0.4 });
    io.observe(ref.current);
    return () => io.disconnect();
  }, [value, duration]);
  return <span ref={ref}>{formatter(n)}</span>;
}

// ------------------------------------------------------------------
// Reveal-on-scroll
// ------------------------------------------------------------------
function RevealMounter() {
  React.useEffect(() => {
    const els = document.querySelectorAll('[data-reveal]');
    if (!('IntersectionObserver' in window)) {
      els.forEach((el) => el.classList.add('is-visible'));
      return;
    }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) { e.target.classList.add('is-visible'); io.unobserve(e.target); }
      });
    }, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' });
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  });
  return null;
}

// ------------------------------------------------------------------
// Word reveal
// ------------------------------------------------------------------
function WordRevealMounter() {
  React.useEffect(() => {
    const els = document.querySelectorAll('[data-word-reveal]');
    els.forEach((el) => {
      if (el.dataset.processed) return;
      el.dataset.processed = '1';
      const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
      const textNodes = [];
      while (walker.nextNode()) textNodes.push(walker.currentNode);
      let i = 0;
      textNodes.forEach((tn) => {
        const parts = tn.nodeValue.split(/(\s+)/);
        const frag = document.createDocumentFragment();
        parts.forEach((p) => {
          if (/^\s+$/.test(p) || p === '') { frag.appendChild(document.createTextNode(p)); }
          else {
            const s = document.createElement('span');
            s.className = 'dr-wr-word';
            s.style.display = 'inline-block';
            s.style.opacity = '0';
            s.style.transform = 'translateY(0.4em)';
            s.style.transition = `opacity 0.6s ease ${i * 55}ms, transform 0.6s cubic-bezier(0.22,0.61,0.36,1) ${i * 55}ms`;
            s.textContent = p;
            frag.appendChild(s); i++;
          }
        });
        tn.parentNode.replaceChild(frag, tn);
      });
    });
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          e.target.querySelectorAll('.dr-wr-word').forEach((wd) => {
            wd.style.opacity = '1'; wd.style.transform = 'translateY(0)';
          });
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.15 });
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  });
  return null;
}

// ------------------------------------------------------------------
// Magnetic buttons
// ------------------------------------------------------------------
function MagneticMounter() {
  React.useEffect(() => {
    if (window.matchMedia('(pointer: coarse)').matches) return;
    const btns = document.querySelectorAll('.dr-btn-primary');
    const handlers = [];
    btns.forEach((b) => {
      const onMove = (e) => {
        const r = b.getBoundingClientRect();
        const x = e.clientX - (r.left + r.width / 2);
        const y = e.clientY - (r.top + r.height / 2);
        b.style.transform = `translate(${x * 0.16}px, ${y * 0.24}px)`;
      };
      const onLeave = () => { b.style.transform = ''; };
      b.addEventListener('mousemove', onMove);
      b.addEventListener('mouseleave', onLeave);
      b.style.transition = 'transform 0.35s cubic-bezier(0.22,0.61,0.36,1), background 0.2s, color 0.2s, box-shadow 0.2s';
      handlers.push([b, onMove, onLeave]);
    });
    return () => handlers.forEach(([b, m, l]) => { b.removeEventListener('mousemove', m); b.removeEventListener('mouseleave', l); });
  });
  return null;
}

Object.assign(window, {
  NetworkCanvas, ScrollProgress, TelemetryTicker, CustomCursor,
  CountUp, RevealMounter, WordRevealMounter, MagneticMounter,
});
