// WebGL Orb — raymarched noise sphere, heavily optimized
// Renders shader to a small internal canvas (~400px) and CSS-scales up.
// This keeps the beautiful volumetric look while ~4x faster.
function Orb({ className = "", intensity = 1, theme = "dark" }) {
  const canvasRef = React.useRef(null);
  const rafRef = React.useRef(null);
  const stateRef = React.useRef({ mx: 0, my: 0, visible: true });

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const gl = canvas.getContext('webgl', { premultipliedAlpha: true, antialias: true, alpha: true, powerPreference: 'low-power' });
    if (!gl) return;

    const vs = `
      attribute vec2 a_pos;
      void main(){ gl_Position = vec4(a_pos, 0., 1.); }
    `;

    // Same raymarched look as before, but: fewer march steps (32), fbm 3 octaves,
    // analytic normals (no 3x map() calls), cheap grain.
    const fs = `
      precision mediump float;
      uniform float u_t;
      uniform vec2 u_res;
      uniform vec2 u_mouse;
      uniform float u_intensity;
      uniform float u_theme;

      vec3 hash3(vec3 p){
        p = vec3(dot(p,vec3(127.1,311.7,74.7)),
                 dot(p,vec3(269.5,183.3,246.1)),
                 dot(p,vec3(113.5,271.9,124.6)));
        return -1.0+2.0*fract(sin(p)*43758.5453);
      }
      float noise(vec3 p){
        vec3 i=floor(p); vec3 f=fract(p);
        vec3 u=f*f*(3.0-2.0*f);
        return mix(mix(mix(dot(hash3(i),f),
                           dot(hash3(i+vec3(1,0,0)),f-vec3(1,0,0)),u.x),
                       mix(dot(hash3(i+vec3(0,1,0)),f-vec3(0,1,0)),
                           dot(hash3(i+vec3(1,1,0)),f-vec3(1,1,0)),u.x),u.y),
                   mix(mix(dot(hash3(i+vec3(0,0,1)),f-vec3(0,0,1)),
                           dot(hash3(i+vec3(1,0,1)),f-vec3(1,0,1)),u.x),
                       mix(dot(hash3(i+vec3(0,1,1)),f-vec3(0,1,1)),
                           dot(hash3(i+vec3(1,1,1)),f-vec3(1,1,1)),u.x),u.y),u.z);
      }
      float fbm(vec3 p){
        float v=0., a=0.5;
        v += a*noise(p); p*=2.03; a*=0.5;
        v += a*noise(p); p*=2.03; a*=0.5;
        v += a*noise(p); p*=2.03; a*=0.5;
        v += a*noise(p);
        return v;
      }

      float map(vec3 p, float t){
        float d = length(p) - 1.0;
        float n = fbm(p*1.3 + vec3(0., t*0.5, t*0.3));
        d -= 0.35 * n * u_intensity;
        return d;
      }

      vec3 palette(float t){
        vec3 cyan = vec3(0.32, 0.82, 0.95);
        vec3 violet = vec3(0.55, 0.38, 0.95);
        vec3 amber = vec3(1.0, 0.78, 0.42);
        vec3 deep = vec3(0.05, 0.1, 0.25);
        vec3 c = mix(deep, cyan, smoothstep(0.0, 0.35, t));
        c = mix(c, violet, smoothstep(0.3, 0.6, t));
        c = mix(c, amber, smoothstep(0.6, 0.95, t));
        return c;
      }

      void main(){
        vec2 uv = (gl_FragCoord.xy - u_res*0.5) / min(u_res.x, u_res.y);
        uv *= 2.6;

        vec3 ro = vec3(0.0, 0.0, -3.2);
        vec3 rd = normalize(vec3(uv, 1.5));

        float rotY = u_mouse.x*0.4 + u_t*0.25;
        float rotX = -u_mouse.y*0.3;
        float cy_ = cos(rotY), sy_ = sin(rotY);
        float cx_ = cos(rotX), sx_ = sin(rotX);
        mat3 ry = mat3(cy_,0.,sy_, 0.,1.,0., -sy_,0.,cy_);
        mat3 rx = mat3(1.,0.,0., 0.,cx_,-sx_, 0.,sx_,cx_);
        rd = rx*ry*rd;
        ro = rx*ry*ro;

        // bounding sphere skip — only march if ray could hit r=1.5
        float b = dot(ro, rd);
        float c = dot(ro, ro) - 2.25;
        float disc = b*b - c;
        float hit = 0.;
        vec3 p = vec3(0.);
        float t = 0.;
        if(disc > 0.0){
          t = max(-b - sqrt(disc), 0.0);
          // 48 march steps with smarter early exit
          for(int i=0;i<48;i++){
            p = ro + rd*t;
            float d = map(p, u_t);
            if(d < 0.003){ hit = 1.; break; }
            if(t > 5.5) break;
            t += d*0.9;
          }
        }

        vec3 col = vec3(0.);
        float alpha = 0.;
        if(hit > 0.5){
          // analytic normal: for a unit sphere with fbm displacement, the sphere normal dominates
          // Using position-as-normal gives a very close approximation and is ~3x cheaper
          vec3 n = normalize(p);
          vec3 ld = normalize(vec3(0.4, 0.7, -0.8));
          float diff = max(dot(n, ld), 0.0);
          float rim = pow(1.0 - max(dot(n, -rd), 0.0), 2.0);
          float fres = pow(1.0 - max(dot(n, -rd), 0.0), 4.0);
          float band = (p.y*0.5 + 0.5) + 0.3*fbm(p*2.0 + u_t*0.6);
          vec3 base = palette(band);
          col = base * (0.35 + 0.65*diff);
          col += rim * vec3(0.4, 0.8, 1.0) * 0.6;
          col += fres * vec3(1.0, 0.7, 0.3) * 0.8;
          alpha = 1.0;
        }

        float glowD = length(uv) - 1.0;
        float glow = exp(-glowD*2.2) * 0.6;
        vec3 glowC = palette(0.5 + 0.4*sin(u_t*0.7));
        col += glowC * glow * (1.0 - alpha*0.7);
        alpha = max(alpha, glow*1.1);

        float vig = 1.0 - smoothstep(0.7, 1.8, length(uv)*0.8);
        col *= mix(0.7, 1.0, vig);

        if(u_theme > 0.5){ col = 1.0 - col*0.95; col *= 0.95; }

        gl_FragColor = vec4(col, clamp(alpha, 0.0, 1.0));
      }
    `;

    function compile(type, src){
      const s = gl.createShader(type);
      gl.shaderSource(s, src); gl.compileShader(s);
      if(!gl.getShaderParameter(s, gl.COMPILE_STATUS)){ console.error(gl.getShaderInfoLog(s)); return null; }
      return s;
    }
    const prog = gl.createProgram();
    gl.attachShader(prog, compile(gl.VERTEX_SHADER, vs));
    gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fs));
    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, 1,-1, 1,1]), gl.STATIC_DRAW);
    const loc = gl.getAttribLocation(prog, 'a_pos');
    gl.enableVertexAttribArray(loc);
    gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);

    const uT = gl.getUniformLocation(prog, 'u_t');
    const uR = gl.getUniformLocation(prog, 'u_res');
    const uM = gl.getUniformLocation(prog, 'u_mouse');
    const uI = gl.getUniformLocation(prog, 'u_intensity');
    const uTh = gl.getUniformLocation(prog, 'u_theme');

    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

    // Render at max 1024px (internal) for crisp details; CSS upscales
    function resize(){
      const r = canvas.getBoundingClientRect();
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const maxSide = 1024;
      const targetW = r.width * dpr;
      const targetH = r.height * dpr;
      const ratio = Math.min(maxSide / Math.max(targetW, 1), maxSide / Math.max(targetH, 1), 1);
      const w = Math.max(1, Math.floor(targetW * ratio));
      const h = Math.max(1, Math.floor(targetH * ratio));
      if(canvas.width !== w || canvas.height !== h){
        canvas.width = w; canvas.height = h;
        gl.viewport(0, 0, w, h);
      }
    }
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    function onMove(e){
      const r = canvas.getBoundingClientRect();
      stateRef.current.mx = ((e.clientX - r.left) / r.width - 0.5) * 2;
      stateRef.current.my = ((e.clientY - r.top) / r.height - 0.5) * 2;
    }
    window.addEventListener('mousemove', onMove, { passive: true });

    const io = new IntersectionObserver((es) => {
      es.forEach(e => stateRef.current.visible = e.isIntersecting);
    }, { threshold: 0.01 });
    io.observe(canvas);

    // Throttle to ~30fps (quality buffer is larger so lower fps balances cost)
    let mx = 0, my = 0, last = 0;
    const start = performance.now();
    function frame(now){
      rafRef.current = requestAnimationFrame(frame);
      if(!stateRef.current.visible) return;
      if(now - last < 40) return;
      last = now;
      const t = (now - start) / 1000;
      mx += (stateRef.current.mx - mx) * 0.08;
      my += (stateRef.current.my - my) * 0.08;
      gl.uniform1f(uT, t);
      gl.uniform2f(uR, canvas.width, canvas.height);
      gl.uniform2f(uM, mx, my);
      gl.uniform1f(uI, intensity);
      gl.uniform1f(uTh, theme === 'light' ? 1 : 0);
      gl.clearColor(0,0,0,0);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.drawArrays(gl.TRIANGLES, 0, 6);
    }
    rafRef.current = requestAnimationFrame(frame);

    return () => {
      cancelAnimationFrame(rafRef.current);
      ro.disconnect();
      io.disconnect();
      window.removeEventListener('mousemove', onMove);
    };
  }, [intensity, theme]);

  // CSS upscales the small canvas — image-rendering keeps it smooth
  return <canvas ref={canvasRef} className={className} style={{width:'100%', height:'100%', display:'block', imageRendering:'high-quality'}} />;
}

window.Orb = Orb;
