// halftone.jsx — animated dot-wave canvas, SIGNAL NOISE style.
// Props: motion, color, gap, intensity, channels (CMYK separation [{color,dx,dy}])
const { useRef, useEffect } = React;

function toRGB(color) {
  const probe = document.createElement('div'); probe.style.color = color; document.body.appendChild(probe);
  const rgb = getComputedStyle(probe).color; document.body.removeChild(probe);
  const m = rgb.match(/\d+/g) || [189, 189, 189];
  return [m[0], m[1], m[2]];
}

function Halftone({ motion = true, color = '#bdbdbd', gap = 16, className = '', style = {}, intensity = 1, channels = null }) {
  const canvasRef = useRef(null);
  const rafRef = useRef(0);
  const tRef = useRef(0);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2);
    const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    const resize = () => {
      const r = canvas.getBoundingClientRect();
      w = Math.max(1, r.width); h = Math.max(1, r.height);
      canvas.width = Math.floor(w * dpr); canvas.height = Math.floor(h * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(canvas);

    // CMYK separation layers, or a single channel from `color`
    const layers = (channels && channels.length)
      ? channels.map((c) => ({ rgb: toRGB(c.color), dx: c.dx || 0, dy: c.dy || 0 }))
      : [{ rgb: toRGB(color), dx: 0, dy: 0 }];
    const multi = layers.length > 1;

    const drawLayer = (rgb, dx, dy, t) => {
      const [cr, cg, cb] = rgb;
      const cols = Math.ceil(w / gap) + 2;
      const rows = Math.ceil(h / gap) + 2;
      const midY = h / 2;
      for (let i = 0; i < cols; i++) {
        const x = i * gap + dx;
        const phase = (x / w) * Math.PI * 4;
        const wave = Math.sin(phase - t) * 0.6 + Math.sin(phase * 0.5 + t * 0.7) * 0.4;
        const bandY = midY + wave * (h * 0.30) * intensity;
        for (let j = 0; j < rows; j++) {
          const y = j * gap + dy;
          const d = Math.abs(y - bandY);
          const noise = (Math.sin(x * 12.9898 + y * 78.233 + t) * 43758.5453) % 1;
          const falloff = Math.max(0, 1 - d / (h * 0.34));
          let a = Math.pow(falloff, 1.6) * (0.55 + 0.45 * Math.abs(noise));
          if (a < 0.02) continue;
          const rad = (0.6 + a * 2.4) * (multi ? 1.15 : 1);
          ctx.beginPath();
          ctx.fillStyle = `rgba(${cr},${cg},${cb},${a})`;
          ctx.arc(x, y, rad, 0, Math.PI * 2);
          ctx.fill();
        }
      }
    };

    const draw = () => {
      ctx.clearRect(0, 0, w, h);
      const t = tRef.current;
      ctx.globalCompositeOperation = multi ? 'lighter' : 'source-over';
      for (const L of layers) drawLayer(L.rgb, L.dx, L.dy, t);
      ctx.globalCompositeOperation = 'source-over';
    };

    const loop = () => {
      if (motion && !prefersReduced) tRef.current += 0.012;
      draw();
      rafRef.current = requestAnimationFrame(loop);
    };
    loop();
    return () => { cancelAnimationFrame(rafRef.current); ro.disconnect(); };
  }, [motion, color, gap, intensity, channels]);

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

window.Halftone = Halftone;
