// background.jsx — scroll-driven realistic DNA animation
//
// Multi-stage:
//   0.00–0.12  vial visible, peptide molecules suspended (Brownian-ish bob)
//   0.12–0.35  vial fades, particles stream out the neck along a curved path
//   0.35–1.00  particles assemble into a 3D rotating DNA double-helix with
//              base-pair rungs and proper front/back occlusion.
//
// Sits z-index 0 behind content. Subtle alpha so it doesn't compete.

const { useEffect: bgUseEffect, useRef: bgUseRef } = React;

function BackgroundAnim({ accent = "#2d6a5f", dark = false }) {
  const canvasRef = bgUseRef(null);

  bgUseEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const DPR = Math.min(window.devicePixelRatio || 1, 2);

    // ─── Particles ──────────────────────────────────────────────────────────
    const PAIRS = 56;                  // base pairs along helix
    const N = PAIRS * 2;               // two strands × pairs

    // Deterministic pseudo-rand per particle
    const rng = (i, k) => {
      const x = Math.sin(i * 12.9898 + k * 78.233) * 43758.5453;
      return x - Math.floor(x);
    };

    // Per-particle properties: stagger, bob params, slight radial offset
    const P = Array.from({ length: N }, (_, i) => ({
      i,
      strand: i % 2,                   // 0 or 1
      pair: Math.floor(i / 2),
      delay: rng(i, 1) * 0.22,          // 0–0.22 stagger fraction
      bobAmp: 1.5 + rng(i, 2) * 2.5,
      bobFreq: 0.00045 + rng(i, 3) * 0.0006,
      bobPhase: rng(i, 4) * Math.PI * 2,
      vialRx: (rng(i, 5) - 0.5),       // -.5..+.5
      vialRy: rng(i, 6),
      streamCurve: 0.4 + rng(i, 7) * 0.4,
    }));

    // ─── Geometry helpers (computed each frame from viewport) ───────────────
    let W = 0, H = 0;

    function getVial() {
      const cx = W < 900 ? W * 0.5 : W * 0.78;
      const top = H * 0.10;
      const bodyW = Math.min(W * 0.18, 200);
      const capH = 12;
      const neckH = 32;
      const bodyTop = top + capH + neckH;
      const bodyH = Math.min(H * 0.48, 380);
      const neckW = bodyW * 0.40;
      return { cx, top, bodyW, bodyH, neckW, capH, neckH, bodyTop };
    }

    function getHelix() {
      const cx = W * 0.5;
      const yStart = H * -0.30;
      const yEnd = H * 1.35;
      const radius = Math.min(W * 0.16, 200);
      const turns = 4.5;
      return { cx, yStart, yEnd, radius, turns };
    }

    // ─── Position phases ────────────────────────────────────────────────────
    function vialPos(p, time) {
      const v = getVial();
      const cx = v.cx;
      // distribute inside inner-body (not edges) with slight clustering
      const cluster = Math.sqrt(Math.abs(p.vialRx) * 2) * Math.sign(p.vialRx);
      const x = cx + cluster * (v.bodyW * 0.35);
      const y = v.bodyTop + p.vialRy * v.bodyH * 0.88 + v.bodyH * 0.06;
      // gentle brownian bob
      const bob = Math.sin(time * p.bobFreq + p.bobPhase) * p.bobAmp;
      const wob = Math.cos(time * p.bobFreq * 0.7 + p.bobPhase * 1.3) * (p.bobAmp * 0.5);
      return { x: x + wob, y: y + bob, z: 0, depth: 1 };
    }

    function helixPos(p, time, scrollP) {
      const h = getHelix();
      const u = p.pair / (PAIRS - 1);                       // 0..1 along helix length
      const y = h.yStart + u * (h.yEnd - h.yStart);
      // continuous slow spin + scroll-driven rotation push
      const spin = time * 0.00035 + scrollP * Math.PI * 1.2;
      const angle = u * h.turns * Math.PI * 2 + p.strand * Math.PI + spin;
      const wx = Math.cos(angle) * h.radius;
      const wz = Math.sin(angle) * h.radius;
      // light perspective: front (z>0) gets slight scale up
      const persp = 1 + wz * 0.0008;
      const x = h.cx + wx * persp;
      // size/alpha factor — front = bright big, back = dim small
      const depth = 0.45 + (wz / h.radius * 0.5 + 0.5) * 0.55;  // 0.45..1
      return { x, y, z: wz, depth, angle };
    }

    // Streaming path: cubic Bezier from vial-top to helix entry point
    function streamPos(p, time, scrollP) {
      const v = getVial();
      const h = getHelix();
      const vp = vialPos(p, time);
      const hp = helixPos(p, time, scrollP);
      // start = top of vial cap above mouth
      const exitX = v.cx + (rng(p.i, 8) - 0.5) * v.neckW * 0.6;
      const exitY = v.top - 6;
      // mid control points for nice curl
      const midX1 = v.cx + (exitX - v.cx) * 1.6 + (rng(p.i, 9) - 0.5) * 60;
      const midY1 = exitY - 60 - rng(p.i, 10) * 40;
      const midX2 = (h.cx + exitX) * 0.5 + (rng(p.i, 11) - 0.5) * 80;
      const midY2 = (exitY + hp.y) * 0.5;
      return { vp, hp, exitX, exitY, midX1, midY1, midX2, midY2 };
    }

    // Cubic Bezier interpolation
    function bez(t, a, b, c, d) {
      const it = 1 - t;
      return it * it * it * a + 3 * it * it * t * b + 3 * it * t * t * c + t * t * t * d;
    }

    // Smoothstep
    function smooth01(x) {
      x = Math.max(0, Math.min(1, x));
      return x * x * (3 - 2 * x);
    }

    // ─── Stages from scroll progress ────────────────────────────────────────
    function stages(p) {
      // p 0..1 — global scroll
      const vial   = 1 - smooth01((p - 0.06) / 0.18);                 // 1 → 0 around 0.06-0.24
      const exit   = smooth01((p - 0.10) / 0.20);                    // 0 → 1 around 0.10-0.30
      const arrive = smooth01((p - 0.30) / 0.18);                    // 0 → 1 around 0.30-0.48
      const helix  = smooth01((p - 0.35) / 0.25);                    // 0 → 1 around 0.35-0.60
      return { vial, exit, arrive, helix };
    }

    // ─── Resize ─────────────────────────────────────────────────────────────
    function resize() {
      W = window.innerWidth;
      H = window.innerHeight;
      canvas.width = W * DPR;
      canvas.height = H * DPR;
      canvas.style.width = W + "px";
      canvas.style.height = H + "px";
      ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
    }
    resize();
    let resizeRaf;
    function onResize() { cancelAnimationFrame(resizeRaf); resizeRaf = requestAnimationFrame(resize); }
    window.addEventListener("resize", onResize);

    // ─── Scroll ─────────────────────────────────────────────────────────────
    let target = 0, sm = 0;
    function onScroll() {
      const max = document.documentElement.scrollHeight - window.innerHeight;
      target = max > 0 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0;
    }
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });

    // ─── Color helpers ──────────────────────────────────────────────────────
    function rgba(hex, a) {
      const h = hex.replace("#", "");
      const r = parseInt(h.substring(0, 2), 16);
      const g = parseInt(h.substring(2, 4), 16);
      const b = parseInt(h.substring(4, 6), 16);
      return `rgba(${r},${g},${b},${a})`;
    }
    const fgHex = dark ? "#f4f3ef" : "#161616";
    const ALPHA_MAX = dark ? 1.1 : 0.85;   // global cap multiplier

    // ─── Vial drawing (geometric, layered for glass feel) ───────────────────
    function drawVial(alpha, time) {
      if (alpha <= 0.005) return;
      const v = getVial();
      const { cx, top, bodyW, bodyH, neckW, capH, neckH, bodyTop } = v;
      const left = cx - bodyW / 2;
      const right = cx + bodyW / 2;
      const bottom = bodyTop + bodyH;
      const r = 22;

      // Cap (filled)
      ctx.fillStyle = rgba(fgHex, alpha * 0.6);
      ctx.fillRect(cx - neckW * 0.65, top, neckW * 1.3, capH);

      // Neck (outline)
      ctx.strokeStyle = rgba(fgHex, alpha * 0.55);
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(cx - neckW / 2, top + capH);
      ctx.lineTo(cx - neckW / 2, bodyTop);
      ctx.moveTo(cx + neckW / 2, top + capH);
      ctx.lineTo(cx + neckW / 2, bodyTop);
      ctx.stroke();

      // Body outline (with rounded bottom & shoulder)
      ctx.beginPath();
      ctx.moveTo(cx - neckW / 2, bodyTop);
      ctx.lineTo(left, bodyTop + 22);
      ctx.lineTo(left, bottom - r);
      ctx.quadraticCurveTo(left, bottom, left + r, bottom);
      ctx.lineTo(right - r, bottom);
      ctx.quadraticCurveTo(right, bottom, right, bottom - r);
      ctx.lineTo(right, bodyTop + 22);
      ctx.lineTo(cx + neckW / 2, bodyTop);
      ctx.strokeStyle = rgba(fgHex, alpha * 0.55);
      ctx.lineWidth = 1.1;
      ctx.stroke();

      // Liquid fill (wave on top)
      const liqTop = bodyTop + bodyH * 0.32;
      const waveAmp = 3;
      const waveFreq = 0.020;
      const wavePhase = time * 0.0009;
      ctx.save();
      ctx.beginPath();
      ctx.moveTo(left, liqTop);
      const steps = 22;
      for (let s = 0; s <= steps; s++) {
        const x = left + (right - left) * (s / steps);
        const y = liqTop + Math.sin(x * waveFreq + wavePhase) * waveAmp;
        ctx.lineTo(x, y);
      }
      ctx.lineTo(right, bottom - r);
      ctx.quadraticCurveTo(right, bottom, right - r, bottom);
      ctx.lineTo(left + r, bottom);
      ctx.quadraticCurveTo(left, bottom, left, bottom - r);
      ctx.closePath();
      ctx.fillStyle = rgba(accent, alpha * (dark ? 0.18 : 0.10));
      ctx.fill();
      ctx.restore();

      // Wave top stroke
      ctx.beginPath();
      for (let s = 0; s <= steps; s++) {
        const x = left + (right - left) * (s / steps);
        const y = liqTop + Math.sin(x * waveFreq + wavePhase) * waveAmp;
        if (s === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
      }
      ctx.strokeStyle = rgba(accent, alpha * (dark ? 0.55 : 0.40));
      ctx.lineWidth = 1;
      ctx.stroke();

      // Inner glass highlight (left side)
      ctx.beginPath();
      ctx.moveTo(left + 8, bodyTop + 28);
      ctx.lineTo(left + 8, bottom - r - 4);
      ctx.strokeStyle = rgba(fgHex, alpha * (dark ? 0.18 : 0.10));
      ctx.lineWidth = 1;
      ctx.stroke();

      // Label band (thin, decorative)
      const labelTop = bodyTop + bodyH * 0.55;
      ctx.strokeStyle = rgba(fgHex, alpha * 0.35);
      ctx.beginPath();
      ctx.moveTo(left, labelTop);
      ctx.lineTo(right, labelTop);
      ctx.moveTo(left, labelTop + 26);
      ctx.lineTo(right, labelTop + 26);
      ctx.stroke();

      // Mono label text (very small)
      ctx.fillStyle = rgba(fgHex, alpha * 0.7);
      ctx.font = "9px 'Geist Mono', ui-monospace, monospace";
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillText("AXN · BPC-157", cx, labelTop + 8);
      ctx.fillStyle = rgba(fgHex, alpha * 0.45);
      ctx.fillText("5 mg · LYO", cx, labelTop + 19);
    }

    // ─── Frame loop ─────────────────────────────────────────────────────────
    let raf;
    let lastT = performance.now();
    let time = 0;

    function frame(now) {
      const dt = Math.min(48, now - lastT);
      lastT = now;
      time += dt;

      sm += (target - sm) * 0.075;
      const st = stages(sm);

      ctx.clearRect(0, 0, W, H);

      // 1) Vial (with subtle continuous wave)
      drawVial(st.vial * (dark ? 0.95 : 0.85), time);

      // 2) Compute particle positions
      const pos = new Array(N);
      for (let k = 0; k < N; k++) {
        const p = P[k];
        // each particle has its own effective progress (stagger)
        const ep = Math.max(0, Math.min(1, (sm - p.delay) / (1 - 0.22)));
        const stEff = stages(ep);

        const vp = vialPos(p, time);
        const hp = helixPos(p, time, ep);

        let x, y, z, depth, alpha;
        if (stEff.helix > 0.985) {
          // fully on helix
          x = hp.x; y = hp.y; z = hp.z; depth = hp.depth;
          alpha = 1;
        } else if (stEff.arrive < 0.02) {
          // still in vial
          x = vp.x; y = vp.y; z = 0; depth = 1;
          alpha = 1;
        } else {
          // streaming via cubic Bezier
          const s = streamPos(p, time, ep);
          // t goes from 0 at start of exit to 1 when arrived
          const t = Math.max(0, Math.min(1, (ep - p.delay - 0.10) / 0.30));
          // We want a hand-off:
          //   t=0 → vial pos
          //   t=0.2 → exit point (just above vial)
          //   t=1 → helix pos
          // Blend in two segments
          let xx, yy;
          if (t < 0.3) {
            const tt = t / 0.3;
            // move from vp to exit
            xx = vp.x + (s.exitX - vp.x) * smooth01(tt);
            yy = vp.y + (s.exitY - vp.y) * smooth01(tt);
          } else {
            const tt = (t - 0.3) / 0.7;
            // cubic Bezier exit → mid1 → mid2 → helix
            xx = bez(tt, s.exitX, s.midX1, s.midX2, hp.x);
            yy = bez(tt, s.exitY, s.midY1, s.midY2, hp.y);
          }
          x = xx; y = yy;
          // 3D depth ramps in late in transit
          const dRamp = smooth01(Math.max(0, (t - 0.5) / 0.5));
          z = hp.z * dRamp;
          depth = 1 - (1 - hp.depth) * dRamp;
          alpha = 1;
        }

        pos[k] = { x, y, z, depth, alpha, strand: p.strand, pair: p.pair, helixT: stEff.helix };
      }

      // 3) Helix structures (only after stuff has started arriving)
      if (st.arrive > 0.05) {
        drawHelixStructure(pos, st);
      }

      // 4) Particles
      drawParticles(pos, st);

      raf = requestAnimationFrame(frame);
    }

    function drawHelixStructure(pos, st) {
      // Build pair-indexed arrays for each strand
      const strandA = [];   // strand 0
      const strandB = [];   // strand 1
      for (let k = 0; k < N; k++) {
        if (pos[k].strand === 0) strandA[pos[k].pair] = pos[k];
        else strandB[pos[k].pair] = pos[k];
      }

      // Helper to draw a smooth strand split by front/back depth.
      function drawStrand(arr) {
        // We'll walk pair-by-pair, sample whether front (z >= 0) or back (z < 0),
        // accumulate segments, draw back ones first then front.
        const segs = [];
        let cur = null;
        for (let i = 0; i < arr.length; i++) {
          const p = arr[i];
          if (!p) continue;
          const front = p.z >= 0;
          if (!cur || cur.front !== front) {
            if (cur && cur.points.length) segs.push(cur);
            cur = { front, points: [] };
            // include previous point (if any) to avoid gaps
            if (i > 0 && arr[i - 1]) cur.points.push(arr[i - 1]);
          }
          cur.points.push(p);
        }
        if (cur && cur.points.length) segs.push(cur);

        // back first
        segs.sort((a, b) => (a.front === b.front ? 0 : a.front ? 1 : -1));
        for (const seg of segs) {
          if (seg.points.length < 2) continue;
          ctx.beginPath();
          // Catmull-Rom-ish via quadratics through midpoints
          for (let i = 0; i < seg.points.length - 1; i++) {
            const a = seg.points[i];
            const b = seg.points[i + 1];
            const mx = (a.x + b.x) / 2;
            const my = (a.y + b.y) / 2;
            if (i === 0) ctx.moveTo(a.x, a.y);
            ctx.quadraticCurveTo(a.x, a.y, mx, my);
            if (i === seg.points.length - 2) ctx.lineTo(b.x, b.y);
          }
          const avgT = seg.points.reduce((s, p) => s + p.helixT, 0) / seg.points.length;
          const baseA = seg.front
            ? (dark ? 0.42 : 0.28)
            : (dark ? 0.16 : 0.10);
          ctx.strokeStyle = rgba(accent, baseA * avgT * ALPHA_MAX);
          ctx.lineWidth = seg.front ? 1.4 : 1;
          ctx.stroke();
        }
      }

      drawStrand(strandA);
      drawStrand(strandB);

      // Base pairs (rungs) every pair where both sides exist & helix > 0.4
      for (let i = 0; i < PAIRS; i++) {
        const a = strandA[i], b = strandB[i];
        if (!a || !b) continue;
        const helixOK = (a.helixT + b.helixT) / 2;
        if (helixOK < 0.4) continue;
        if (a.y < -30 && b.y < -30) continue;
        if (a.y > H + 30 && b.y > H + 30) continue;
        const avgZ = (a.z + b.z) / 2;
        const front = avgZ >= 0;
        // every other pair colored slightly different (mimics A-T vs G-C)
        const isPurine = i % 2 === 0;
        const col = isPurine ? accent : fgHex;
        const baseA = (front ? (dark ? 0.24 : 0.16) : (dark ? 0.10 : 0.06)) * helixOK;
        ctx.beginPath();
        // shorten rung slightly so it doesn't overlap circles
        const dx = b.x - a.x, dy = b.y - a.y;
        const len = Math.hypot(dx, dy) || 1;
        const off = 4;
        const ax = a.x + (dx / len) * off;
        const ay = a.y + (dy / len) * off;
        const bx = b.x - (dx / len) * off;
        const by = b.y - (dy / len) * off;
        ctx.moveTo(ax, ay);
        ctx.lineTo(bx, by);
        ctx.strokeStyle = rgba(col, baseA * ALPHA_MAX);
        ctx.lineWidth = front ? 1.2 : 0.8;
        ctx.stroke();
      }
    }

    function drawParticles(pos, st) {
      // Two-pass: back particles first (smaller, dimmer), front on top
      const back = [], front = [];
      for (const p of pos) {
        if (p.y < -30 || p.y > H + 30) continue;
        (p.z < 0 ? back : front).push(p);
      }
      function drawOne(p) {
        const r = (p.strand === 0 ? 2.4 : 1.7) * p.depth;
        const tintAlpha = (p.strand === 0
          ? (dark ? 0.78 : 0.55)
          : (dark ? 0.48 : 0.32)) * p.depth * p.alpha * ALPHA_MAX;
        // soft glow when on helix
        if (p.helixT > 0.6 && p.z >= 0) {
          const g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 4);
          g.addColorStop(0, rgba(accent, tintAlpha * 0.45));
          g.addColorStop(1, rgba(accent, 0));
          ctx.fillStyle = g;
          ctx.beginPath();
          ctx.arc(p.x, p.y, r * 4, 0, Math.PI * 2);
          ctx.fill();
        }
        ctx.fillStyle = p.strand === 0 ? rgba(accent, tintAlpha) : rgba(fgHex, tintAlpha * 0.75);
        ctx.beginPath();
        ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
        ctx.fill();
      }
      back.forEach(drawOne);
      front.forEach(drawOne);
    }

    raf = requestAnimationFrame(frame);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onResize);
    };
  }, [accent, dark]);

  return (
    <canvas
      ref={canvasRef}
      aria-hidden="true"
      style={{
        position: "fixed",
        inset: 0,
        width: "100vw",
        height: "100vh",
        zIndex: 0,
        pointerEvents: "none",
      }}
    />
  );
}

window.BackgroundAnim = BackgroundAnim;
