/* global React */
const { useEffect, useRef } = React;

function MotionCanvas({ type = 'harmonograph', speed = 1, density = 1, background = 'ink', interactive = true, className, style }) {
  const ref = useRef(null);
  const raf = useRef(0);
  const mouse = useRef({ x: 0.5, y: 0.5, active: false });

  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio || 1, 2);

    let W = 0, H = 0;
    const resize = () => {
      const parent = canvas.parentElement;
      const r = parent.getBoundingClientRect();
      W = Math.max(1, r.width); H = Math.max(1, r.height);
      canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
      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);
    if (canvas.parentElement) ro.observe(canvas.parentElement);

    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      mouse.current.x = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
      mouse.current.y = Math.max(0, Math.min(1, (e.clientY - r.top) / r.height));
      mouse.current.active = true;
    };
    const onLeave = () => { mouse.current.active = false; };
    if (interactive) {
      canvas.addEventListener('mousemove', onMove);
      canvas.addEventListener('mouseleave', onLeave);
    }

    const bg = background === 'ink' ? '#0E1014' : '#FAFAF7';
    const fg = background === 'ink' ? '#FAFAF7' : '#0E1014';
    const dim = background === 'ink' ? 'rgba(250,250,247,0.14)' : 'rgba(14,16,20,0.12)';
    const signal = '#D9541A';
    const signalDim = 'rgba(217,84,26,0.35)';

    let t0 = performance.now();

    const draw = (now) => {
      const t = ((now - t0) / 1000) * speed;
      const m = mouse.current;
      ctx.fillStyle = bg;
      ctx.fillRect(0, 0, W, H);

      const args = [ctx, W, H, t, density, fg, signal, dim, signalDim, m];
      switch (type) {
        case 'lissajous':   drawLissajous(...args); break;
        case 'field':       drawField(...args); break;
        case 'torus':       drawTorus(...args); break;
        case 'lorenz':      drawLorenz(...args); break;
        case 'rose':        drawRose(...args); break;
        case 'fourier':     drawFourier(...args); break;
        case 'dejong':      drawDeJong(...args); break;
        case 'phyllotaxis': drawPhyllotaxis(...args); break;
        default:            drawHarmonograph(...args);
      }

      raf.current = requestAnimationFrame(draw);
    };
    raf.current = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(raf.current);
      ro.disconnect();
      canvas.removeEventListener('mousemove', onMove);
      canvas.removeEventListener('mouseleave', onLeave);
    };
  }, [type, speed, density, background, interactive]);

  return <canvas ref={ref} className={className} style={style}/>;
}

/* ---------- Harmonograph ---------- */
function drawHarmonograph(ctx, W, H, t, density, fg, signal, dim, _sd, m) {
  const cx = W/2, cy = H/2;
  const R = Math.min(W, H) * 0.36;
  const slow = t * 0.12;
  const mx = (m.active ? m.x : 0.5) - 0.5;
  const my = (m.active ? m.y : 0.5) - 0.5;

  const layers = [
    { a1: 2.01 + mx*1.5, a2: 3.00 + my*1.5, p1: 0.0, p2: Math.PI/2, d1: 0.0020, d2: 0.0016, color: signal, w: 1.3, alpha: 0.95 },
    { a1: 3.00, a2: 2.00, p1: slow, p2: slow*0.5, d1: 0.0008, d2: 0.0010, color: fg, w: 0.8, alpha: 0.35 },
    { a1: 5.00, a2: 3.00, p1: slow*0.3, p2: -slow, d1: 0.0006, d2: 0.0008, color: fg, w: 0.6, alpha: 0.18 },
  ];
  const steps = Math.floor(1400 * density);
  layers.forEach(L => {
    ctx.globalAlpha = L.alpha; ctx.strokeStyle = L.color; ctx.lineWidth = L.w; ctx.beginPath();
    for (let i = 0; i <= steps; i++) {
      const tt = (i/steps)*40;
      const x = cx + R*(Math.sin(L.a1*tt+L.p1)*Math.exp(-L.d1*tt) + 0.6*Math.sin(L.a2*tt+L.p2)*Math.exp(-L.d2*tt));
      const y = cy + R*(Math.cos(L.a1*tt+L.p1+0.3)*Math.exp(-L.d1*tt) + 0.6*Math.cos(L.a2*tt+L.p2+0.9)*Math.exp(-L.d2*tt));
      i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y);
    }
    ctx.stroke();
  });
  ctx.globalAlpha = 1;
}

/* ---------- Lissajous ---------- */
function drawLissajous(ctx, W, H, t, density, fg, signal, dim, _sd, m) {
  const cx=W/2, cy=H/2;
  const R = Math.min(W,H)*0.38;
  const p = Math.sin(t*0.12)*Math.PI + (m.active ? (m.x-0.5)*Math.PI*2 : 0);
  const ratios = [[3,2],[5,4],[5,3]];
  ratios.forEach((r, idx) => {
    ctx.strokeStyle = idx===0 ? signal : fg;
    ctx.globalAlpha = idx===0 ? 0.95 : (idx===1 ? 0.28 : 0.14);
    ctx.lineWidth = idx===0 ? 1.4 : 0.8;
    ctx.beginPath();
    const steps = Math.floor(900*density);
    for (let i=0;i<=steps;i++){
      const tt = (i/steps)*Math.PI*2;
      const x = cx + R*Math.sin(r[0]*tt + p + idx*0.4);
      const y = cy + R*Math.sin(r[1]*tt);
      i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);
    }
    ctx.stroke();
  });
  ctx.globalAlpha = 1;
}

/* ---------- Vector field ---------- */
const fieldState = { particles: null };
function drawField(ctx, W, H, t, density, fg, signal, dim, _sd, m) {
  const N = Math.floor(180*density);
  if (!fieldState.particles || fieldState.particles.length !== N) {
    fieldState.particles = Array.from({length:N}, () => ({x:Math.random()*W, y:Math.random()*H, age:Math.random()*200, seed:Math.random()}));
  }
  const mx = m.active ? m.x*W : -1e9, my = m.active ? m.y*H : -1e9;
  fieldState.particles.forEach(p => {
    let fx = Math.cos(p.y*0.006+t*0.3) + Math.sin(p.x*0.004-t*0.2);
    let fy = Math.sin(p.x*0.006+t*0.25) + Math.cos(p.y*0.004+t*0.15);
    if (m.active) {
      const dx = p.x - mx, dy = p.y - my;
      const d2 = dx*dx+dy*dy + 2000;
      const k = 6000 / d2;
      fx += (dx/Math.sqrt(d2))*k;
      fy += (dy/Math.sqrt(d2))*k;
    }
    const nx = p.x + fx*1.2, ny = p.y + fy*1.2;
    const isSig = p.seed < 0.08;
    ctx.strokeStyle = isSig ? signal : fg;
    ctx.globalAlpha = isSig ? 0.9 : 0.25;
    ctx.lineWidth = isSig ? 1.3 : 0.7;
    ctx.beginPath(); ctx.moveTo(p.x,p.y); ctx.lineTo(nx,ny); ctx.stroke();
    p.x=nx; p.y=ny; p.age++;
    if (p.age>180 || p.x<0||p.x>W||p.y<0||p.y>H){ p.x=Math.random()*W; p.y=Math.random()*H; p.age=0; }
  });
  ctx.globalAlpha = 1;
}

/* ---------- Torus ---------- */
function drawTorus(ctx, W, H, t, density, fg, signal, dim, signalDim, m) {
  const cx=W/2, cy=H/2;
  const scale = Math.min(W,H)*0.36;
  const R=1.0, r=0.42;
  const ax = Math.sin(t*0.25)*0.5 + 0.6 + (m.active ? (m.y-0.5)*1.2 : 0);
  const ay = t*0.3 + (m.active ? (m.x-0.5)*2 : 0);
  const U = Math.max(18, Math.floor(34*density));
  const V = Math.max(10, Math.floor(18*density));
  const proj = (u,v) => {
    const cu=Math.cos(u), su=Math.sin(u), cv=Math.cos(v), sv=Math.sin(v);
    const x=(R+r*cv)*cu, y=(R+r*cv)*su, z=r*sv;
    const cx_=Math.cos(ax), sx_=Math.sin(ax);
    const y1=y*cx_-z*sx_, z1=y*sx_+z*cx_;
    const cy_=Math.cos(ay), sy_=Math.sin(ay);
    const x2=x*cy_+z1*sy_, z2=-x*sy_+z1*cy_;
    const persp = 1/(2.6 - z2*0.8);
    return {x:cx+x2*scale*persp, y:cy+y1*scale*persp, z:z2};
  };
  ctx.lineWidth = 0.8;
  for (let i=0;i<U;i++){
    const u=(i/U)*Math.PI*2;
    ctx.beginPath();
    for (let j=0;j<=V;j++){
      const v=(j/V)*Math.PI*2, p=proj(u,v);
      j===0?ctx.moveTo(p.x,p.y):ctx.lineTo(p.x,p.y);
    }
    ctx.strokeStyle=fg; ctx.globalAlpha=0.4; ctx.stroke();
  }
  [0.0, 0.5].forEach((offset,idx) => {
    ctx.strokeStyle=signal; ctx.lineWidth=idx===0?1.6:1.0; ctx.globalAlpha=idx===0?0.95:0.55;
    ctx.beginPath();
    const v = offset*Math.PI*2 + Math.sin(t*0.4)*0.3;
    for (let i=0;i<=U;i++){
      const u=(i/U)*Math.PI*2, p=proj(u,v);
      i===0?ctx.moveTo(p.x,p.y):ctx.lineTo(p.x,p.y);
    }
    ctx.stroke();
  });
  ctx.globalAlpha=1;
}

/* ---------- Lorenz attractor ----------
   dx/dt = σ(y−x), dy/dt = x(ρ−z)−y, dz/dt = xy−βz
*/
const lorenzState = { x: 0.1, y: 0, z: 0, trail: [] };
function drawLorenz(ctx, W, H, t, density, fg, signal, dim, _sd, m) {
  const sigma = 10, beta = 8/3;
  const rho = 28 + (m.active ? (m.x-0.5)*20 : 0);
  const dt = 0.006;
  const iters = Math.floor(8 * density);
  const trailMax = 1400;
  const s = lorenzState;
  for (let i=0;i<iters;i++){
    const dx = sigma*(s.y - s.x);
    const dy = s.x*(rho - s.z) - s.y;
    const dz = s.x*s.y - beta*s.z;
    s.x += dx*dt; s.y += dy*dt; s.z += dz*dt;
    s.trail.push([s.x, s.y, s.z]);
  }
  while (s.trail.length > trailMax) s.trail.shift();

  const scale = Math.min(W,H) / 60;
  const cx=W/2, cy=H/2 + H*0.08;
  const rotY = t*0.15 + (m.active ? (m.x-0.5)*1.2 : 0);
  const rotX = 0.2 + (m.active ? (m.y-0.5)*0.6 : 0);

  const project = (x,y,z) => {
    const cyr = Math.cos(rotY), syr = Math.sin(rotY);
    let x1 = x*cyr - y*syr, y1 = x*syr + y*cyr, z1 = z - 25;
    const cxr = Math.cos(rotX), sxr = Math.sin(rotX);
    const y2 = y1*cxr - z1*sxr, z2 = y1*sxr + z1*cxr;
    const persp = 1 / (1.8 - z2*0.02);
    return { x: cx + x1*scale*persp, y: cy - y2*scale*persp };
  };

  ctx.lineWidth = 0.9;
  ctx.beginPath();
  for (let i=1;i<s.trail.length;i++){
    const [x0,y0,z0] = s.trail[i-1];
    const [x1,y1,z1] = s.trail[i];
    const p0 = project(x0,y0,z0), p1 = project(x1,y1,z1);
    const a = i/s.trail.length;
    ctx.globalAlpha = a * 0.9;
    ctx.strokeStyle = i > s.trail.length - 120 ? signal : fg;
    ctx.beginPath(); ctx.moveTo(p0.x, p0.y); ctx.lineTo(p1.x, p1.y); ctx.stroke();
  }
  // head
  const last = s.trail[s.trail.length-1];
  if (last) {
    const p = project(last[0], last[1], last[2]);
    ctx.globalAlpha = 1; ctx.fillStyle = signal;
    ctx.beginPath(); ctx.arc(p.x, p.y, 3, 0, Math.PI*2); ctx.fill();
    ctx.strokeStyle = signal; ctx.globalAlpha = 0.3; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.arc(p.x, p.y, 10, 0, Math.PI*2); ctx.stroke();
  }
  ctx.globalAlpha = 1;
}

/* ---------- Rose / Maurer rose ----------
   r = cos(k θ), with Maurer connect-the-dots at step d
*/
function drawRose(ctx, W, H, t, density, fg, signal, dim, _sd, m) {
  const cx=W/2, cy=H/2;
  const R = Math.min(W,H)*0.4;
  const n = 6 + Math.floor((m.active ? m.x : 0.5)*6);
  const d = 29 + Math.floor(Math.sin(t*0.2)*20) + (m.active ? (m.y-0.5)*30|0 : 0);
  ctx.strokeStyle = dim; ctx.lineWidth = 1;
  // rose guide
  ctx.globalAlpha = 0.25; ctx.beginPath();
  for (let i=0;i<=360;i++){
    const th = (i*Math.PI)/180;
    const r = R*Math.cos(n*th);
    const x = cx + r*Math.cos(th), y = cy + r*Math.sin(th);
    i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);
  }
  ctx.stroke();
  // Maurer lines
  ctx.globalAlpha = 0.9; ctx.strokeStyle = signal; ctx.lineWidth = 0.8;
  const steps = Math.floor(361*density);
  ctx.beginPath();
  for (let i=0;i<=steps;i++){
    const k = (i*d*Math.PI)/180;
    const r = R*Math.cos(n*k);
    const x = cx + r*Math.cos(k), y = cy + r*Math.sin(k);
    i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);
  }
  ctx.stroke();
  ctx.globalAlpha = 1;
}

/* ---------- Fourier epicycles ----------
   Sum of rotating circles tracing a square-wave approximation.
*/
function drawFourier(ctx, W, H, t, density, fg, signal, dim, _sd, m) {
  const cx = W*0.32, cy = H/2;
  const R = Math.min(W,H)*0.22;
  const N = 3 + Math.floor((m.active ? m.y : 0.5)*6)*2 + 1; // odd harmonics
  const speedMul = 1 + (m.active ? (m.x-0.5)*1.5 : 0);

  let x=cx, y=cy;

  const pts = [];
  for (let k=1; k<=N; k+=2) {
    const r = (4*R/Math.PI)/k;
    const ang = k*t*0.8*speedMul;
    const nx = x + r*Math.cos(ang);
    const ny = y + r*Math.sin(ang);
    ctx.strokeStyle = fg; ctx.globalAlpha = 0.25; ctx.lineWidth = 0.8;
    ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2); ctx.stroke();
    ctx.globalAlpha = 0.6; ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(nx,ny); ctx.stroke();
    x=nx; y=ny;
  }
  // waveform trail
  if (!drawFourier.trail) drawFourier.trail = [];
  drawFourier.trail.unshift(y);
  if (drawFourier.trail.length > 400) drawFourier.trail.length = 400;
  ctx.strokeStyle = signal; ctx.lineWidth = 1.4; ctx.globalAlpha = 0.95;
  ctx.beginPath();
  drawFourier.trail.forEach((yv, i) => {
    const xv = W*0.55 + i*((W*0.42)/drawFourier.trail.length);
    i===0?ctx.moveTo(xv,yv):ctx.lineTo(xv,yv);
  });
  ctx.stroke();
  // connector from pen to waveform
  ctx.strokeStyle = signal; ctx.globalAlpha = 0.4; ctx.lineWidth = 0.8;
  ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(W*0.55, drawFourier.trail[0]); ctx.stroke();
  ctx.fillStyle = signal; ctx.globalAlpha = 1;
  ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI*2); ctx.fill();
}

/* ---------- De Jong attractor ----------
   x' = sin(a y) − cos(b x); y' = sin(c x) − cos(d y)
*/
const dejongState = { pts: null, params: [2.01, -2.53, 1.61, -0.33] };
function drawDeJong(ctx, W, H, t, density, fg, signal, dim, _sd, m) {
  const s = dejongState;
  const a = 1.4 + Math.sin(t*0.05)*0.6 + (m.active ? (m.x-0.5)*1.2 : 0);
  const b = -2.3 + Math.cos(t*0.04)*0.5;
  const c = 1.6 + (m.active ? (m.y-0.5)*0.8 : 0);
  const d = -0.3 + Math.sin(t*0.03)*0.6;
  const N = Math.floor(12000 * density);

  const cx=W/2, cy=H/2, scale = Math.min(W,H)*0.22;
  let x=0.1, y=0.1;
  // warm-up
  for (let i=0;i<50;i++){ const nx=Math.sin(a*y)-Math.cos(b*x); const ny=Math.sin(c*x)-Math.cos(d*y); x=nx; y=ny; }
  for (let i=0;i<N;i++){
    const nx=Math.sin(a*y)-Math.cos(b*x);
    const ny=Math.sin(c*x)-Math.cos(d*y);
    x=nx; y=ny;
    const px = cx + x*scale, py = cy + y*scale;
    const isSig = (i % 40) === 0;
    ctx.fillStyle = isSig ? signal : fg;
    ctx.globalAlpha = isSig ? 0.45 : 0.12;
    ctx.fillRect(px, py, 1, 1);
  }
  ctx.globalAlpha = 1;
}

/* ---------- Phyllotaxis (sunflower spiral) ----------
   r = c√n, θ = n · φ  (golden angle 137.5°)
*/
function drawPhyllotaxis(ctx, W, H, t, density, fg, signal, dim, _sd, m) {
  const cx=W/2, cy=H/2;
  const phi = 137.5 + (m.active ? (m.x-0.5)*8 : 0) + Math.sin(t*0.12)*0.6;
  const N = Math.floor(900*density);
  const c = Math.min(W,H) * 0.018;
  for (let n=1;n<=N;n++){
    const ang = n * phi * Math.PI/180 + t*0.05;
    const r = c * Math.sqrt(n);
    const x = cx + r*Math.cos(ang), y = cy + r*Math.sin(ang);
    const size = 1.5 + (n/N)*2;
    const isSig = (n % 21) === 0 || n > N-30;
    ctx.fillStyle = isSig ? signal : fg;
    ctx.globalAlpha = isSig ? 0.9 : 0.22 + (n/N)*0.3;
    ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI*2); ctx.fill();
  }
  ctx.globalAlpha = 1;
}

window.MotionCanvas = MotionCanvas;
