// src/shader.jsx
// WebGL fragment-shader preview component. Used by the floating preview
// tooltip that appears when you hover a project row.
//
// To register a new shader variant: add another `const FS_FOO = SHARED + …`
// block, then map a name to it in the `SHADERS` lookup at the bottom. Set
// `shader: 'foo'` on a project in content.jsx to use it.

function ShaderPreview({ active = true, variant = 'plasma', width, height, style }) {
  const canvasRef = React.useRef(null);
  const rafRef    = React.useRef(0);
  const stateRef  = React.useRef({ gl: null, prog: null, uTime: null, uRes: null });
  const [ready, setReady] = React.useState(false);

  // Compile + link on mount (and whenever `variant` changes — we tear down
  // the old program and build a new one).
  React.useEffect(() => {
    const cv = canvasRef.current;
    if (!cv) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    cv.width  = cv.clientWidth  * dpr;
    cv.height = cv.clientHeight * dpr;

    const gl = cv.getContext('webgl', { antialias: false, preserveDrawingBuffer: false });
    if (!gl) return;

    const vsrc = `
      attribute vec2 p;
      void main(){ gl_Position = vec4(p, 0., 1.); }
    `;
    const fsrc = SHADERS[variant] || SHADERS.plasma;

    function compile(type, src) {
      const sh = gl.createShader(type);
      gl.shaderSource(sh, src); gl.compileShader(sh);
      if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
        console.error(gl.getShaderInfoLog(sh)); return null;
      }
      return sh;
    }
    const vs = compile(gl.VERTEX_SHADER, vsrc);
    const fs = compile(gl.FRAGMENT_SHADER, fsrc);
    const prog = gl.createProgram();
    gl.attachShader(prog, vs); gl.attachShader(prog, fs);
    gl.linkProgram(prog);
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
      console.error(gl.getProgramInfoLog(prog)); return;
    }
    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 loc = gl.getAttribLocation(prog, 'p');
    gl.enableVertexAttribArray(loc);
    gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);

    const uTime = gl.getUniformLocation(prog, 'uTime');
    const uRes  = gl.getUniformLocation(prog, 'uRes');
    gl.viewport(0, 0, cv.width, cv.height);
    gl.uniform2f(uRes, cv.width, cv.height);

    stateRef.current = { gl, prog, uTime, uRes, vs, fs, buf };
    setReady(true);

    return () => {
      cancelAnimationFrame(rafRef.current);
      gl.deleteProgram(prog); gl.deleteShader(vs); gl.deleteShader(fs);
      gl.deleteBuffer(buf);
    };
  }, [variant]);

  // Animation loop — runs only while `active`. Stops on unhover to save
  // battery / GPU.
  React.useEffect(() => {
    const { gl, uTime } = stateRef.current;
    if (!gl || !active) { cancelAnimationFrame(rafRef.current); return; }
    const t0 = performance.now() - (stateRef.current.elapsed || 0) * 1000;
    function frame(now) {
      const t = (now - t0) / 1000;
      stateRef.current.elapsed = t;
      gl.uniform1f(uTime, t);
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
      rafRef.current = requestAnimationFrame(frame);
    }
    rafRef.current = requestAnimationFrame(frame);
    return () => cancelAnimationFrame(rafRef.current);
  }, [active, ready]);

  return (
    <canvas
      ref={canvasRef}
      style={{
        width:  width  || '100%',
        height: height || '100%',
        display: 'block',
        background: 'var(--bg-0)',
        ...style,
      }}
    />
  );
}

// Static placeholder shown when no shader is running (e.g. preview tweak is
// "off"). A warm peach radial gradient that mimics the plasma stillframe.
function ShaderStill({ width, height, style }) {
  return (
    <div style={{
      width:  width  || '100%',
      height: height || '100%',
      background: 'radial-gradient(120% 90% at 30% 35%, rgba(255,184,107,0.55), rgba(217,156,90,0.35) 45%, rgba(15,17,21,0.95) 90%)',
      ...style,
    }} />
  );
}

// ── Shader sources ──────────────────────────────────────────────────────
// Both shaders share a noise/fbm preamble; what differs is the warp, the
// palette, and the post step.
const SHARED = `
  precision mediump float;
  uniform vec2  uRes;
  uniform float uTime;
  float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453); }
  float noise(vec2 p){
    vec2 i = floor(p), f = fract(p);
    float a = hash(i),                b = hash(i + vec2(1.,0.));
    float c = hash(i + vec2(0.,1.)),  d = hash(i + vec2(1.,1.));
    vec2 u = f * f * (3. - 2. * f);
    return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
  }
  float fbm(vec2 p){
    float v = 0., a = 0.5;
    for (int i = 0; i < 5; i++){ v += a * noise(p); p *= 2.02; a *= 0.5; }
    return v;
  }
`;

const FS_PLASMA = SHARED + `
  void main(){
    vec2 uv = (gl_FragCoord.xy - 0.5 * uRes) / min(uRes.x, uRes.y);
    float t = uTime * 0.18;
    vec2 q = uv * 1.6;
    q += 0.6 * vec2(fbm(q + t), fbm(q - t + 4.2));
    float n = fbm(q * 1.4 + vec2(t * 0.6, -t * 0.3));
    vec3 bg    = vec3(0.04, 0.05, 0.07);
    vec3 amber = vec3(0.85, 0.61, 0.35);
    vec3 peach = vec3(1.00, 0.72, 0.42);
    vec3 hot   = vec3(1.00, 0.86, 0.62);
    vec3 col = mix(bg,    amber, smoothstep(0.30, 0.55, n));
    col      = mix(col,   peach, smoothstep(0.50, 0.75, n));
    col      = mix(col,   hot,   smoothstep(0.78, 0.92, n));
    float v = smoothstep(1.05, 0.35, length(uv));
    col *= mix(0.65, 1.0, v);
    gl_FragColor = vec4(col, 1.0);
  }
`;

const FS_CAVES = SHARED + `
  void main(){
    vec2 uv = (gl_FragCoord.xy - 0.5 * uRes) / min(uRes.x, uRes.y);
    float t = uTime * 0.08;
    vec2 q = uv * 2.4 + vec2(t, -t * 0.6);
    float n = fbm(q);
    float floorMask = smoothstep(0.48, 0.50, n);
    float rim       = smoothstep(0.49, 0.51, n) - smoothstep(0.51, 0.55, n);
    vec3 bg   = vec3(0.04, 0.05, 0.08);
    vec3 wall = vec3(0.18, 0.20, 0.28);
    vec3 glow = vec3(1.00, 0.66, 0.36);
    vec3 col  = mix(bg, wall, floorMask);
    col += glow * rim * 1.2;
    float embers = pow(noise(uv * 22.0 + t * 4.0), 18.0);
    col += glow * embers * 1.6;
    float v = smoothstep(1.1, 0.3, length(uv));
    col *= mix(0.55, 1.0, v);
    gl_FragColor = vec4(col, 1.0);
  }
`;

const SHADERS = {
  plasma: FS_PLASMA,
  caves:  FS_CAVES,
};

window.ShaderPreview = ShaderPreview;
window.ShaderStill   = ShaderStill;
