// ghost-cursor.jsx — lightweight WebGL2 "ghost cursor": a soft fbm smoke blob
// with a fading trail that follows the pointer. Plain WebGL2 (no three.js), in
// our palette, composited with mix-blend-mode: screen so it glows over the
// dark background. Pauses when idle + tab hidden; renders at 1x DPR for speed.

const _ghostVert = `#version 300 es
precision highp float;
in vec4 position;
void main(){ gl_Position = position; }
`;

function _ghostFrag(trail) {
  return `#version 300 es
precision highp float;
#define TRAIL ${trail}
uniform float iTime;
uniform vec2  iResolution;
uniform vec2  iMouse;
uniform vec2  iPrev[TRAIL];
uniform float iOpacity;
uniform float iScale;
uniform vec3  iColor;
out vec4 fragColor;

float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453123); }
float noise(vec2 p){
  vec2 i = floor(p), f = fract(p);
  f *= f * (3. - 2. * f);
  return mix(mix(hash(i), hash(i+vec2(1,0)), f.x),
             mix(hash(i+vec2(0,1)), hash(i+vec2(1,1)), f.x), f.y);
}
float fbm(vec2 p){
  float v = 0., a = 0.5;
  mat2 m = mat2(0.8,0.6,-0.6,0.8);
  for(int i=0;i<5;i++){ v += a*noise(p); p = m*p*2.0; a *= 0.5; }
  return v;
}
vec4 blob(vec2 uv, vec2 m, float intensity, float activity){
  vec2 q = vec2(fbm(uv*iScale + iTime*0.10), fbm(uv*iScale + vec2(5.2,1.3) + iTime*0.10));
  vec2 r = vec2(fbm(uv*iScale + q*1.5 + iTime*0.15), fbm(uv*iScale + q*1.5 + vec2(8.3,2.8) + iTime*0.15));
  float smoke = fbm(uv*iScale + r*0.8);
  float radius = 0.5 + 0.3 * (1.0/iScale);
  float distF = 1.0 - smoothstep(0.0, radius*activity, length(uv - m));
  float alpha = pow(smoke, 2.5) * distF;
  vec3 c1 = mix(iColor, vec3(1.0), 0.18);
  vec3 c2 = mix(iColor, vec3(0.75,0.85,1.0), 0.3);
  vec3 col = mix(c1, c2, sin(iTime*0.5)*0.5 + 0.5);
  return vec4(col * alpha * intensity, alpha * intensity);
}
void main(){
  vec2 aspect = vec2(iResolution.x/iResolution.y, 1.0);
  vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * aspect;
  vec2 m  = (iMouse * 2.0 - 1.0) * aspect;

  vec3 colAcc = vec3(0.0);
  float aAcc = 0.0;

  vec4 b = blob(uv, m, 1.0, iOpacity);
  colAcc += b.rgb; aAcc += b.a;

  for(int i=0;i<TRAIL;i++){
    vec2 pm = (iPrev[i] * 2.0 - 1.0) * aspect;
    float t = 1.0 - float(i)/float(TRAIL);
    t = pow(t, 2.0);
    if(t > 0.01){
      vec4 bt = blob(uv, pm, t*0.8, iOpacity);
      colAcc += bt.rgb; aAcc += bt.a;
    }
  }

  float outA = clamp(aAcc * iOpacity, 0.0, 1.0);
  fragColor = vec4(colAcc, outA);
}
`;
}

function _ghostHexToRgb(hex){
  const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return m ? [parseInt(m[1],16)/255, parseInt(m[2],16)/255, parseInt(m[3],16)/255] : [0.36,0.55,1];
}
function _ghostCompile(gl, type, src){
  const s = gl.createShader(type);
  gl.shaderSource(s, src); gl.compileShader(s);
  if(!gl.getShaderParameter(s, gl.COMPILE_STATUS)) console.error('ghost shader', gl.getShaderInfoLog(s));
  return s;
}

function GhostCursor({
  color = '#5b8bff',
  trailLength = 22,
  inertia = 0.5,
  fadeDelayMs = 900,
  fadeDurationMs = 1300,
  zIndex = 0,
  mixBlendMode = 'screen',
  style
}) {
  const canvasRef = React.useRef(null);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const gl = canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: false, antialias: true, depth: false });
    if (!gl) return;

    const TRAIL = Math.max(1, Math.floor(trailLength));
    const program = gl.createProgram();
    const vs = _ghostCompile(gl, gl.VERTEX_SHADER, _ghostVert);
    const fs = _ghostCompile(gl, gl.FRAGMENT_SHADER, _ghostFrag(TRAIL));
    gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program);
    if(!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('ghost link', gl.getProgramInfoLog(program)); return; }

    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(program, 'position');
    gl.enableVertexAttribArray(posLoc);
    gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

    const U = {
      time:  gl.getUniformLocation(program, 'iTime'),
      res:   gl.getUniformLocation(program, 'iResolution'),
      mouse: gl.getUniformLocation(program, 'iMouse'),
      prev:  gl.getUniformLocation(program, 'iPrev'),
      op:    gl.getUniformLocation(program, 'iOpacity'),
      scale: gl.getUniformLocation(program, 'iScale'),
      color: gl.getUniformLocation(program, 'iColor'),
    };

    const DPR = Math.min(window.devicePixelRatio || 1, 1); // 1x for speed
    const resize = () => {
      const w = Math.floor(window.innerWidth * DPR);
      const h = Math.floor(window.innerHeight * DPR);
      if (canvas.width !== w || canvas.height !== h) {
        canvas.width = w; canvas.height = h;
        gl.viewport(0, 0, w, h);
      }
    };
    resize();
    window.addEventListener('resize', resize);

    // State
    const mouse = [0.5, 0.5];
    const target = [0.5, 0.5];
    const vel = [0, 0];
    const trail = Array.from({ length: TRAIL }, () => [0.5, 0.5]);
    let head = 0;
    const flat = new Float32Array(TRAIL * 2);
    let pointerActive = false;
    let lastMove = performance.now();
    let opacity = 0;
    const colorRGB = _ghostHexToRgb(color);

    const onMove = (e) => {
      target[0] = e.clientX / window.innerWidth;
      target[1] = 1 - e.clientY / window.innerHeight;
      pointerActive = true;
      lastMove = performance.now();
    };
    const onLeave = () => { pointerActive = false; lastMove = performance.now(); };
    window.addEventListener('pointermove', onMove, { passive: true });
    window.addEventListener('pointerout', onLeave, { passive: true });

    const start = performance.now();
    let raf = 0, last = 0;
    const minFrame = 1000 / 40;
    const scale = Math.max(0.5, Math.min(2.0, Math.min(window.innerWidth, window.innerHeight) / 600));

    const frame = (now) => {
      raf = requestAnimationFrame(frame);
      if (document.hidden) return;
      if (now - last < minFrame) return;
      last = now;

      if (pointerActive) {
        vel[0] = target[0] - mouse[0];
        vel[1] = target[1] - mouse[1];
        mouse[0] = target[0]; mouse[1] = target[1];
        opacity = 1;
      } else {
        vel[0] *= inertia; vel[1] *= inertia;
        if (vel[0]*vel[0] + vel[1]*vel[1] > 1e-6) { mouse[0] += vel[0]; mouse[1] += vel[1]; }
        const dt = now - lastMove;
        if (dt > fadeDelayMs) opacity = Math.max(0, 1 - Math.min(1, (dt - fadeDelayMs) / fadeDurationMs));
      }

      head = (head + 1) % TRAIL;
      trail[head][0] = mouse[0]; trail[head][1] = mouse[1];
      for (let i = 0; i < TRAIL; i++) {
        const src = (head - i + TRAIL) % TRAIL;
        flat[i*2] = trail[src][0];
        flat[i*2+1] = trail[src][1];
      }

      if (opacity <= 0.001 && !pointerActive) return; // skip draw while fully faded

      gl.useProgram(program);
      gl.clearColor(0,0,0,0);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.uniform1f(U.time, (now - start) / 1000);
      gl.uniform2f(U.res, canvas.width, canvas.height);
      gl.uniform2f(U.mouse, mouse[0], mouse[1]);
      gl.uniform2fv(U.prev, flat);
      gl.uniform1f(U.op, opacity);
      gl.uniform1f(U.scale, scale);
      gl.uniform3fv(U.color, colorRGB);
      gl.enable(gl.BLEND);
      gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    };
    raf = requestAnimationFrame(frame);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', resize);
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerout', onLeave);
      gl.deleteProgram(program);
    };
  }, [color, trailLength, inertia, fadeDelayMs, fadeDurationMs]);

  return (
    <canvas
      ref={canvasRef}
      style={{
        position: 'fixed',
        inset: 0,
        width: '100%',
        height: '100%',
        zIndex,
        pointerEvents: 'none',
        mixBlendMode,
        ...style
      }}
    />
  );
}

window.GhostCursor = GhostCursor;
