// src/dots-v2.jsx — Dot-matrix engine for Mighty Orbit
// Renders particles that simulate gravitational pull toward an event horizon.
// All <DotCanvas> variants share one renderer; you tune via `mode` + props.
//
// Exports to window: DotCanvas, OrbitRings, AccretionDisk, DotHalftone
//
// Performance notes:
// - Renders at devicePixelRatio with capped DPR (1.5 max)
// - Reduced motion -> static layout, no animation tick

const DOT_DEFAULTS = {
  density: 1.0,           // 0.3..2.5 — tweakable
  ringR: 0.18,            // event horizon ring radius (0..1 of min(w,h))
  pullStrength: 0.08,     // base inward velocity
  swirl: 0.6,             // tangential spin
  baseSize: 1.0,          // base dot radius in CSS px (smaller = higher fidelity)
  color: "#FFFFFF",
  bg: "transparent",      // canvas bg
  fade: 0.18,             // trail fade per frame (0=no trail)
  centerVoid: 0.08,       // black hole (no dots inside this radius)
  highlight: null,        // accent color used near the horizon (string)
  highlightR: 0.22,       // radius band that gets the highlight color
};

function makeRng(seed){
  let s = seed >>> 0;
  return () => {
    s = (s + 0x6D2B79F5) >>> 0;
    let t = s;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

// Spawn a particle near the outer rim — angle and offset randomized.
function spawn(p, rng, w, h){
  const minDim = Math.min(w, h);
  const maxR = Math.hypot(w, h) * 0.55;
  const theta = rng() * Math.PI * 2;
  const r = maxR * (0.45 + rng() * 0.55);
  p.x = w/2 + Math.cos(theta) * r;
  p.y = h/2 + Math.sin(theta) * r;
  p.life = 1.0;
  p.r = 0.6 + rng() * 1.4;
  p.tw = 0.6 + rng() * 0.4; // base alpha
  p.seed = rng();
}

function useDotEngine(canvasRef, opts){
  const cfg = { ...DOT_DEFAULTS, ...opts };

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    let w = 0, h = 0;
    let particles = [];
    let raf = 0;
    let alive = true;

    const dpr = Math.min(window.devicePixelRatio || 1, 2);

    function resize(){
      const rect = canvas.getBoundingClientRect();
      w = rect.width; h = rect.height;
      canvas.width = Math.round(w * dpr);
      canvas.height = Math.round(h * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      seedField();
    }

    function seedField(){
      const target = Math.max(60, Math.floor((w * h) / 1600 * cfg.density));
      const rng = makeRng(7);
      particles = new Array(target).fill(0).map(() => {
        const p = {};
        spawn(p, rng, w, h);
        // Pre-age so the first frame doesn't show empty rim
        p.life = rng();
        return p;
      });
    }

    function tick(){
      if (!alive) return;
      const cx = w/2, cy = h/2;
      const minDim = Math.min(w, h);
      const horizonR = cfg.ringR * minDim;
      const voidR = cfg.centerVoid * minDim;
      const highlightR = cfg.highlightR * minDim;

      // Fade existing frame for trail
      if (cfg.bg === "transparent"){
        ctx.clearRect(0, 0, w, h);
      } else {
        ctx.fillStyle = cfg.bg;
        ctx.fillRect(0, 0, w, h);
      }

      const rng = makeRng(101);
      for (let i = 0; i < particles.length; i++){
        const p = particles[i];
        const dx = cx - p.x, dy = cy - p.y;
        const d = Math.hypot(dx, dy) + 0.001;

        // Inward + tangential velocity
        const inward = cfg.pullStrength * (1 + (200 / (d + 30)));
        const vx = (dx / d) * inward;
        const vy = (dy / d) * inward;
        // Perpendicular swirl
        const sx = -dy / d * cfg.swirl;
        const sy =  dx / d * cfg.swirl;

        p.x += vx + sx;
        p.y += vy + sy;
        p.life -= 0.004;

        // Respawn when crossing event horizon or dying
        if (d < voidR || p.life <= 0 || p.x < -20 || p.x > w+20 || p.y < -20 || p.y > h+20){
          spawn(p, rng, w, h);
          continue;
        }

        // Color: highlight near horizon if configured
        let color = cfg.color;
        let alpha = p.tw * Math.min(1, p.life * 1.6);
        if (cfg.highlight && d < highlightR){
          color = cfg.highlight;
          alpha *= 1.1;
        }

        // Size grows slightly as it approaches horizon (gravitational compression)
        const sizeScale = 1 + Math.max(0, (highlightR - d) / highlightR) * 0.8;

        ctx.globalAlpha = alpha;
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.arc(p.x, p.y, cfg.baseSize * p.r * sizeScale, 0, Math.PI*2);
        ctx.fill();
      }
      ctx.globalAlpha = 1;

      if (!reduceMotion) raf = requestAnimationFrame(tick);
    }

    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    if (reduceMotion){
      // Single static draw
      tick();
    } else {
      raf = requestAnimationFrame(tick);
    }

    return () => {
      alive = false;
      cancelAnimationFrame(raf);
      ro.disconnect();
    };
  }, [cfg.density, cfg.ringR, cfg.pullStrength, cfg.swirl, cfg.baseSize, cfg.color, cfg.bg, cfg.fade, cfg.centerVoid, cfg.highlight, cfg.highlightR]);
}

function DotCanvas(props){
  const ref = React.useRef(null);
  useDotEngine(ref, props);
  return <canvas ref={ref} style={{ width:"100%", height:"100%", display:"block", ...(props.style||{}) }}/>;
}

// ── Decorative SVG: concentric orbit rings (the "blueprint" lines) ───
function OrbitRings({ color = "rgba(255,255,255,.12)", count = 6, style }){
  return (
    <svg
      viewBox="-100 -60 200 120"
      preserveAspectRatio="xMidYMid meet"
      style={{position:"absolute",inset:0,width:"100%",height:"100%",pointerEvents:"none",...style}}
      aria-hidden="true"
    >
      {Array.from({length:count}).map((_,i)=>(
        <ellipse key={i}
          cx="0" cy="0"
          rx={20 + i*12}
          ry={(20 + i*12) * 0.55}
          fill="none"
          stroke={color}
          strokeWidth="0.3"
        />
      ))}
      {/* Cardinal lines */}
      <line x1="-100" y1="0" x2="100" y2="0" stroke={color} strokeWidth="0.2"/>
      <line x1="0" y1="-60" x2="0" y2="60" stroke={color} strokeWidth="0.2"/>
    </svg>
  );
}

// ── Static halftone field — non-animated dot pattern for backgrounds ─
function DotHalftone({ color = "#1b2540", opacity = 0.12, size = 10, dot = 1.4, style }){
  const id = React.useId();
  return (
    <svg
      style={{position:"absolute",inset:0,width:"100%",height:"100%",pointerEvents:"none",opacity,...style}}
      aria-hidden="true"
    >
      <defs>
        <pattern id={id} width={size} height={size} patternUnits="userSpaceOnUse">
          <circle cx={size/2} cy={size/2} r={dot} fill={color}/>
        </pattern>
      </defs>
      <rect width="100%" height="100%" fill={`url(#${id})`}/>
    </svg>
  );
}

Object.assign(window, {
  DotCanvas, OrbitRings, DotHalftone,
});
