// Shader runtime — minimal WebGL host that compiles a fragment shader,
// wires u_resolution / u_time / u_mouse / u_click / u_clickAge uniforms,
// and renders into a <canvas> sized to its parent.
//
// Each Shader passes a `frag` string. Vertex shader is shared.
// Mouse is in pixels (0,0 at bottom-left to match GL convention).

const VERT_SRC = `
  attribute vec2 a_pos;
  void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }
`;

const FRAG_HEADER = `
  precision highp float;
  uniform vec2 u_resolution;
  uniform float u_time;
  uniform vec2 u_mouse;       // pixels, bottom-left origin
  uniform vec2 u_mouseSmooth; // pixels, eased
  uniform vec2 u_click;       // last click pos in pixels
  uniform float u_clickAge;   // seconds since last click (clamped)
  uniform float u_pressed;    // 0..1 pointer-down hold
`;

function compile(gl, type, src) {
  const sh = gl.createShader(type);
  gl.shaderSource(sh, src);
  gl.compileShader(sh);
  if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
    const log = gl.getShaderInfoLog(sh);
    console.error('Shader compile error:', log, '\n--- src ---\n', src);
    gl.deleteShader(sh);
    return null;
  }
  return sh;
}

function ShaderCanvas({ frag, label, hint, idleSwirl = true, style }) {
  const wrapRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const stateRef = React.useRef({
    mouse: [0.5, 0.5],
    mouseSmooth: [0.5, 0.5],
    click: [0.5, 0.5],
    clickAt: -10,
    pressed: 0,
    start: performance.now(),
  });

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;

    const gl = canvas.getContext('webgl', { antialias: true, premultipliedAlpha: false });
    if (!gl) return;

    const fullFrag = FRAG_HEADER + '\n' + frag;
    const v = compile(gl, gl.VERTEX_SHADER, VERT_SRC);
    const f = compile(gl, gl.FRAGMENT_SHADER, fullFrag);
    if (!v || !f) return;
    const prog = gl.createProgram();
    gl.attachShader(prog, v);
    gl.attachShader(prog, f);
    gl.linkProgram(prog);
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
      console.error('Shader link 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, 1, -1, 1, 1,
    ]), gl.STATIC_DRAW);
    const aPos = gl.getAttribLocation(prog, 'a_pos');
    gl.enableVertexAttribArray(aPos);
    gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);

    const u = {
      resolution: gl.getUniformLocation(prog, 'u_resolution'),
      time: gl.getUniformLocation(prog, 'u_time'),
      mouse: gl.getUniformLocation(prog, 'u_mouse'),
      mouseSmooth: gl.getUniformLocation(prog, 'u_mouseSmooth'),
      click: gl.getUniformLocation(prog, 'u_click'),
      clickAge: gl.getUniformLocation(prog, 'u_clickAge'),
      pressed: gl.getUniformLocation(prog, 'u_pressed'),
    };

    let raf = 0;
    let alive = true;

    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const r = wrap.getBoundingClientRect();
      const w = Math.max(2, Math.floor(r.width * dpr));
      const h = Math.max(2, Math.floor(r.height * dpr));
      if (canvas.width !== w || canvas.height !== h) {
        canvas.width = w;
        canvas.height = h;
      }
    };
    const ro = new ResizeObserver(resize);
    ro.observe(wrap);
    resize();

    const setMouseFromEvent = (e) => {
      const r = canvas.getBoundingClientRect();
      const x = (e.clientX - r.left) / r.width;
      const y = 1 - (e.clientY - r.top) / r.height;
      stateRef.current.mouse = [x, y];
    };
    const onMove = (e) => setMouseFromEvent(e);
    const onDown = (e) => {
      setMouseFromEvent(e);
      stateRef.current.click = stateRef.current.mouse.slice();
      stateRef.current.clickAt = (performance.now() - stateRef.current.start) / 1000;
      stateRef.current.pressed = 1;
    };
    const onUp = () => { stateRef.current.pressed = 0; };
    const onLeave = () => { stateRef.current.pressed = 0; };

    canvas.addEventListener('pointermove', onMove);
    canvas.addEventListener('pointerdown', onDown);
    window.addEventListener('pointerup', onUp);
    canvas.addEventListener('pointerleave', onLeave);

    const render = () => {
      if (!alive) return;
      const t = (performance.now() - stateRef.current.start) / 1000;
      const s = stateRef.current;

      // Idle swirl: if user hasn't moved mouse in this canvas, drift it
      // gently in a circle so the shader stays alive.
      let mx = s.mouse[0], my = s.mouse[1];
      if (idleSwirl) {
        // No idle swirl applied to actual mouse, only to mouseSmooth fallback.
      }

      // Ease mouseSmooth toward mouse.
      const k = 0.08;
      s.mouseSmooth[0] += (mx - s.mouseSmooth[0]) * k;
      s.mouseSmooth[1] += (my - s.mouseSmooth[1]) * k;

      gl.viewport(0, 0, canvas.width, canvas.height);
      gl.uniform2f(u.resolution, canvas.width, canvas.height);
      gl.uniform1f(u.time, t);
      gl.uniform2f(u.mouse, mx * canvas.width, my * canvas.height);
      gl.uniform2f(u.mouseSmooth, s.mouseSmooth[0] * canvas.width, s.mouseSmooth[1] * canvas.height);
      gl.uniform2f(u.click, s.click[0] * canvas.width, s.click[1] * canvas.height);
      gl.uniform1f(u.clickAge, Math.min(10, t - s.clickAt));
      gl.uniform1f(u.pressed, s.pressed);

      gl.drawArrays(gl.TRIANGLES, 0, 6);
      raf = requestAnimationFrame(render);
    };
    raf = requestAnimationFrame(render);

    return () => {
      alive = false;
      cancelAnimationFrame(raf);
      ro.disconnect();
      canvas.removeEventListener('pointermove', onMove);
      canvas.removeEventListener('pointerdown', onDown);
      window.removeEventListener('pointerup', onUp);
      canvas.removeEventListener('pointerleave', onLeave);
      gl.deleteProgram(prog);
      gl.deleteShader(v);
      gl.deleteShader(f);
      gl.deleteBuffer(buf);
    };
  }, [frag]);

  return (
    <div ref={wrapRef} style={{ position: 'absolute', inset: 0, overflow: 'hidden', ...style }}>
      <canvas ref={canvasRef} style={{ display: 'block', width: '100%', height: '100%' }} />
      {(label || hint) && (
        <div style={{
          position: 'absolute', left: 24, bottom: 22, color: 'rgba(255,255,255,0.85)',
          fontFamily: 'ui-monospace, "SF Mono", Menlo, Consolas, monospace',
          fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase',
          pointerEvents: 'none', mixBlendMode: 'screen',
        }}>
          {label && <div style={{ fontWeight: 600 }}>{label}</div>}
          {hint && <div style={{ opacity: 0.55, marginTop: 4, letterSpacing: '0.05em', textTransform: 'none', fontSize: 10.5 }}>{hint}</div>}
        </div>
      )}
    </div>
  );
}

window.ShaderCanvas = ShaderCanvas;
