// js/velaris.jsx — WebGL simplex-noise background with cursor interaction

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

const FRAG = `
precision highp float;
varying vec2 vUv;
uniform vec2  u_resolution;
uniform float u_time;
uniform float u_grain;
uniform vec3  u_colors[4];
uniform vec3  u_bg;
uniform vec2  u_mouse;   // normalised 0-1, smoothed
uniform float u_mouseStr; // 0=idle, 1=active

vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }

float snoise(vec2 v){
  const vec4 C = vec4(0.211324865405187, 0.366025403784439,
           -0.577350269189626, 0.024390243902439);
  vec2 i  = floor(v + dot(v, C.yy));
  vec2 x0 = v - i + dot(i, C.xx);
  vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
  vec4 x12 = x0.xyxy + C.xxzz;
  x12.xy -= i1;
  i = mod(i, 289.0);
  vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
  vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
  m = m*m; m = m*m;
  vec3 x = 2.0 * fract(p * C.www) - 1.0;
  vec3 h = abs(x) - 0.5;
  vec3 ox = floor(x + 0.5);
  vec3 a0 = x - ox;
  m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
  vec3 g;
  g.x  = a0.x  * x0.x  + h.x  * x0.y;
  g.yz = a0.yz * x12.xz + h.yz * x12.yw;
  return 130.0 * dot(m, g);
}

void main() {
  vec2 uv = vUv;
  float ratio = u_resolution.x / u_resolution.y;

  /* aspect-correct coordinates, centred */
  vec2 p = uv - 0.5;
  p.x *= ratio;

  /* cursor in same space */
  vec2 mouse = (u_mouse - 0.5);
  mouse.x *= ratio;
  vec2 toMouse = mouse - p;
  float mDist  = length(toMouse);

  /* cursor repulsion/attraction — pushes noise coords away from cursor */
  float pull = u_mouseStr * 0.38 / (mDist * mDist + 0.08);
  vec2 distort = normalize(toMouse + 0.0001) * pull;

  float t = u_time * 0.18;
  vec2 pd = p + distort; // distorted coordinates for noise

  float n1 = snoise(pd * 0.4  + vec2( t * 0.20, -t * 0.30));
  float n2 = snoise(pd * 0.55 + vec2(-t * 0.15,  t * 0.25) + n1 * 0.25);
  float n3 = snoise(pd * 0.75 + vec2( t * 0.10, -t * 0.20) + n2 * 0.20);

  vec3 col = u_bg;

  /* vignette — keep edges dark */
  float dist = length(p) * 1.4;
  float vignette = 1.0 - smoothstep(0.25, 1.1, dist);

  /* colour blends */
  col = mix(col, u_colors[0], smoothstep(-0.1, 0.6, n1) * 0.92);
  col = mix(col, u_colors[1], smoothstep(-0.05, 0.65, n2) * 0.78);
  col = mix(col, u_colors[2], smoothstep(-0.2, 0.5, n3) * 0.68);
  col = mix(col, u_colors[3], smoothstep( 0.0, 0.7, n1 * n2) * 0.55);

  /* ambient centre glow */
  float glow = smoothstep(0.9, 0.0, dist) * 0.22;
  col += u_colors[0] * glow;

  /* cursor orb — fades out as mouse moves below the fold (vUv.y < 0.5) */
  float aboveFold = smoothstep(0.35, 0.65, vUv.y);
  float orbR   = 0.28;
  float orbSoft = 0.55;
  float orbAmt  = smoothstep(orbR, 0.0, mDist) * u_mouseStr * aboveFold;
  float orbRim  = (smoothstep(orbR + orbSoft, orbR * 0.3, mDist)
                 - smoothstep(orbR * 0.6,    0.0,        mDist)) * aboveFold;
  col += u_colors[1] * orbAmt  * 0.55;
  col += u_colors[0] * orbRim  * 0.30 * u_mouseStr;

  /* pinpoint highlight at exact cursor — hero only */
  float pinDist = length(toMouse);
  float pin = smoothstep(0.025, 0.0, pinDist) * u_mouseStr * aboveFold;
  col += vec3(1.0) * pin * 0.35;

  /* vignette darkens edges */
  col = mix(col * 0.15, col, vignette);

  /* subtle film grain */
  float grain = fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453 + u_time);
  col += (grain - 0.5) * u_grain * 0.08;

  gl_FragColor = vec4(col, 1.0);
}
`;

function hexToRgb(hex) {
  const h = hex.replace("#", "");
  return [
    parseInt(h.slice(0, 2), 16) / 255,
    parseInt(h.slice(2, 4), 16) / 255,
    parseInt(h.slice(4, 6), 16) / 255,
  ];
}

function Velaris({ bg = "#050D0E", colors, speed = 2.8, grain = 0.22 }) {
  const canvasRef = React.useRef(null);
  const colorsRef = React.useRef(colors);
  colorsRef.current = colors;

  /* smoothed mouse state */
  const mouseRef = React.useRef({ x: 0.5, y: 0.5, tx: 0.5, ty: 0.5, str: 0, tStr: 0 });

  React.useEffect(() => {
    const onMove = (e) => {
      const m = mouseRef.current;
      m.tx   = e.clientX / window.innerWidth;
      m.ty   = 1.0 - e.clientY / window.innerHeight;
      m.tStr = 1.0; // cursor is active
    };
    const onLeave = () => { mouseRef.current.tStr = 0; };
    window.addEventListener("pointermove", onMove);
    window.addEventListener("pointerleave", onLeave);
    return () => {
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerleave", onLeave);
    };
  }, []);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const gl = canvas.getContext("webgl");
    if (!gl) { console.warn("WebGL not supported"); return; }

    const mkShader = (type, src) => {
      const s = gl.createShader(type);
      gl.shaderSource(s, src);
      gl.compileShader(s);
      return s;
    };

    const prog = gl.createProgram();
    gl.attachShader(prog, mkShader(gl.VERTEX_SHADER, VERT));
    gl.attachShader(prog, mkShader(gl.FRAGMENT_SHADER, FRAG));
    gl.linkProgram(prog);
    gl.useProgram(prog);

    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);
    const posLoc = gl.getAttribLocation(prog, "position");
    gl.enableVertexAttribArray(posLoc);
    gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

    const u = {
      res:      gl.getUniformLocation(prog, "u_resolution"),
      time:     gl.getUniformLocation(prog, "u_time"),
      grain:    gl.getUniformLocation(prog, "u_grain"),
      colors:   gl.getUniformLocation(prog, "u_colors"),
      bg:       gl.getUniformLocation(prog, "u_bg"),
      mouse:    gl.getUniformLocation(prog, "u_mouse"),
      mouseStr: gl.getUniformLocation(prog, "u_mouseStr"),
    };

    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio, 2);
      canvas.width  = window.innerWidth  * dpr;
      canvas.height = window.innerHeight * dpr;
      gl.viewport(0, 0, canvas.width, canvas.height);
    };
    window.addEventListener("resize", resize);
    resize();

    const easePos = 0.055; /* mouse position lerp — lower = lazier */
    const easeStr = 0.08;  /* activation strength lerp */
    let raf;
    const render = (t) => {
      const m = mouseRef.current;
      m.x   += (m.tx   - m.x)   * easePos;
      m.y   += (m.ty   - m.y)   * easePos;
      m.str += (m.tStr - m.str) * easeStr;

      const c = colorsRef.current;
      gl.uniform2f(u.res, canvas.width, canvas.height);
      gl.uniform1f(u.time, t * 0.001 * speed);
      gl.uniform1f(u.grain, grain);
      gl.uniform3f(u.bg, ...hexToRgb(bg));
      gl.uniform3fv(u.colors, new Float32Array(c.slice(0, 4).flatMap(hexToRgb)));
      gl.uniform2f(u.mouse, m.x, m.y);
      gl.uniform1f(u.mouseStr, m.str);
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
      raf = requestAnimationFrame(render);
    };
    raf = requestAnimationFrame(render);

    return () => {
      window.removeEventListener("resize", resize);
      cancelAnimationFrame(raf);
      gl.deleteProgram(prog);
    };
  }, [bg, speed, grain]);

  return (
    <canvas
      ref={canvasRef}
      style={{ position: "fixed", inset: 0, width: "100vw", height: "100vh", zIndex: 0, pointerEvents: "none", display: "block" }}
    />
  );
}

window.Velaris = Velaris;
