// OMNIX — Librería de animaciones estilo Aera Technology
// Reveal on scroll · CountUp · Draw lines · Node diagrams
// Todas sobrias, motivadas por física, máx. 600ms, sin decoración innecesaria.

// ── useReveal ────────────────────────────────────────────────
// Retorna { ref, visible }. Fade + subida sutil al entrar viewport.
//
// FILOSOFÍA: el contenido NO puede quedar invisible por un bug del observer.
// Default: visible=true. Sólo escondemos temporalmente si detectamos con
// certeza que el elemento está fuera de viewport en el primer paint —
// entonces animamos su entrada cuando entra. En cualquier caso de duda,
// mostramos.
const useReveal = (opts = {}) => {
  const ref = React.useRef(null);
  const [visible, setVisible] = React.useState(true); // ← default TRUE
  const armedRef = React.useRef(false); // ¿estamos animando entrada?

  // useLayoutEffect corre síncronamente antes del paint — leemos rect
  // y decidimos si queda fuera del viewport (armar animación) o no (dejar visible).
  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const r = ref.current.getBoundingClientRect();
    const vh = window.innerHeight || document.documentElement.clientHeight || 800;
    // Si está claramente FUERA del viewport (top > vh + 100), armamos entrada
    if (r.top > vh + 100) {
      armedRef.current = true;
      setVisible(false);
    }
  }, []);

  React.useEffect(() => {
    if (!armedRef.current) return; // ya visible, nada que hacer
    if (!ref.current) return;
    let disposed = false;
    let raf = null;

    const check = () => {
      if (disposed || !ref.current) return;
      const r = ref.current.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight || 800;
      if (r.top < vh + 100) {
        setVisible(true);
        cleanup();
      }
    };
    const scheduleCheck = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => { raf = null; check(); });
    };
    const cleanup = () => {
      window.removeEventListener('scroll', scheduleCheck);
      window.removeEventListener('resize', scheduleCheck);
      if (raf) { cancelAnimationFrame(raf); raf = null; }
    };

    window.addEventListener('scroll', scheduleCheck, { passive: true });
    window.addEventListener('resize', scheduleCheck, { passive: true });
    // Safety timer: si nada dispara scroll en 3s, mostrar de todas formas
    const safety = setTimeout(() => { setVisible(true); cleanup(); }, 3000);

    return () => {
      disposed = true;
      clearTimeout(safety);
      cleanup();
    };
  }, []);
  return { ref, visible };
};

// ── <Reveal> wrapper ──────────────────────────────────────────
const Reveal = ({ children, delay = 0, y = 24, duration = 520, as: Tag = 'div', style = {}, ...rest }) => {
  const { ref, visible } = useReveal();
  return (
    <Tag
      ref={ref}
      style={{
        opacity: visible ? 1 : 0,
        transform: visible ? 'translateY(0)' : `translateY(${y}px)`,
        transition: `opacity ${duration}ms cubic-bezier(0.2,0.7,0.2,1) ${delay}ms, transform ${duration}ms cubic-bezier(0.2,0.7,0.2,1) ${delay}ms`,
        willChange: 'opacity, transform',
        ...style,
      }}
      {...rest}
    >
      {children}
    </Tag>
  );
};

// ── <CountUp> — number counter tabular ─────────────────────
const CountUp = ({ from = 0, to, prefix = '', suffix = '', duration = 1400, decimals = 0, style = {} }) => {
  const { ref, visible } = useReveal({ threshold: 0.35 });
  const [value, setValue] = React.useState(from);
  React.useEffect(() => {
    if (!visible) return;
    const start = performance.now();
    let raf;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = 1 - Math.pow(1 - t, 3); // ease-out cubic
      setValue(from + (to - from) * eased);
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [visible]);
  const formatted = value.toLocaleString('en-US', {
    minimumFractionDigits: decimals, maximumFractionDigits: decimals,
  });
  return (
    <span ref={ref} style={{ fontVariantNumeric: 'tabular-nums', ...style }}>
      {prefix}{formatted}{suffix}
    </span>
  );
};

// ── <DrawLine> — SVG path que se dibuja al entrar viewport ────
const DrawLine = ({ d, stroke = '#1E1F26', strokeWidth = 1.5, duration = 1200, delay = 0, dashed = false, style = {} }) => {
  const { ref, visible } = useReveal({ threshold: 0.2 });
  const pathRef = React.useRef(null);
  const [len, setLen] = React.useState(0);
  React.useEffect(() => {
    if (pathRef.current) setLen(pathRef.current.getTotalLength());
  }, [d]);
  return (
    <g ref={ref}>
      <path
        ref={pathRef}
        d={d}
        fill="none"
        stroke={stroke}
        strokeWidth={strokeWidth}
        strokeLinecap="square"
        strokeDasharray={dashed ? '4 4' : len}
        strokeDashoffset={visible ? 0 : len}
        style={{
          transition: `stroke-dashoffset ${duration}ms cubic-bezier(0.2,0.7,0.2,1) ${delay}ms`,
          ...style,
        }}
      />
    </g>
  );
};

// ── <PulseDot> — nodo con pulse muy sutil ─────────────────────
const PulseDot = ({ cx, cy, r = 5, color = '#F4024C', pulse = true }) => (
  <>
    {pulse && (
      <circle cx={cx} cy={cy} r={r}>
        <animate attributeName="r" from={r} to={r * 3} dur="2.4s" repeatCount="indefinite"/>
        <animate attributeName="opacity" from="0.5" to="0" dur="2.4s" repeatCount="indefinite"/>
        <animate attributeName="stroke" values={color} dur="2.4s" repeatCount="indefinite"/>
        <animate attributeName="fill" values="none" dur="2.4s" repeatCount="indefinite"/>
      </circle>
    )}
    <circle cx={cx} cy={cy} r={r} fill={color}/>
  </>
);

// ── <FlowingParticle> — punto que recorre un path ─────────────
const FlowingParticle = ({ pathId, color = '#F4024C', duration = 3, size = 3 }) => (
  <circle r={size} fill={color}>
    <animateMotion dur={`${duration}s`} repeatCount="indefinite" rotate="auto">
      <mpath href={`#${pathId}`}/>
    </animateMotion>
  </circle>
);

// ── <TickerRow> — fila auto-scroll infinita (logos) ───────────
const TickerRow = ({ children, speed = 40, style = {} }) => {
  const items = React.Children.toArray(children);
  return (
    <div style={{ overflow: 'hidden', maskImage: 'linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent)', WebkitMaskImage: 'linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent)', ...style }}>
      <div style={{
        display: 'flex', gap: 64, whiteSpace: 'nowrap', width: 'max-content',
        animation: `omnix-marquee ${speed}s linear infinite`,
      }}>
        {items} {items} {items}
      </div>
    </div>
  );
};

// Inject marquee keyframes once
if (typeof document !== 'undefined' && !document.getElementById('omnix-anim-keyframes')) {
  const s = document.createElement('style');
  s.id = 'omnix-anim-keyframes';
  s.textContent = `
    @keyframes omnix-marquee { from { transform: translateX(0); } to { transform: translateX(-33.333%); } }
    @keyframes omnix-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
    @keyframes omnix-flow-dash { to { stroke-dashoffset: -200; } }
    .omnix-blink { animation: omnix-blink 1.6s ease-in-out infinite; }
    .omnix-flow-dash { stroke-dasharray: 6 8; animation: omnix-flow-dash 3s linear infinite; }
  `;
  document.head.appendChild(s);
}

Object.assign(window, { useReveal, Reveal, CountUp, DrawLine, PulseDot, FlowingParticle, TickerRow });
