// smoke-background.jsx — global atmospheric smoke shader (WebGL2).
// One canvas, fixed full-viewport, sits behind everything. Adapted from the
// "spooky-smoke" shader; TS types stripped, smokeColor takes our palette.

const _smokeVertex = `#version 300 es
precision highp float;
in vec4 position;
void main(){ gl_Position = position; }
`;

const _smokeFragment = `#version 300 es
precision highp float;
out vec4 O;
uniform float time;
uniform vec2 resolution;
uniform vec3 u_color;

#define FC gl_FragCoord.xy
#define R resolution
#define T (time + 660.)

float rnd(vec2 p){ p = fract(p * vec2(12.9898, 78.233)); p += dot(p, p + 34.56); return fract(p.x * p.y); }
float noise(vec2 p){
  vec2 i = floor(p), f = fract(p), u = f * f * (3. - 2. * f);
  return mix(mix(rnd(i),         rnd(i + vec2(1, 0)), u.x),
             mix(rnd(i + vec2(0, 1)), rnd(i + 1.),       u.x), u.y);
}
float fbm(vec2 p){
  float t = .0, a = 1.;
  for (int i = 0; i < 5; i++) { t += a * noise(p); p *= mat2(1, -1.2, .2, 1.2) * 2.; a *= .5; }
  return t;
}

void main(){
  vec2 uv = (FC - .5 * R) / R.y;
  vec3 col = vec3(1);
  uv.x += .25;
  uv *= vec2(2, 1);

  float n = fbm(uv * .28 - vec2(T * .01, 0));
  n = noise(uv * 3. + n * 2.);

  col.r -= fbm(uv               + vec2(0, T * .015) + n);
  col.g -= fbm(uv * 1.003       + vec2(0, T * .015) + n + .003);
  col.b -= fbm(uv * 1.006       + vec2(0, T * .015) + n + .006);

  col = mix(col, u_color, dot(col, vec3(.21, .71, .07)));
  col = mix(vec3(.05), col, min(time * .1, 1.));
  col = clamp(col, .04, 1.);

  O = vec4(col, 1);
}
`;

function _smokeHexToRgb(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]
    : [0.5, 0.5, 0.5];
}

class _SmokeRenderer {
  constructor(canvas) {
    this.canvas = canvas;
    this.gl = canvas.getContext('webgl2', { antialias: true }) ||
              canvas.getContext('webgl', { antialias: true });
    this.color = [0.5, 0.5, 0.5];
    this.program = null;
    this.vs = null;
    this.fs = null;
    this.buffer = null;
    if (!this.gl) return;            // no WebGL → stay inert, never throw
    try {
      this._setup();
      this._init();
    } catch (e) {
      this.program = null;
    }
  }
  updateColor(rgb) { this.color = rgb; }
  updateScale() {
    if (!this.gl) return;
    // Render at 1x even on retina. The shader is atmospheric noise — a
    // higher-resolution canvas costs ~4× fragment work for no visible win.
    const dpr = 1;
    // Use the canvas's ACTUAL rendered box (it is sized to 100lvh/100vw in CSS),
    // so the buffer always covers the full mobile screen — incl. the area behind
    // a retracting URL bar — instead of the short window.innerHeight.
    const rect = this.canvas.getBoundingClientRect();
    const w = Math.ceil(rect.width)  || window.innerWidth;
    const h = Math.ceil(rect.height) || window.innerHeight;
    this.canvas.width  = Math.floor(w * dpr);
    this.canvas.height = Math.floor(h * dpr);
    this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
  }
  _compile(shader, src) {
    const gl = this.gl;
    gl.shaderSource(shader, src);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      console.error('smoke shader compile', gl.getShaderInfoLog(shader));
    }
  }
  _setup() {
    const gl = this.gl;
    this.vs = gl.createShader(gl.VERTEX_SHADER);
    this.fs = gl.createShader(gl.FRAGMENT_SHADER);
    const program = gl.createProgram();
    this._compile(this.vs, _smokeVertex);
    this._compile(this.fs, _smokeFragment);
    gl.attachShader(program, this.vs);
    gl.attachShader(program, this.fs);
    gl.linkProgram(program);
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
      console.error('smoke program link', gl.getProgramInfoLog(program));
    }
    this.program = program;
  }
  _init() {
    const { gl, program } = this;
    this.buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, 1, -1, -1, 1, 1, 1, -1]), gl.STATIC_DRAW);
    const pos = gl.getAttribLocation(program, 'position');
    gl.enableVertexAttribArray(pos);
    gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
    this.u_resolution = gl.getUniformLocation(program, 'resolution');
    this.u_time       = gl.getUniformLocation(program, 'time');
    this.u_color      = gl.getUniformLocation(program, 'u_color');
  }
  render(now) {
    const { gl, program, buffer, canvas } = this;
    if (!gl || !program) return;
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.useProgram(program);
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.uniform2f(this.u_resolution, canvas.width, canvas.height);
    gl.uniform1f(this.u_time, now * 1e-3);
    gl.uniform3fv(this.u_color, this.color);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  }
  reset() {
    const { gl, program, vs, fs } = this;
    if (!program) return;
    if (vs) { gl.detachShader(program, vs); gl.deleteShader(vs); }
    if (fs) { gl.detachShader(program, fs); gl.deleteShader(fs); }
    gl.deleteProgram(program);
    this.program = null;
  }
}

function SmokeBackground({ smokeColor = '#5b8bff', style }) {
  const canvasRef = React.useRef(null);
  const rendererRef = React.useRef(null);

  React.useEffect(() => {
    if (!canvasRef.current) return;
    const r = new _SmokeRenderer(canvasRef.current);
    rendererRef.current = r;
    const onResize = () => r.updateScale();
    onResize();
    window.addEventListener('resize', onResize);

    // Pause when tab hidden + run at ~30fps instead of vsync 60fps.
    let raf = 0;
    let last = 0;
    const minFrameMs = 1000 / 30;
    const loop = (now) => {
      raf = requestAnimationFrame(loop);
      if (document.hidden) return;
      if (now - last < minFrameMs) return;
      last = now;
      r.render(now);
    };
    raf = requestAnimationFrame(loop);
    return () => {
      window.removeEventListener('resize', onResize);
      cancelAnimationFrame(raf);
      r.reset();
    };
  }, []);

  React.useEffect(() => {
    const r = rendererRef.current;
    if (r) r.updateColor(_smokeHexToRgb(smokeColor));
  }, [smokeColor]);

  return (
    <canvas
      ref={canvasRef}
      style={{
        position: 'fixed',
        top: '-8vh',
        left: '-4vw',
        width: '108vw',
        height: '116vh',
        zIndex: -1,
        pointerEvents: 'none',
        display: 'block',
        ...style,
      }}
      className="smoke-bg"
    />
  );
}

window.SmokeBackground = SmokeBackground;
