import { jsx } from 'react/jsx-runtime';
import { useRef, useEffect } from 'react';

const VERTEX_SHADER = `#version 300 es
in vec2 a_position;
void main() {
  gl_Position = vec4(a_position, 0.0, 1.0);
}`;
/**
 * Minimal raw WebGL2 canvas renderer for fullscreen fragment shaders.
 * - Built-in uniforms: iTime (float), iResolution (vec2)
 * - Custom uniforms read from `uniformsRef` each frame (no stale closures)
 * - ResizeObserver for responsive canvas sizing
 * - rAF animation loop with cleanup on unmount
 * - Graceful degradation: renders nothing if WebGL2 unavailable
 */
const ShaderCanvas = ({ fragmentShader, uniformsRef, maxDpr = 2, className, style, }) => {
    const canvasRef = useRef(null);
    useEffect(() => {
        const canvas = canvasRef.current;
        if (!canvas)
            return;
        const gl = canvas.getContext("webgl2", {
            alpha: true,
            premultipliedAlpha: false,
            antialias: false,
        });
        if (!gl)
            return;
        // ── Compile shaders ────────────────────────────────────────────────
        const compileShader = (type, src) => {
            const shader = gl.createShader(type);
            if (!shader)
                return null;
            gl.shaderSource(shader, src);
            gl.compileShader(shader);
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                if (process.env.NODE_ENV !== "production") {
                    console.error("[ShaderCanvas]", gl.getShaderInfoLog(shader));
                }
                gl.deleteShader(shader);
                return null;
            }
            return shader;
        };
        const vs = compileShader(gl.VERTEX_SHADER, VERTEX_SHADER);
        const fs = compileShader(gl.FRAGMENT_SHADER, fragmentShader);
        if (!vs || !fs)
            return;
        const program = gl.createProgram();
        gl.attachShader(program, vs);
        gl.attachShader(program, fs);
        gl.linkProgram(program);
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            if (process.env.NODE_ENV !== "production") {
                console.error("[ShaderCanvas]", gl.getProgramInfoLog(program));
            }
            return;
        }
        gl.useProgram(program);
        // ── Fullscreen quad ────────────────────────────────────────────────
        const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
        const vao = gl.createVertexArray();
        gl.bindVertexArray(vao);
        const buf = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buf);
        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
        const aPos = gl.getAttribLocation(program, "a_position");
        gl.enableVertexAttribArray(aPos);
        gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
        // ── Uniform locations ──────────────────────────────────────────────
        const uTime = gl.getUniformLocation(program, "iTime");
        const uRes = gl.getUniformLocation(program, "iResolution");
        // Cache custom uniform locations
        const customLocs = new Map();
        const getCustomLoc = (name) => {
            var _a;
            if (!customLocs.has(name)) {
                customLocs.set(name, gl.getUniformLocation(program, name));
            }
            return (_a = customLocs.get(name)) !== null && _a !== void 0 ? _a : null;
        };
        // ── Resize handling ────────────────────────────────────────────────
        const dpr = Math.min(window.devicePixelRatio || 1, maxDpr);
        let width = 0;
        let height = 0;
        const resize = () => {
            const rect = canvas.getBoundingClientRect();
            const w = Math.round(rect.width * dpr);
            const h = Math.round(rect.height * dpr);
            if (w !== width || h !== height) {
                width = w;
                height = h;
                canvas.width = w;
                canvas.height = h;
                gl.viewport(0, 0, w, h);
            }
        };
        const observer = new ResizeObserver(resize);
        observer.observe(canvas);
        resize();
        // ── Animation loop ─────────────────────────────────────────────────
        gl.enable(gl.BLEND);
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
        let rafId = 0;
        const startTime = performance.now();
        const frame = () => {
            rafId = requestAnimationFrame(frame);
            const t = (performance.now() - startTime) / 1000;
            gl.uniform1f(uTime, t);
            gl.uniform2f(uRes, width, height);
            // Set custom uniforms from ref
            const uniforms = uniformsRef.current;
            for (const name in uniforms) {
                const loc = getCustomLoc(name);
                if (!loc)
                    continue;
                const val = uniforms[name];
                if (typeof val === "number") {
                    gl.uniform1f(loc, val);
                }
                else if (Array.isArray(val)) {
                    if (val.length === 2)
                        gl.uniform2f(loc, val[0], val[1]);
                    else if (val.length === 3)
                        gl.uniform3f(loc, val[0], val[1], val[2]);
                    else if (val.length === 4)
                        gl.uniform4f(loc, val[0], val[1], val[2], val[3]);
                }
            }
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        };
        rafId = requestAnimationFrame(frame);
        return () => {
            cancelAnimationFrame(rafId);
            observer.disconnect();
            gl.deleteProgram(program);
            gl.deleteShader(vs);
            gl.deleteShader(fs);
            gl.deleteBuffer(buf);
            gl.deleteVertexArray(vao);
        };
        // Fragment shader is static — only set up once
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);
    return (jsx("canvas", { ref: canvasRef, className: className, style: { display: "block", width: "100%", height: "100%", ...style } }));
};

export { ShaderCanvas };
