/* global React, ReactDOM */
const { useEffect: useEffectAurora, useRef: useRefAurora } = React;

/* =================================================================== */
/*  ASPACE AURORA  —  cursor-reactive atmospheric field                 */
/*                                                                      */
/*  A single fixed canvas, mounted once per page.                       */
/*  Activates only when the cursor enters an element marked              */
/*  [data-aurora] (hero, AI panel, booking confirmation, etc.) —         */
/*  fades to nothing everywhere else.                                    */
/*                                                                      */
/*  Visual model — vapor cluster, NOT a glow circle.                   */
/*                                                                      */
/*  Old approach: stack of concentric radial gradients centred on the   */
/*  cursor → unavoidably reads as a blue spotlight. Replaced with:      */
/*                                                                      */
/*    A · Vapor cluster — 7 small elongated puffs that ORBIT the        */
/*        smoothed cursor on independent sine waves. They never align   */
/*        concentrically; the eye reads asymmetric drifting smoke.      */
/*    B · Tiny denser core — small whisper of brightness near the       */
/*        cursor, stretched along velocity. Fades to nothing on idle.   */
/*    C · Transient chromatic ripple — only fires on a fast cursor      */
/*        burst, anchored at the burst origin (NOT moving with cursor). */
/*        Decays over 720ms. Rare and intentional.                      */
/*                                                                      */
/*  Palette: dark teal · cold white · lavender · low-alpha electric     */
/*  blue. Saturation low; per-puff alpha cap is 0.05 (3× dimmer than    */
/*  the previous build).                                                */
/*                                                                      */
/*  Idle floor: blobs continue their gentle drift but at 0.4× alpha so  */
/*  the field almost disappears when the user isn't moving.             */
/*                                                                      */
/*  Cursor lag is heavier (lerp 0.035) so the atmosphere clearly drifts */
/*  behind the cursor — "moving through thin fog", not a glow tied to   */
/*  the pointer.                                                        */
/*                                                                      */
/*  Performance: full-viewport canvas at devicePixelRatio capped at 2,  */
/*  7 small ellipse fills per frame + 1–2 occasional layers. Pauses     */
/*  when tab is hidden / no [data-aurora] in view / activation near 0. */
/*                                                                      */
/*  Mobile / reduced-motion: skipped entirely. Cursor-reactive doesn't  */
/*  apply on touch and we don't add visual noise without interaction.   */
/* =================================================================== */

/* (No injected stylesheet — aurora is canvas-only, desktop-only.) */

function AuroraField() {
  const canvasRef = useRefAurora(null);

  useEffectAurora(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d', { alpha: true });
    if (!ctx) return;

    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const isCoarse = window.matchMedia('(pointer: coarse)').matches;
    const isMobile = window.innerWidth < 760 || isCoarse;

    // Mobile + reduced-motion: skip entirely. Cursor-reactive doesn't make
    // sense on touch devices, and we never want to add visual noise without
    // an interaction reason. The hero sections already carry their own
    // atmospheric design language; the aurora is a desktop-only refinement.
    if (reduced || isMobile) {
      canvas.style.display = 'none';
      return;
    }

    let dpr = Math.min(window.devicePixelRatio || 1, 2);
    let W = 0, H = 0;
    const resize = () => {
      W = window.innerWidth; H = window.innerHeight;
      canvas.width  = Math.floor(W * dpr);
      canvas.height = Math.floor(H * dpr);
      canvas.style.width  = W + 'px';
      canvas.style.height = H + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();

    /* ----- pointer + activation state ------------------------------ */
    let pX = W * 0.5, pY = H * 0.5;     // raw target
    let sX = pX, sY = pY;                // smoothed (delayed follow)
    let lastSX = sX, lastSY = sY;
    let vX = 0, vY = 0;                  // smoothed velocity
    let active = 0;                      // 0..1 envelope — over [data-aurora]?
    let visible = true;
    let pointerSeen = false;             // bail until first real pointer move

    /* ----- section rect cache -------------------------------------- */
    let auroraRects = [];
    const refreshRects = () => {
      const els = document.querySelectorAll('[data-aurora]');
      const out = [];
      for (let i = 0; i < els.length; i++) {
        const r = els[i].getBoundingClientRect();
        if (r.bottom < -80 || r.top > H + 80) continue;
        if (r.width < 1 || r.height < 1) continue;
        out.push(r);
      }
      auroraRects = out;
    };
    refreshRects();

    /* ----- listeners ----------------------------------------------- */
    const onMove = (e) => {
      pX = e.clientX; pY = e.clientY;
      pointerSeen = true;
    };
    const onLeave = () => { active = 0; };
    const onResize = () => { resize(); refreshRects(); };
    const onScroll = () => { refreshRects(); };
    const onVis = () => { visible = !document.hidden; };

    window.addEventListener('mousemove', onMove, { passive: true });
    document.addEventListener('mouseleave', onLeave);
    window.addEventListener('resize', onResize);
    window.addEventListener('scroll', onScroll, { passive: true });
    document.addEventListener('visibilitychange', onVis);

    // DOM mutations may add/remove [data-aurora] regions (route changes,
    // panels opening). Refresh rects on a mutation tick.
    let mo;
    if (typeof MutationObserver !== 'undefined') {
      mo = new MutationObserver(() => { refreshRects(); });
      mo.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-aurora'] });
    }

    /* ----- desaturated atmospheric palette ------------------------ */
    /* Old: bright cyan/white centred radial → "blue spotlight".      */
    /* New: dark teal, cold white, lavender + a restrained electric   */
    /* blue accent. Saturation drops, alpha drops ~3×.                */
    const HUES = [
      { r:  60, g: 120, b: 140, w: 0.85 }, // dark teal — primary atmosphere
      { r: 200, g: 215, b: 230, w: 0.55 }, // soft cold white — subtle breath
      { r: 175, g: 165, b: 205, w: 0.55 }, // faint lavender edge
      { r:  30, g: 130, b: 180, w: 0.45 }, // translucent electric blue (low alpha cap)
    ];

    /* ----- vapor cluster ------------------------------------------ */
    /* 7 small puffs that orbit the smoothed cursor, each on its own  */
    /* drift wave. They NEVER align concentrically — that's exactly   */
    /* what made the old version look like a flashlight. Each puff is */
    /* small, drawn as an elongated ellipse, low alpha. The sum reads */
    /* as irregular, smoke-like, breaking-apart vapor.                */
    const CLUSTER = 7;
    const blobs = [];
    for (let i = 0; i < CLUSTER; i++) {
      blobs.push({
        ang0:    (i / CLUSTER) * Math.PI * 2 + (i * 0.37) % 1,
        r0:      40 + (i % 4) * 28,                  // 40 / 68 / 96 / 124
        size:    72 + (i * 13) % 48,                 // 72–116 — small, never fills the screen
        col:     HUES[i % HUES.length],
        phaseX:  i * 0.93,
        phaseY:  i * 1.41,
        phaseR:  i * 0.71,
      });
    }

    /* ----- transient chromatic ripple — only on fast movement ----- */
    let ripple = { x: 0, y: 0, age: Infinity, intensity: 0 };
    const RIPPLE_MS = 720;

    const t0 = performance.now();
    let raf;

    const tick = () => {
      raf = requestAnimationFrame(tick);
      if (!visible) return;

      // Activation envelope — is cursor inside any [data-aurora] rect?
      let inside = false;
      if (pointerSeen) {
        for (let i = 0; i < auroraRects.length; i++) {
          const r = auroraRects[i];
          if (pX >= r.left && pX <= r.right && pY >= r.top && pY <= r.bottom) {
            inside = true; break;
          }
        }
      }
      const target = inside ? 0.85 : 0;       // never reaches full 1.0
      active += (target - active) * 0.04;     // gentler ease, ~700ms

      // Cheap exit when nothing to draw
      if (active < 0.005) {
        ctx.clearRect(0, 0, W, H);
        return;
      }

      const t  = (performance.now() - t0) / 1000;
      const now = performance.now();

      // Heavier cursor lag — lerp 0.035 (was 0.06). Atmosphere clearly
      // drifts behind the cursor instead of clinging to it.
      lastSX = sX; lastSY = sY;
      sX += (pX - sX) * 0.035;
      sY += (pY - sY) * 0.035;

      // Smoothed velocity (longer carry → smoother trails)
      const dx = sX - lastSX, dy = sY - lastSY;
      vX = vX * 0.90 + dx * 0.10;
      vY = vY * 0.90 + dy * 0.10;
      const speed     = Math.hypot(vX, vY);
      const speedClamp = Math.min(speed, 60);
      const speedNrm  = Math.min(speedClamp / 22, 1);   // 0..1 normalised
      const velAng    = speed > 0.01 ? Math.atan2(vY, vX) : 0;

      // Idle dampening — when not moving, alphas fall to ~0.4 so the
      // field almost disappears, just gentle drift remains.
      const motionFloor = 0.4 + speedNrm * 0.6;

      // Detect a "burst" (fast movement) → trigger the chromatic ripple
      if (speed > 5 && now - ripple.age > 220) {
        ripple = { x: sX, y: sY, age: now, intensity: Math.min(speed / 30, 1) };
      }

      // Clear
      ctx.clearRect(0, 0, W, H);

      // Internal screen-blend → colour layers brighten where they meet
      ctx.globalCompositeOperation = 'screen';

      /* ===== LAYER A · vapor cluster =============================== */
      /* 7 elongated puffs scattered around the smoothed cursor — each */
      /* with own drift, orbit and stretch. Top alpha cap is 0.05 per  */
      /* puff (was 0.16 for the old single bright disc).                */
      for (let i = 0; i < CLUSTER; i++) {
        const b = blobs[i];

        // Each puff has its own slowly evolving angle and radius
        const ang = b.ang0
                  + Math.sin(t * 0.27 + b.phaseX) * 0.55
                  + Math.cos(t * 0.19 + b.phaseY) * 0.30;
        const r   = b.r0
                  + Math.sin(t * 0.41 + b.phaseR) * 18
                  + Math.cos(t * 0.23 + b.phaseX) * 12;

        const bx = sX + Math.cos(ang) * r;
        const by = sY + Math.sin(ang) * r;

        // Stretch along velocity — fast cursor smears the ellipses
        const stretch = 1 + speedNrm * 1.6;
        const squash  = 1 / Math.sqrt(stretch);

        // Per-puff alpha — extremely subtle. motionFloor halves it on idle.
        const a = active * b.col.w * 0.05 * motionFloor;
        if (a < 0.002) continue;

        ctx.save();
        ctx.translate(bx, by);
        ctx.rotate(velAng);
        ctx.scale(stretch, squash);

        const g = ctx.createRadialGradient(0, 0, 0, 0, 0, b.size);
        g.addColorStop(0,    `rgba(${b.col.r},${b.col.g},${b.col.b},${a})`);
        g.addColorStop(0.55, `rgba(${b.col.r},${b.col.g},${b.col.b},${a * 0.35})`);
        g.addColorStop(1,    `rgba(${b.col.r},${b.col.g},${b.col.b},0)`);
        ctx.fillStyle = g;
        ctx.beginPath();
        ctx.arc(0, 0, b.size, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }

      /* ===== LAYER B · tiny denser core =========================== */
      /* A small whisper of brightness near the cursor — never a       */
      /* spotlight. Fades to nothing on idle. Stretches along motion.  */
      if (speedNrm > 0.04 || active > 0.4) {
        const coreA = active * 0.035 * motionFloor;
        const coreSize = 28 + speedNrm * 22;
        const coreStretch = 1 + speedNrm * 1.2;

        ctx.save();
        ctx.translate(sX, sY);
        ctx.rotate(velAng);
        ctx.scale(coreStretch, 1 / coreStretch);
        const cg = ctx.createRadialGradient(0, 0, 0, 0, 0, coreSize);
        cg.addColorStop(0,    `rgba(220,230,240,${coreA})`);
        cg.addColorStop(0.5,  `rgba(180,200,220,${coreA * 0.4})`);
        cg.addColorStop(1,    'rgba(180,200,220,0)');
        ctx.fillStyle = cg;
        ctx.beginPath();
        ctx.arc(0, 0, coreSize, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }

      /* ===== LAYER C · transient chromatic ripple ================== */
      /* Brief, off-cursor (anchored at the burst origin) so it reads  */
      /* as the atmosphere noticing the disturbance, not as a halo.    */
      const rAge = now - ripple.age;
      if (rAge < RIPPLE_MS) {
        const k    = rAge / RIPPLE_MS;            // 0 → 1
        const ease = 1 - Math.pow(1 - k, 3);      // ease-out cubic
        const ra   = ripple.intensity * (1 - ease) * 0.06 * active;
        if (ra > 0.002) {
          const r0 = 30  + ease * 80;
          const r1 = 110 + ease * 220;
          const rg = ctx.createRadialGradient(ripple.x, ripple.y, r0, ripple.x, ripple.y, r1);
          rg.addColorStop(0.0,  'rgba(180,200,220,0)');
          rg.addColorStop(0.55, `rgba(140,180,210,${ra * 0.45})`);
          rg.addColorStop(0.78, `rgba(170,160,210,${ra * 0.50})`);
          rg.addColorStop(0.95, `rgba(200,210,220,${ra * 0.20})`);
          rg.addColorStop(1.0,  'rgba(0,0,0,0)');
          ctx.fillStyle = rg;
          ctx.fillRect(0, 0, W, H);
        }
      }

      ctx.globalCompositeOperation = 'source-over';
    };

    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseleave', onLeave);
      window.removeEventListener('resize', onResize);
      window.removeEventListener('scroll', onScroll);
      document.removeEventListener('visibilitychange', onVis);
      if (mo) mo.disconnect();
    };
  }, []);

  return (
    <canvas
      ref={canvasRef}
      aria-hidden="true"
      style={{
        position: 'fixed',
        inset: 0,
        width: '100vw',
        height: '100vh',
        pointerEvents: 'none',
        // Sits ABOVE most overlays (AI panel z-9800, contact panel) so the
        // atmospheric haze can wash gently over premium surfaces too.
        // Stays below the menu (z-9900) and cookie banner (z-9999) so those
        // never get tinted. Max α here is 0.16 — never blocks legibility.
        zIndex: 9850,
      }}
    />
  );
}

window.AuroraField = AuroraField;

/* -------------------------------------------------------------------- */
/*  Auto-mount: one canvas per page, no need to thread it through        */
/*  every App() in every HTML file.                                      */
/* -------------------------------------------------------------------- */
(function mountAurora() {
  if (typeof window === 'undefined' || typeof document === 'undefined') return;
  const ready = () => {
    if (!window.React || !window.ReactDOM) { setTimeout(ready, 50); return; }
    if (document.getElementById('aspace-aurora-root')) return;
    const host = document.createElement('div');
    host.id = 'aspace-aurora-root';
    document.body.appendChild(host);
    ReactDOM.createRoot(host).render(React.createElement(AuroraField));
  };
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', ready);
  } else {
    ready();
  }
})();
