// src/illustrations-v2.jsx — Sales-motion dot illustrations for Mighty Orbit
//
// Five canvas components, each a different marketing/sales metaphor rendered
// as the same dot primitive:
//
//   Globe         — dot-rendered rotating planet (brand object)
//   Funnel        — gravitational funnel (prospects → close)
//   Convergence   — multi-source lead generation (channels → conversion)
//   Pipeline      — horizontal pipeline stages (Lead → MQL → SQL → Won)
//   Broadcast     — radial ad burst (the opposite of gravity)
//
// All share the same DPR + ResizeObserver + RAF scaffolding via useCanvasLoop.
// Exposed via window for use across Babel scripts.

// ─── Shared canvas hook ───────────────────────────────────────────────
function useCanvasLoop(canvasRef, draw, deps){
  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    let w = 0, h = 0, t0 = performance.now(), raf = 0, alive = true;
    const state = {};

    function resize(){
      const rect = canvas.getBoundingClientRect();
      w = rect.width; h = rect.height;
      if (w <= 0 || h <= 0) return;
      canvas.width = Math.round(w * dpr);
      canvas.height = Math.round(h * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      state.w = w; state.h = h; state.init = false;
    }

    function tick(now){
      if (!alive) return;
      const dt = Math.min(40, now - t0) / 16.67;
      t0 = now;
      ctx.clearRect(0, 0, w, h);
      draw(ctx, { w, h, dt, t:now, state });
      if (!reduce) raf = requestAnimationFrame(tick);
    }

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

    if (reduce) tick(performance.now());
    else raf = requestAnimationFrame(tick);

    return () => { alive = false; cancelAnimationFrame(raf); ro.disconnect(); };
  }, deps);
}

// ─── PRNG ────────────────────────────────────────────────────────────
function mrng(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;
  };
}

// ═══════════════════════════════════════════════════════════════════════
// 1. GLOBE — rotating dot-rendered planet
//    Spherical lat/lon grid, only front-facing dots drawn, rim glows.
// ═══════════════════════════════════════════════════════════════════════
function Globe({ color = "#FFFFFF", accent = "#E724FF", speed = 0.0003, density = 1.2, style }){
  const ref = React.useRef(null);
  useCanvasLoop(ref, (ctx, { w, h, t }) => {
    const cx = w/2, cy = h/2;
    const r = Math.min(w, h) * 0.42;
    const rot = t * speed;

    const latSteps = Math.max(14, Math.round(28 * density));
    for (let i = 1; i < latSteps; i++){
      const lat = (i / latSteps - 0.5) * Math.PI; // -π/2 .. π/2
      const rho = r * Math.cos(lat);
      const y = cy + r * Math.sin(lat);
      const lonCount = Math.max(6, Math.round(72 * density * Math.cos(lat)));
      for (let j = 0; j < lonCount; j++){
        const lon = (j / lonCount) * Math.PI * 2 + rot;
        const cosLon = Math.cos(lon);
        const sinLon = Math.sin(lon);
        // Skip back hemisphere
        if (cosLon < -0.05) continue;
        const x = cx + rho * sinLon;
        // Rim emphasis: |cosLon| near 0 = at the rim
        const rim = 1 - Math.abs(cosLon);
        const alpha = 0.18 + rim * 0.82;
        const useAccent = rim > 0.92 && Math.abs(lat) < 0.6;
        ctx.globalAlpha = alpha;
        ctx.fillStyle = useAccent ? accent : color;
        const size = 0.7 + rim * 1.1;
        ctx.beginPath();
        ctx.arc(x, y, size, 0, Math.PI*2);
        ctx.fill();
      }
    }
    ctx.globalAlpha = 1;
  }, [color, accent, speed, density]);
  return <canvas ref={ref} style={{width:"100%",height:"100%",display:"block",...style}}/>;
}

// ═══════════════════════════════════════════════════════════════════════
// 2. FUNNEL — gravitational funnel
//    Particles spawn across top, pulled toward central axis as they fall,
//    exit at bottom as a concentrated stream. The funnel SHAPE emerges
//    from particle density alone — never drawn.
// ═══════════════════════════════════════════════════════════════════════
function Funnel({ color = "#FFFFFF", accent = "#E724FF", density = 1.0, mouthWidth = 0.85, throatWidth = 0.10, style }){
  const ref = React.useRef(null);
  useCanvasLoop(ref, (ctx, { w, h, dt, state }) => {
    if (!state.init){
      const target = Math.max(80, Math.floor((w * h) / 800 * density));
      const rng = mrng(13);
      state.parts = new Array(target).fill(0).map(() => ({
        x: w * (0.5 - mouthWidth/2 + rng() * mouthWidth),
        y: -rng() * h * 0.2,
        vx: 0, vy: 0.4 + rng() * 0.6,
        r: 0.7 + rng() * 0.8,
        life: rng(),
        rng,
      }));
      state.init = true;
    }
    const cx = w/2;
    for (const p of state.parts){
      const yt = Math.max(0, Math.min(1, p.y / h));
      // Funnel half-width at this y: lerp from mouth → throat
      const halfW = (mouthWidth * (1 - yt) + throatWidth * yt) * w / 2;
      // Pull horizontally toward centerline, scaled by how far outside the funnel
      const dx = cx - p.x;
      const outside = Math.max(0, Math.abs(dx) - halfW);
      const pull = Math.sign(dx) * (0.02 + outside * 0.08);
      p.vx += pull * dt;
      p.vx *= 0.94; // friction
      p.x += p.vx * dt;
      p.y += p.vy * dt;

      const inside = Math.abs(dx) < halfW;
      const dist = Math.min(1, Math.abs(dx) / Math.max(halfW, 1));
      const isAccent = yt > 0.7 && inside;
      const alpha = inside ? (0.35 + (1 - dist) * 0.55) : 0.25;
      ctx.globalAlpha = alpha;
      ctx.fillStyle = isAccent ? accent : color;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.r * (isAccent ? 1.2 : 1), 0, Math.PI*2);
      ctx.fill();

      if (p.y > h + 4){
        p.y = -4 - p.rng() * h * 0.1;
        p.x = w * (0.5 - mouthWidth/2 + p.rng() * mouthWidth);
        p.vx = 0;
        p.vy = 0.4 + p.rng() * 0.6;
      }
    }
    ctx.globalAlpha = 1;
  }, [color, accent, density, mouthWidth, throatWidth]);
  return <canvas ref={ref} style={{width:"100%",height:"100%",display:"block",...style}}/>;
}

// ═══════════════════════════════════════════════════════════════════════
// 3. CONVERGENCE — multi-source lead generation
//    N source points around the perimeter; each emits particles that
//    travel toward center. Each source can be color-tinted (channels).
// ═══════════════════════════════════════════════════════════════════════
function Convergence({ color = "#FFFFFF", accent = "#E724FF", sources = 5, density = 1.0, style }){
  const ref = React.useRef(null);
  useCanvasLoop(ref, (ctx, { w, h, dt, state }) => {
    const cx = w/2, cy = h/2;
    if (!state.init){
      const target = Math.max(120, Math.floor((w * h) / 700 * density));
      const rng = mrng(31);
      // Place sources at angles around an outer ring
      const srcs = [];
      for (let i = 0; i < sources; i++){
        const a = (i / sources) * Math.PI * 2 - Math.PI/2;
        const r = Math.min(w, h) * 0.46;
        srcs.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r });
      }
      state.srcs = srcs;
      state.parts = new Array(target).fill(0).map(() => spawnConv(srcs, rng, cx, cy));
      state.rng = rng;
      state.init = true;
    }
    // Draw source markers (small ring + dot)
    for (const s of state.srcs){
      ctx.globalAlpha = 0.55;
      ctx.strokeStyle = color;
      ctx.lineWidth = 0.6;
      ctx.beginPath(); ctx.arc(s.x, s.y, 8, 0, Math.PI*2); ctx.stroke();
      ctx.globalAlpha = 1;
      ctx.fillStyle = color;
      ctx.beginPath(); ctx.arc(s.x, s.y, 2, 0, Math.PI*2); ctx.fill();
    }
    // Center target — accent ring
    ctx.globalAlpha = 0.9;
    ctx.strokeStyle = accent;
    ctx.lineWidth = 0.8;
    ctx.beginPath(); ctx.arc(cx, cy, 14, 0, Math.PI*2); ctx.stroke();
    ctx.fillStyle = accent;
    ctx.beginPath(); ctx.arc(cx, cy, 3, 0, Math.PI*2); ctx.fill();

    for (const p of state.parts){
      const dx = cx - p.x, dy = cy - p.y;
      const d = Math.hypot(dx, dy) + 0.001;
      // Accelerate toward center, slight curve
      const a = 0.06;
      p.vx += (dx/d) * a * dt;
      p.vy += (dy/d) * a * dt;
      p.vx *= 0.98; p.vy *= 0.98;
      p.x += p.vx * dt;
      p.y += p.vy * dt;

      const t = 1 - Math.min(1, d / (Math.min(w, h) * 0.46));
      ctx.globalAlpha = 0.35 + t * 0.55;
      ctx.fillStyle = d < 28 ? accent : color;
      ctx.beginPath();
      ctx.arc(p.x, p.y, 0.8 + t * 0.6, 0, Math.PI*2);
      ctx.fill();

      if (d < 10) Object.assign(p, spawnConv(state.srcs, state.rng, cx, cy));
    }
    ctx.globalAlpha = 1;
  }, [color, accent, sources, density]);
  return <canvas ref={ref} style={{width:"100%",height:"100%",display:"block",...style}}/>;
}
function spawnConv(srcs, rng, cx, cy){
  const s = srcs[Math.floor(rng() * srcs.length)];
  // Small jitter at source
  const j = rng() * Math.PI * 2;
  const r = rng() * 4;
  return {
    x: s.x + Math.cos(j) * r,
    y: s.y + Math.sin(j) * r,
    vx: 0, vy: 0,
  };
}

// ═══════════════════════════════════════════════════════════════════════
// 4. PIPELINE — horizontal sales stages
//    N stage clusters along x. Particles cluster at each stage, then a
//    fraction "advances" to the next stage. Drop-off rendered as a fade
//    on particles that don't make the jump.
// ═══════════════════════════════════════════════════════════════════════
function Pipeline({
  color = "#FFFFFF",
  accent = "#E724FF",
  stages = ["Lead","MQL","SQL","Opp","Won"],
  density = 1.0,
  style,
  showLabels = true,
}){
  const ref = React.useRef(null);
  useCanvasLoop(ref, (ctx, { w, h, dt, state }) => {
    const margin = 40;
    const stageCount = stages.length;
    const cy = h/2;
    const gap = (w - margin*2) / (stageCount - 1);
    if (!state.init){
      const target = Math.max(140, Math.floor((w * h) / 700 * density));
      const rng = mrng(53);
      state.parts = new Array(target).fill(0).map((_,i) => ({
        stage: Math.floor(rng() * stageCount),
        x: 0, y: 0, vx: 0, vy: 0,
        target: 0,
        nextAt: rng() * 4000,
        rng,
      }));
      state.t = 0;
      state.init = true;
    }
    state.t += dt * 16;

    // Stage anchor positions
    const anchors = stages.map((_,i) => margin + i * gap);

    // Draw stage rails
    ctx.globalAlpha = 0.15;
    ctx.strokeStyle = color;
    ctx.lineWidth = 0.5;
    ctx.beginPath();
    ctx.moveTo(margin, cy); ctx.lineTo(w - margin, cy);
    ctx.stroke();
    ctx.globalAlpha = 1;

    // Stage dots
    for (let i = 0; i < anchors.length; i++){
      const isLast = i === anchors.length - 1;
      ctx.fillStyle = isLast ? accent : color;
      ctx.globalAlpha = 0.9;
      ctx.beginPath();
      ctx.arc(anchors[i], cy, isLast ? 4 : 3, 0, Math.PI*2);
      ctx.fill();
      ctx.globalAlpha = 0.4;
      ctx.strokeStyle = isLast ? accent : color;
      ctx.lineWidth = 0.6;
      ctx.beginPath();
      ctx.arc(anchors[i], cy, 14, 0, Math.PI*2);
      ctx.stroke();
    }
    ctx.globalAlpha = 1;

    // Particles cluster around stage anchors
    for (const p of state.parts){
      if (p.target === 0){
        const baseX = anchors[p.stage];
        // Hover near anchor
        const tx = baseX + (p.rng() - 0.5) * 24;
        const ty = cy + (p.rng() - 0.5) * 40;
        p.x = p.x || tx;
        p.y = p.y || ty;
        p.tx = p.tx ?? tx;
        p.ty = p.ty ?? ty;
        p.x += (p.tx - p.x) * 0.08;
        p.y += (p.ty - p.y) * 0.08;

        // Time to advance?
        p.nextAt -= dt * 16;
        if (p.nextAt <= 0){
          // 65% chance to advance to next stage, else respawn at top
          if (p.stage < stageCount - 1 && p.rng() < 0.72){
            p.target = 1;
            p.targetStage = p.stage + 1;
            p.targetX = anchors[p.targetStage] + (p.rng() - 0.5) * 24;
            p.targetY = cy + (p.rng() - 0.5) * 40;
          } else {
            // drop off -> respawn at first stage
            p.stage = 0;
            p.tx = anchors[0] + (p.rng() - 0.5) * 24;
            p.ty = cy + (p.rng() - 0.5) * 40;
            p.nextAt = 1500 + p.rng() * 3500;
          }
        }
      } else {
        // Travelling to next stage
        const dx = p.targetX - p.x, dy = p.targetY - p.y;
        const d = Math.hypot(dx, dy) + 0.001;
        p.x += (dx / d) * 1.6 * dt;
        p.y += (dy / d) * 1.6 * dt;
        if (d < 2){
          p.stage = p.targetStage;
          p.tx = p.targetX;
          p.ty = p.targetY;
          p.target = 0;
          p.nextAt = 1500 + p.rng() * 3500;
        }
      }

      const isWon = p.stage === stageCount - 1;
      ctx.globalAlpha = 0.55;
      ctx.fillStyle = isWon ? accent : color;
      ctx.beginPath();
      ctx.arc(p.x, p.y, isWon ? 1.4 : 1.1, 0, Math.PI*2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;

    // Stage labels
    if (showLabels){
      ctx.fillStyle = color;
      ctx.globalAlpha = 0.55;
      ctx.font = '500 10px "Geist", "DM Sans", system-ui, sans-serif';
      ctx.textAlign = "center";
      for (let i = 0; i < anchors.length; i++){
        ctx.fillText(stages[i].toUpperCase(), anchors[i], cy + 38);
      }
      ctx.globalAlpha = 1;
    }
  }, [color, accent, stages.join("|"), density, showLabels]);
  return <canvas ref={ref} style={{width:"100%",height:"100%",display:"block",...style}}/>;
}

// ═══════════════════════════════════════════════════════════════════════
// 5. BROADCAST — radial ad burst (inverse of gravity)
//    A center emitter pulses outward; particles travel in radiating
//    waves, fade as they expand. Use for ads, distribution, reach.
// ═══════════════════════════════════════════════════════════════════════
function Broadcast({ color = "#FFFFFF", accent = "#E724FF", density = 1.0, style }){
  const ref = React.useRef(null);
  useCanvasLoop(ref, (ctx, { w, h, dt, t, state }) => {
    const cx = w/2, cy = h/2;
    const maxR = Math.hypot(w, h) * 0.55;
    if (!state.init){
      const target = Math.max(140, Math.floor((w * h) / 700 * density));
      const rng = mrng(71);
      state.parts = new Array(target).fill(0).map(() => spawnBroadcast(rng, maxR));
      state.rng = rng;
      state.init = true;
    }
    // Concentric ripple rings (thin)
    const rings = 4;
    for (let i = 0; i < rings; i++){
      const phase = ((t / 2400) + i / rings) % 1;
      const r = phase * maxR;
      ctx.globalAlpha = (1 - phase) * 0.18;
      ctx.strokeStyle = color;
      ctx.lineWidth = 0.6;
      ctx.beginPath();
      ctx.arc(cx, cy, r, 0, Math.PI*2);
      ctx.stroke();
    }
    ctx.globalAlpha = 1;
    // Center emitter
    ctx.fillStyle = accent;
    ctx.beginPath();
    ctx.arc(cx, cy, 4, 0, Math.PI*2);
    ctx.fill();

    for (const p of state.parts){
      p.r += p.speed * dt;
      const x = cx + Math.cos(p.a) * p.r;
      const y = cy + Math.sin(p.a) * p.r;
      const fade = 1 - p.r / maxR;
      ctx.globalAlpha = Math.max(0, fade * 0.85);
      ctx.fillStyle = p.r < maxR * 0.15 ? accent : color;
      ctx.beginPath();
      ctx.arc(x, y, 0.8 + fade * 0.8, 0, Math.PI*2);
      ctx.fill();
      if (p.r > maxR) Object.assign(p, spawnBroadcast(state.rng, maxR));
    }
    ctx.globalAlpha = 1;
  }, [color, accent, density]);
  return <canvas ref={ref} style={{width:"100%",height:"100%",display:"block",...style}}/>;
}
function spawnBroadcast(rng, maxR){
  return {
    a: rng() * Math.PI * 2,
    r: rng() * 8,
    speed: 0.4 + rng() * 0.9,
  };
}

Object.assign(window, {
  Globe, Funnel, Convergence, Pipeline, Broadcast,
});
