"use client";

/**
 * FLUID BACKGROUND — a live, cursor-reactive WebGL field (ogl) that sits BEHIND
 * the content. A full-screen fragment shader paints a flowing iridescent
 * metaball / fluid mesh in the brand palette (blue -> violet -> emerald). The
 * field bends, glows and FLOWS toward the pointer: a spring-smoothed pointer
 * position warps the domain-warped noise and injects a bright, additive glow
 * blob that trails the cursor with momentum.
 *
 * How the cursor-reactivity works:
 *   - We track the raw pointer in 0..1 UV space and LERP a smoothed pointer
 *     toward it every frame (momentum => the glow trails, never snaps).
 *   - We also track pointer *velocity* (smoothed delta) and feed it to the
 *     shader as `uVel`, so fast flicks visibly push/stretch the fluid.
 *   - In the shader, the metaballs and the domain-warp offset are pulled toward
 *     `uPointer`; a soft radial glow blooms at the pointer; warp strength scales
 *     with `uVel` so the surface "reacts" to how you move.
 *
 * Variants:
 *   - "hero": subtle, recedes behind the headline.
 *   - "cta":  bigger, brighter, faster — the closing dramatic moment.
 *
 * Performance & resilience:
 *   - Pauses rendering when the tab is hidden OR the canvas is offscreen (IO).
 *   - DPR capped at 1.6 so it never tanks fill-rate / LCP.
 *   - WebGL capability check: if no GL context, we render nothing and the CSS
 *     gradient fallback (always painted underneath) shows through.
 *   - prefers-reduced-motion: we never start the GL loop; the static CSS
 *     gradient fallback stands in.
 *   - Full cleanup on unmount (RAF, listeners, GL context lost).
 */
import { useEffect, useRef } from "react";
import { Renderer, Program, Mesh, Triangle, Vec2 } from "ogl";

type Variant = "hero" | "cta";

const FRAG = /* glsl */ `
precision highp float;

uniform float uTime;
uniform vec2  uRes;
uniform vec2  uPointer;   // smoothed pointer, 0..1 (y up)
uniform float uVel;       // smoothed pointer speed 0..~1
uniform float uIntensity; // variant intensity
uniform float uReveal;    // 0..1 fade-in on mount

varying vec2 vUv;

// brand palette
const vec3 C_BLUE   = vec3(0.231, 0.510, 0.965); // #3b82f6
const vec3 C_VIOLET = vec3(0.545, 0.361, 0.965); // #8b5cf6
const vec3 C_EMER   = vec3(0.063, 0.725, 0.506); // #10b981
const vec3 C_BG     = vec3(0.027, 0.027, 0.043); // #07070b

// hash / value noise
float hash(vec2 p){
  p = fract(p * vec2(123.34, 456.21));
  p += dot(p, p + 45.32);
  return fract(p.x * p.y);
}
float noise(vec2 p){
  vec2 i = floor(p);
  vec2 f = fract(p);
  vec2 u = f * f * (3.0 - 2.0 * f);
  float a = hash(i);
  float b = hash(i + vec2(1.0, 0.0));
  float c = hash(i + vec2(0.0, 1.0));
  float d = hash(i + vec2(1.0, 1.0));
  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
float fbm(vec2 p){
  float v = 0.0;
  float a = 0.5;
  for (int i = 0; i < 5; i++){
    v += a * noise(p);
    p *= 2.0;
    a *= 0.5;
  }
  return v;
}

// soft metaball field — sum of inverse-distance blobs that orbit + drift
float metaballs(vec2 p, vec2 ptr, float t, float speed){
  float f = 0.0;
  for (int i = 0; i < 5; i++){
    float fi = float(i);
    // each blob orbits on its own phase
    vec2 c = vec2(
      0.5 + 0.34 * sin(t * speed * (0.4 + fi * 0.12) + fi * 1.7),
      0.5 + 0.30 * cos(t * speed * (0.5 + fi * 0.09) + fi * 2.3)
    );
    // pull every blob a little toward the pointer => the field "flows" to it
    c = mix(c, ptr, 0.18 + 0.05 * fi);
    float r = 0.16 + 0.05 * sin(t * 0.6 + fi);
    f += r * r / (dot(p - c, p - c) + 0.0009);
  }
  return f;
}

void main(){
  vec2 uv = vUv;
  // correct for aspect so blobs stay round-ish
  float aspect = uRes.x / max(uRes.y, 1.0);
  vec2 p = uv;
  p.x *= aspect;
  vec2 ptr = uPointer;
  ptr.x *= aspect;

  float t = uTime;

  // domain warp — distortion grows toward the pointer and with pointer speed
  float dPtr = distance(p, ptr);
  float pull = exp(-dPtr * 2.4);                 // 1 near cursor -> 0 far
  float warpAmt = (0.18 + uVel * 0.9) * (0.4 + pull);
  vec2 q = p + warpAmt * vec2(
    fbm(p * 2.2 + vec2(t * 0.10, t * 0.07)),
    fbm(p * 2.2 + vec2(-t * 0.08, t * 0.12) + 5.2)
  );

  // base flowing fbm field
  float n = fbm(q * 1.7 + vec2(t * 0.05, -t * 0.04));

  // metaballs flowing toward the pointer
  float mb = metaballs(q, ptr, t, 0.7 + uVel * 1.2);
  float field = smoothstep(0.7, 1.9, mb) * 0.9 + n * 0.6;

  // iridescent color ramp across the field
  vec3 col = mix(C_BLUE, C_VIOLET, smoothstep(0.15, 0.7, field + n * 0.3));
  col = mix(col, C_EMER, smoothstep(0.55, 1.05, field + 0.18 * sin(t * 0.4 + uv.x * 3.0)));

  // composite onto the dark canvas by the field strength
  float lum = smoothstep(0.05, 1.2, field);
  vec3 outc = mix(C_BG, col, clamp(lum, 0.0, 1.0) * uIntensity);

  // pointer bloom — a bright additive glow that trails the cursor
  float glow = exp(-dPtr * (4.5 - uVel * 1.5)) * (0.45 + uVel * 0.6);
  vec3 glowCol = mix(C_VIOLET, C_EMER, 0.4 + 0.4 * sin(t * 0.7));
  outc += glowCol * glow * uIntensity;

  // subtle grain so it never bands on dark gradients
  float g = hash(uv * uRes.xy * 0.5 + t) - 0.5;
  outc += g * 0.018;

  // soft vignette keeps edges grounded in the canvas
  float vig = smoothstep(1.25, 0.25, length(uv - 0.5));
  outc = mix(C_BG, outc, 0.35 + 0.65 * vig);

  gl_FragColor = vec4(outc * uReveal, 1.0);
}
`;

const VERT = /* glsl */ `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main(){
  vUv = uv;
  gl_Position = vec4(position, 0.0, 1.0);
}
`;

export default function FluidBackground({
  variant = "hero",
  className,
}: {
  variant?: Variant;
  className?: string;
}) {
  const wrapRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const wrap = wrapRef.current;
    if (!wrap) return;

    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce) return; // CSS gradient fallback stands in

    // --- WebGL capability check ---
    let renderer: Renderer;
    try {
      renderer = new Renderer({
        alpha: false,
        antialias: false,
        dpr: Math.min(window.devicePixelRatio || 1, 1.6),
        powerPreference: "high-performance",
      });
    } catch {
      return; // no GL -> CSS fallback shows
    }
    const gl = renderer.gl;
    if (!gl) return;

    const canvas = gl.canvas as HTMLCanvasElement;
    canvas.className = "fluid__canvas";
    canvas.setAttribute("aria-hidden", "true");
    wrap.appendChild(canvas);

    const isCta = variant === "cta";
    // cta is bigger/faster but slightly toned so the centred headline stays
    // legible against it (the readability backdrop does the rest)
    const intensity = isCta ? 0.9 : 0.78;

    const geometry = new Triangle(gl);
    const program = new Program(gl, {
      vertex: VERT,
      fragment: FRAG,
      uniforms: {
        uTime: { value: 0 },
        uRes: { value: new Vec2(1, 1) },
        uPointer: { value: new Vec2(0.5, 0.5) },
        uVel: { value: 0 },
        uIntensity: { value: intensity },
        uReveal: { value: 0 },
      },
    });
    const mesh = new Mesh(gl, { geometry, program });

    const resize = () => {
      const w = wrap.clientWidth || window.innerWidth;
      const h = wrap.clientHeight || window.innerHeight;
      renderer.setSize(w, h);
      program.uniforms.uRes.value.set(gl.drawingBufferWidth, gl.drawingBufferHeight);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(wrap);

    // --- pointer tracking (raw -> smoothed, with velocity) ---
    const rawPtr = { x: 0.5, y: 0.5 };
    const smoothPtr = { x: 0.5, y: 0.5 };
    let vel = 0;
    let lastX = 0.5;
    let lastY = 0.5;

    const onPointerMove = (e: PointerEvent) => {
      const r = wrap.getBoundingClientRect();
      rawPtr.x = (e.clientX - r.left) / r.width;
      // flip Y so shader-space y is up and matches screen intuition
      rawPtr.y = 1 - (e.clientY - r.top) / r.height;
    };
    // listen on window so movement is tracked even over the content layer
    window.addEventListener("pointermove", onPointerMove, { passive: true });

    // --- visibility / offscreen pausing ---
    let running = false;
    let rafId = 0;
    let inView = true;
    let pageVisible = !document.hidden;
    const startTime = performance.now();
    let lastTick = startTime;

    const frame = (now: number) => {
      if (!running) return;
      const dt = Math.min((now - lastTick) / 1000, 0.05);
      lastTick = now;

      // momentum: lerp smoothed pointer toward raw
      const ease = 0.07;
      smoothPtr.x += (rawPtr.x - smoothPtr.x) * ease;
      smoothPtr.y += (rawPtr.y - smoothPtr.y) * ease;

      // velocity from smoothed delta, decayed for a soft trailing reaction
      const dx = smoothPtr.x - lastX;
      const dy = smoothPtr.y - lastY;
      lastX = smoothPtr.x;
      lastY = smoothPtr.y;
      const instVel = Math.min(Math.hypot(dx, dy) * 60, 1);
      vel += (instVel - vel) * 0.12;

      program.uniforms.uTime.value = (now - startTime) / 1000;
      program.uniforms.uPointer.value.set(smoothPtr.x, smoothPtr.y);
      program.uniforms.uVel.value = vel;
      // ease the reveal in over ~1s
      const rev = program.uniforms.uReveal.value as number;
      if (rev < 1) program.uniforms.uReveal.value = Math.min(1, rev + dt * 1.4);

      renderer.render({ scene: mesh });
      rafId = requestAnimationFrame(frame);
    };

    const start = () => {
      if (running || !inView || !pageVisible) return;
      running = true;
      lastTick = performance.now();
      rafId = requestAnimationFrame(frame);
    };
    const stop = () => {
      running = false;
      if (rafId) cancelAnimationFrame(rafId);
      rafId = 0;
    };

    const io = new IntersectionObserver(
      ([entry]) => {
        inView = entry.isIntersecting;
        if (inView) start();
        else stop();
      },
      { threshold: 0 }
    );
    io.observe(wrap);

    const onVisibility = () => {
      pageVisible = !document.hidden;
      if (pageVisible) start();
      else stop();
    };
    document.addEventListener("visibilitychange", onVisibility);

    start();

    return () => {
      stop();
      io.disconnect();
      ro.disconnect();
      window.removeEventListener("pointermove", onPointerMove);
      document.removeEventListener("visibilitychange", onVisibility);
      const ext = gl.getExtension("WEBGL_lose_context");
      ext?.loseContext();
      if (canvas.parentNode) canvas.parentNode.removeChild(canvas);
    };
  }, [variant]);

  return (
    <div
      ref={wrapRef}
      className={`fluid fluid--${variant} ${className ?? ""}`}
      aria-hidden="true"
    >
      {/* static gradient fallback — always painted underneath; the only thing
          shown under reduced-motion / no-WebGL */}
      <div className="fluid__fallback" />
    </div>
  );
}
