// SideRays — ported from React Bits (reactbits.dev/backgrounds/side-rays) to plain JSX.
//
// This site has no bundler, so `ogl` cannot be imported here. index.html loads it as an
// ES module from esm.sh, puts it on window.OGL and fires "ps:ogl"; this component waits
// for that rather than assuming it. Module scripts are deferred, so by the time React
// mounts the library may or may not be there yet.
const SideRays = ({
  speed = 2.5, rayColor1 = "#EAB308", rayColor2 = "#96c8ff", intensity = 2, spread = 2,
  origin = "top-right", tilt = 0, saturation = 1.5, blend = 0.75, falloff = 1.6,
  opacity = 1.0, className = "",
}) => {
  const { useRef, useEffect, useState } = React;
  const containerRef = useRef(null);
  const uniformsRef = useRef(null);
  const [visible, setVisible] = useState(false);
  const [lib, setLib] = useState(window.OGL || null);

  // Never draw for visitors who asked for reduced motion; nothing is allocated either.
  const reduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  useEffect(() => {
    if (lib) return;
    // The module can land in the gap between mount and this effect, in which case the
    // event has already fired. Check first, then subscribe for the other ordering.
    if (window.OGL) { setLib(window.OGL); return; }
    const onReady = () => setLib(window.OGL);
    window.addEventListener("ps:ogl", onReady);
    // Belt and braces: also poll, so no ordering of module load vs. React mount can
    // leave the effect waiting forever. Gives up after 20s (CDN down).
    let ticks = 0;
    const id = setInterval(() => {
      if (window.OGL) { setLib(window.OGL); clearInterval(id); }
      else if (++ticks > 130) clearInterval(id);
    }, 150);
    return () => { window.removeEventListener("ps:ogl", onReady); clearInterval(id); };
  }, [lib]);

  useEffect(() => {
    if (!containerRef.current) return;
    const io = new IntersectionObserver((entries) => setVisible(entries[0].isIntersecting), { threshold: 0.1 });
    io.observe(containerRef.current);
    // The hero is at the top of the page; if the observer has not reported by now,
    // assume it is on screen rather than never drawing.
    const fallback = setTimeout(() => setVisible((v) => v || true), 600);
    return () => { io.disconnect(); clearTimeout(fallback); };
  }, []);

  useEffect(() => {
    if (reduced || !visible || !lib || !containerRef.current) return;
    const { Renderer, Program, Triangle, Mesh } = lib;
    const hexToRgb = (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] : [1, 1, 1];
    };
    const flip = { "top-left": [1, 0], "bottom-right": [0, 1], "bottom-left": [1, 1] }[origin] || [0, 0];

    const renderer = new Renderer({ dpr: Math.min(window.devicePixelRatio, 2), alpha: true });
    const gl = renderer.gl;
    gl.canvas.style.width = "100%";
    gl.canvas.style.height = "100%";
    const el = containerRef.current;
    while (el.firstChild) el.removeChild(el.firstChild);
    el.appendChild(gl.canvas);

    const vert = `attribute vec2 position; void main(){ gl_Position = vec4(position, 0.0, 1.0); }`;
    const frag = `precision highp float;
uniform float iTime; uniform vec2 iResolution; uniform float iSpeed;
uniform vec3 iRayColor1; uniform vec3 iRayColor2;
uniform float iIntensity; uniform float iSpread; uniform float iFlipX; uniform float iFlipY;
uniform float iTilt; uniform float iSaturation; uniform float iBlend; uniform float iFalloff; uniform float iOpacity;
float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, float seedA, float seedB, float speed) {
  vec2 sourceToCoord = coord - raySource;
  float cosAngle = dot(normalize(sourceToCoord), rayRefDirection);
  return clamp((0.45 + 0.15 * sin(cosAngle * seedA + iTime * speed)) +
               (0.3 + 0.2 * cos(-cosAngle * seedB + iTime * speed)), 0.0, 1.0) *
         clamp((iResolution.x - length(sourceToCoord)) / iResolution.x, 0.5, 1.0);
}
void main() {
  vec2 fragCoord = gl_FragCoord.xy;
  if (iFlipX > 0.5) fragCoord.x = iResolution.x - fragCoord.x;
  if (iFlipY > 0.5) fragCoord.y = iResolution.y - fragCoord.y;
  vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
  vec2 rayPos = vec2(iResolution.x * 1.1, -0.5 * iResolution.y);
  float tiltRad = iTilt * 3.14159265 / 180.0; float cs = cos(tiltRad); float sn = sin(tiltRad);
  vec2 rel = coord - rayPos;
  vec2 tiltedCoord = vec2(rel.x * cs - rel.y * sn, rel.x * sn + rel.y * cs) + rayPos;
  float halfSpread = iSpread * 0.275;
  vec2 rayRefDir1 = normalize(vec2(cos(0.785398 + halfSpread), sin(0.785398 + halfSpread)));
  vec2 rayRefDir2 = normalize(vec2(cos(0.785398 - halfSpread), sin(0.785398 - halfSpread)));
  vec4 rays1 = vec4(iRayColor1, 1.0) * rayStrength(rayPos, rayRefDir1, tiltedCoord, 36.2214, 21.11349, iSpeed);
  vec4 rays2 = vec4(iRayColor2, 1.0) * rayStrength(rayPos, rayRefDir2, tiltedCoord, 22.3991, 18.0234, iSpeed * 0.2);
  vec4 color = rays1 * (1.0 - iBlend) * 0.9 + rays2 * iBlend * 0.9;
  float distanceToLight = length(fragCoord.xy - vec2(rayPos.x, iResolution.y - rayPos.y)) / iResolution.y;
  float brightness = iIntensity * 0.4 / pow(max(distanceToLight, 0.001), iFalloff);
  color.rgb *= brightness;
  float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
  color.rgb = mix(vec3(gray), color.rgb, iSaturation);
  color.a = max(color.r, max(color.g, color.b)) * iOpacity;
  gl_FragColor = color;
}`;

    const uniforms = {
      iTime: { value: 0 }, iResolution: { value: [1, 1] }, iSpeed: { value: speed },
      iRayColor1: { value: hexToRgb(rayColor1) }, iRayColor2: { value: hexToRgb(rayColor2) },
      iIntensity: { value: intensity }, iSpread: { value: spread },
      iFlipX: { value: flip[0] }, iFlipY: { value: flip[1] }, iTilt: { value: tilt },
      iSaturation: { value: saturation }, iBlend: { value: blend }, iFalloff: { value: falloff },
      iOpacity: { value: opacity },
    };
    uniformsRef.current = uniforms;
    const mesh = new Mesh(gl, { geometry: new Triangle(gl), program: new Program(gl, { vertex: vert, fragment: frag, uniforms }) });

    const updateSize = () => {
      renderer.dpr = Math.min(window.devicePixelRatio, 2);
      const { clientWidth: w, clientHeight: h } = el;
      renderer.setSize(w, h);
      uniforms.iResolution.value = [w * renderer.dpr, h * renderer.dpr];
    };
    let raf = 0;
    const loop = (t) => {
      uniforms.iTime.value = t * 0.001;
      try { renderer.render({ scene: mesh }); raf = requestAnimationFrame(loop); } catch (e) {}
    };
    window.addEventListener("resize", updateSize);
    updateSize();
    raf = requestAnimationFrame(loop);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", updateSize);
      try {
        const lose = gl.getExtension("WEBGL_lose_context");
        if (lose) lose.loseContext();
        if (gl.canvas.parentNode) gl.canvas.parentNode.removeChild(gl.canvas);
      } catch (e) {}
      uniformsRef.current = null;
    };
  }, [reduced, visible, lib, speed, rayColor1, rayColor2, intensity, spread, origin, tilt, saturation, blend, falloff, opacity]);

  return <div ref={containerRef} className={`side-rays-container ${className}`.trim()} aria-hidden="true" />;
};
window.SideRays = SideRays;
