// Norton-Gauss · Spline magnet
// ─────────────────────────────────────────────────────────────
// Mounts a <spline-viewer> inside a wrapper that applies a
// "magnetic field" interaction: the spline scene smoothly translates
// toward the cursor as it moves over the section, with eased return.
//
// Loading state: shows a Three.js placeholder mesh until Spline's
// runtime fires the 'load' event. Provides a graceful fallback if
// the Spline scene URL is unreachable.

const { useEffect: smUE, useRef: smUR, useState: smUS, useMemo: smUM } = React;

function SplineMagnet({
  sceneUrl,
  strength = 0.18,          // how far it follows cursor (0..1, of section radius)
  range = 0.6,              // 0..1 fraction of viewport to start pulling
  hint = 'Move your cursor — the field pulls.',
}) {
  const wrapRef = smUR(null);
  const viewerRef = smUR(null);
  const fallbackRef = smUR(null);
  const [loaded, setLoaded] = smUS(false);
  const [failed, setFailed] = smUS(false);

  // If no scene URL is supplied, skip Spline entirely — the Three.js
  // fallback IS the centerpiece. This avoids 404s & infinite-load hangs.
  const hasUrl = !!(sceneUrl && /^https?:\/\//.test(sceneUrl));

  // Mount the <spline-viewer> imperatively, only if a URL is supplied.
  smUE(() => {
    if (!hasUrl) return;
    if (!wrapRef.current) return;
    let disposed = false;
    let loadTimeout;

    // Lazy-load the Spline viewer custom element only when we actually have
    // a scene URL — keeps the megabyte bundle off the critical path.
    function ensureViewerScript() {
      if (window.__ngSplineScript) return window.__ngSplineScript;
      window.__ngSplineScript = new Promise((resolve) => {
        const s = document.createElement('script');
        s.type = 'module';
        s.src = 'https://unpkg.com/@splinetool/viewer@1.9.28/build/spline-viewer.js';
        s.onload = () => resolve();
        s.onerror = () => resolve();
        document.head.appendChild(s);
      });
      return window.__ngSplineScript;
    }

    ensureViewerScript().then(() => {
      function waitDefined(retry) {
        if (disposed) return;
        const isDefined = !!(customElements && customElements.get && customElements.get('spline-viewer'));
        if (!isDefined) {
          if (retry > 50) { setFailed(true); return; }
          setTimeout(() => waitDefined(retry + 1), 100);
          return;
        }
        mountViewer();
      }
      waitDefined(0);
    });

    function mountViewer() {
      if (disposed || !wrapRef.current) return;
      const v = document.createElement('spline-viewer');
      v.setAttribute('url', sceneUrl);
      v.setAttribute('loading-anim-type', 'none');
      v.style.width = '100%';
      v.style.height = '100%';
      v.style.display = 'block';
      v.style.opacity = '0';
      v.style.transition = 'opacity 0.8s ease';
      v.addEventListener('load', () => {
        if (disposed) return;
        v.style.opacity = '1';
        setLoaded(true);
      });
      v.addEventListener('error', () => { if (!disposed) setFailed(true); });
      viewerRef.current = v;
      wrapRef.current.appendChild(v);

      // Hard timeout — if Spline doesn't load in 5s, give up
      loadTimeout = setTimeout(() => {
        if (disposed) return;
        if (!loaded) {
          setFailed(true);
          if (viewerRef.current && viewerRef.current.parentNode) {
            viewerRef.current.parentNode.removeChild(viewerRef.current);
          }
        }
      }, 5000);
    }

    return () => {
      disposed = true;
      clearTimeout(loadTimeout);
      if (viewerRef.current && viewerRef.current.parentNode) {
        viewerRef.current.parentNode.removeChild(viewerRef.current);
      }
    };
  }, [sceneUrl, hasUrl]);

  // Mount a Three.js fallback mesh BEHIND the spline-viewer.
  // Defer until the section approaches the viewport so we don't spin up
  // a second WebGL context behind the hero's one for nothing.
  smUE(() => {
    if (!fallbackRef.current) return;
    let dispose = () => {};
    let mounted = true;
    let started = false;

    const start = () => {
      if (started) return;
      started = true;
      function tryMount(retry) {
        if (!mounted) return;
        if (!window.THREE || !window.NGHeroMesh) {
          if (retry > 60) return;
          setTimeout(() => tryMount(retry + 1), 80);
          return;
        }
        dispose = window.NGHeroMesh(fallbackRef.current, { decorative: true, meshScale: 0.78 });
      }
      tryMount(0);
    };

    // Use IntersectionObserver to wait for the section to approach the viewport
    const section = fallbackRef.current.closest('.innovation-spline') || fallbackRef.current;
    let io = null;
    if (typeof IntersectionObserver !== 'undefined') {
      io = new IntersectionObserver((entries) => {
        if (entries.some((e) => e.isIntersecting)) start();
      }, { rootMargin: '300px' });
      io.observe(section);
    } else {
      // No IO support — just mount immediately
      start();
    }

    return () => {
      mounted = false;
      if (io) io.disconnect();
      dispose && dispose();
    };
  }, []);

  // Magnetic field — the viewer + fallback container translate toward
  // the cursor as it moves over the section.
  smUE(() => {
    const wrap = wrapRef.current;
    if (!wrap) return;
    let raf = 0;
    let cx = 0, cy = 0;        // current translate
    let tx = 0, ty = 0;        // target translate
    const section = wrap.closest('.innovation-section') || wrap.parentElement;

    function loop() {
      cx += (tx - cx) * 0.10;
      cy += (ty - cy) * 0.10;
      wrap.style.transform = `translate(${cx.toFixed(2)}px, ${cy.toFixed(2)}px)`;
      if (Math.abs(cx - tx) > 0.05 || Math.abs(cy - ty) > 0.05) {
        raf = requestAnimationFrame(loop);
      } else {
        raf = 0;
      }
    }

    function onMove(e) {
      if (!section) return;
      const r = section.getBoundingClientRect();
      // Only pull while cursor is within or near the section
      if (e.clientY < r.top - r.height * 0.2 || e.clientY > r.bottom + r.height * 0.2) {
        tx = 0; ty = 0;
        if (!raf) raf = requestAnimationFrame(loop);
        return;
      }
      // Normalised offset from section center
      const cxR = r.left + r.width / 2;
      const cyR = r.top + r.height / 2;
      const dx = (e.clientX - cxR) / (r.width * 0.5);
      const dy = (e.clientY - cyR) / (r.height * 0.5);
      // Clamp + apply strength * section radius
      const radius = Math.min(r.width, r.height) * range * strength;
      tx = Math.max(-1, Math.min(1, dx)) * radius;
      ty = Math.max(-1, Math.min(1, dy)) * radius;
      if (!raf) raf = requestAnimationFrame(loop);
    }
    function onLeave() {
      tx = 0; ty = 0;
      if (!raf) raf = requestAnimationFrame(loop);
    }

    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseleave', onLeave);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseleave', onLeave);
      cancelAnimationFrame(raf);
    };
  }, [strength, range]);

  return (
    <div className="spline-magnet-stage">
      {/* Three.js fallback — always rendered. Hidden only if Spline successfully loads. */}
      <div
        ref={fallbackRef}
        className={`spline-fallback ${(loaded && hasUrl && !failed) ? 'is-hidden' : ''}`}
        aria-hidden="true"
      />
      {/* Spline viewer is appended into wrapRef imperatively */}
      <div ref={wrapRef} className="spline-magnet-wrap" />

      <div className="spline-hint" aria-hidden="true">
        <span className="dot" />
        {hasUrl
          ? (failed ? 'Three.js fallback · drop a Spline URL in Tweaks' : (loaded ? hint : 'Loading scene…'))
          : 'Three.js neural field · drop a Spline URL in Tweaks'}
      </div>
    </div>
  );
}

Object.assign(window, { SplineMagnet });
