// shader.jsx — WebGL Voronoi/Cellular shader for the Hero background.
// No water. Generative stone-mosaic that slowly breathes.

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

// BRICK/BLOCK shader — generative running-bond masonry pattern.
// No fluid cells, no water. Hard rectangular bricks with mortar joints,
// per-brick color variance, stone grain, and a slow "spotlight" sweep so
// it stays alive without animating the geometry itself.
const VORONOI_FRAG = `
#extension GL_OES_standard_derivatives : enable
precision highp float;
uniform vec2  u_res;
uniform float u_time;
uniform float u_speed;
uniform float u_intensity;
uniform float u_scale;     // bricks per viewport-height
uniform vec3  u_bg;
uniform vec3  u_fg;
uniform vec3  u_accent;

float hash1(vec2 p){ return fract(sin(dot(p, vec2(41.3, 289.1))) * 47453.13); }
vec2  hash2(vec2 p){
  return fract(sin(vec2(
    dot(p, vec2(127.1, 311.7)),
    dot(p, vec2(269.5, 183.3))
  )) * 43758.5453);
}

// 2D value-noise fbm for stone surface texture
float vnoise(vec2 p){
  vec2 i = floor(p), f = fract(p);
  float a = hash1(i);
  float b = hash1(i + vec2(1.0, 0.0));
  float c = hash1(i + vec2(0.0, 1.0));
  float d = hash1(i + vec2(1.0, 1.0));
  vec2 u = f*f*(3.0-2.0*f);
  return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
}
float fbm(vec2 p){
  float v = 0.0, a = 0.5;
  for(int i=0; i<5; i++){ v += a*vnoise(p); p *= 2.07; a *= 0.5; }
  return v;
}

void main(){
  // Aspect-correct UV, origin bottom-left of viewport
  vec2 uv = gl_FragCoord.xy / u_res.y;
  float t = u_time * u_speed;

  // Brick grid — bricks 2:1, running bond (every other row offset by half-brick)
  // u_scale = approximate # of brick-rows visible vertically
  float rows  = u_scale;
  float bw    = 2.0 / rows;     // brick width  (units: viewport heights)
  float bh    = 1.0 / rows;     // brick height
  float row   = floor(uv.y / bh);
  float offX  = mod(row, 2.0) * 0.5 * bw;     // half-brick offset on alt rows
  vec2  cell  = vec2(floor((uv.x + offX) / bw), row);
  vec2  local = vec2(fract((uv.x + offX) / bw), fract(uv.y / bh));   // 0..1 inside brick

  // Mortar — distance to nearest brick edge in pixels
  vec2 px = vec2(dFdx(uv.x), dFdy(uv.y)) * u_res.y;
  float mortarPx = 1.6;  // joint thickness in px
  float edgeX = min(local.x, 1.0 - local.x) * bw * u_res.y;
  float edgeY = min(local.y, 1.0 - local.y) * bh * u_res.y;
  float edge  = min(edgeX, edgeY);
  float mortar = 1.0 - smoothstep(mortarPx, mortarPx + 1.2, edge);

  // Per-brick variance
  float bid    = hash1(cell);
  float bidB   = hash1(cell + 17.3);
  vec2  bidV   = hash2(cell);

  // Stone surface: fbm computed in LOCAL brick coords + per-brick seed offset,
  // so the texture starts fresh at every brick edge instead of bleeding across.
  vec2  stoneUV = local * vec2(rows * 4.0, rows * 2.0) + bidV * 50.0;
  float stone   = fbm(stoneUV);                 // 0..1
  float stone2  = fbm(stoneUV * 2.7 + bidV * 23.0);

  // Brick base tone — warm/cool variance around fg
  float tone = mix(0.7, 1.15, bid);
  tone *= mix(0.92, 1.05, stone);                // surface light/dark
  vec3 brick = u_fg * tone;

  // Subtle warm tint on a fraction of bricks (clinker variance)
  float warm = smoothstep(0.78, 0.96, bidB);
  brick = mix(brick, mix(brick, u_accent, 0.35), warm * 0.6);

  // Pock marks / chips — sparse dark spots from fbm
  float chip = smoothstep(0.78, 0.86, stone2);
  brick *= mix(1.0, 0.78, chip);

  // Mortar color = darkened bg with its own grain
  vec3 mortarCol = mix(u_bg * 0.55, u_bg * 0.9, fbm(uv * rows * 18.0));

  // Compose brick vs mortar
  vec3 col = mix(brick, mortarCol, mortar);

  // Very rare accent brick (every ~40 bricks) softly pulsing
  float pick = step(0.975, bidB);
  float pulse = 0.5 + 0.5*sin(t * 0.7 + bid * 30.0);
  col = mix(col, u_accent * 0.85, pick * pulse * 0.55 * u_intensity);

  // Global grain (subtle)
  float g = (hash1(gl_FragCoord.xy) - 0.5) * 0.035;
  col += g;

  // Vignette
  vec2 vu = (gl_FragCoord.xy - 0.5*u_res) / u_res.y;
  float vig = smoothstep(1.5, 0.4, length(vu * vec2(1.0, 1.15)));
  col *= mix(0.7, 1.0, vig);

  gl_FragColor = vec4(col, 1.0);
}
`;

function hexToRgb(hex){
  const h = hex.replace('#','');
  const n = h.length === 3
    ? h.split('').map(c=>c+c).join('')
    : h;
  const r = parseInt(n.slice(0,2),16)/255;
  const g = parseInt(n.slice(2,4),16)/255;
  const b = parseInt(n.slice(4,6),16)/255;
  return [r,g,b];
}

function ShaderHero({ speed=1, intensity=1, scale=4.5, bg='#15140f', fg='#3b3a33', accent='#c25a2a', paused=false, style }){
  const ref = React.useRef(null);
  const stateRef = React.useRef({});

  // Keep latest props in a ref so the rAF loop reads fresh values without re-init.
  const propsRef = React.useRef({ speed, intensity, scale, bg, fg, accent, paused });
  propsRef.current = { speed, intensity, scale, bg, fg, accent, paused };

  React.useEffect(() => {
    const canvas = ref.current;
    if(!canvas) return;
    const gl = canvas.getContext('webgl', { antialias: false, premultipliedAlpha: false });
    if(!gl){
      canvas.style.background = bg;
      return;
    }
    gl.getExtension('OES_standard_derivatives');

    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('Shader compile error:', gl.getShaderInfoLog(s));
      }
      return s;
    }
    const vs = compile(gl.VERTEX_SHADER, VORONOI_VERT);
    const fs = compile(gl.FRAGMENT_SHADER, VORONOI_FRAG);
    const prog = gl.createProgram();
    gl.attachShader(prog, vs); gl.attachShader(prog, 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 aPos = gl.getAttribLocation(prog, 'a_pos');
    gl.enableVertexAttribArray(aPos);
    gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);

    const uRes = gl.getUniformLocation(prog, 'u_res');
    const uTime = gl.getUniformLocation(prog, 'u_time');
    const uSpeed = gl.getUniformLocation(prog, 'u_speed');
    const uIntensity = gl.getUniformLocation(prog, 'u_intensity');
    const uScale = gl.getUniformLocation(prog, 'u_scale');
    const uBg = gl.getUniformLocation(prog, 'u_bg');
    const uFg = gl.getUniformLocation(prog, 'u_fg');
    const uAccent = gl.getUniformLocation(prog, 'u_accent');

    function resize(){
      const dpr = Math.min(window.devicePixelRatio || 1, 1.75);
      const w = canvas.clientWidth;
      const h = canvas.clientHeight;
      canvas.width = Math.max(1, Math.floor(w * dpr));
      canvas.height = Math.max(1, Math.floor(h * dpr));
      gl.viewport(0, 0, canvas.width, canvas.height);
    }
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    let raf = 0;
    let t0 = performance.now();
    let acc = 0;
    let last = t0;

    function frame(now){
      const dt = (now - last) / 1000;
      last = now;
      const p = propsRef.current;
      if(!p.paused) acc += dt;

      gl.uniform2f(uRes, canvas.width, canvas.height);
      gl.uniform1f(uTime, acc);
      gl.uniform1f(uSpeed, p.speed);
      gl.uniform1f(uIntensity, p.intensity);
      gl.uniform1f(uScale, p.scale);
      gl.uniform3fv(uBg, hexToRgb(p.bg));
      gl.uniform3fv(uFg, hexToRgb(p.fg));
      gl.uniform3fv(uAccent, hexToRgb(p.accent));
      gl.drawArrays(gl.TRIANGLES, 0, 6);
      raf = requestAnimationFrame(frame);
    }
    raf = requestAnimationFrame(frame);

    stateRef.current = { gl, prog, raf, ro };
    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      gl.deleteProgram(prog);
      gl.deleteShader(vs);
      gl.deleteShader(fs);
      gl.deleteBuffer(buf);
    };
  }, []);

  return <canvas ref={ref} style={{ display:'block', width:'100%', height:'100%', ...style }} />;
}

window.ShaderHero = ShaderHero;
