// Heartfield visualization for the Heart page.
//
// Two parts:
//   1. A pulsing heart with concentric ripples spreading outward — the
//      electromagnetic field the page describes.
//   2. A live HRV (heart-rate variability) canvas scrolling underneath:
//      • Coherent mode: smooth sine wave (~0.1 Hz, the resonant frequency
//        where heart, breath, and brainwaves synchronize).
//      • Chaotic mode:  jagged irregular line (stress / non-coherent state).
//
// The toggle is the page's argument made visible.

(function () {
  const root = document.getElementById("heartfield");
  if (!root) return;

  const canvas = root.querySelector(".hf-hrv");
  const ctx = canvas ? canvas.getContext("2d") : null;
  const buttons = root.querySelectorAll(".hf-toggle[data-state]");
  const stage = root.querySelector(".hf-stage");

  // Use the device pixel ratio for crisp lines on retina.
  function sizeCanvas() {
    if (!canvas) return;
    const dpr = window.devicePixelRatio || 1;
    const rect = canvas.getBoundingClientRect();
    canvas.width = Math.round(rect.width * dpr);
    canvas.height = Math.round(rect.height * dpr);
    if (ctx) ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }

  let state = loadState();
  let rafId = null;
  let lastTs = null;
  let x = 0;  // scroll offset (in seconds since start)
  let history = []; // [{x, y}] line points

  function loadState() {
    try {
      const v = localStorage.getItem("wuys-heart-state");
      if (v === "coherent" || v === "chaotic") return v;
    } catch (_) { /* ignore */ }
    return "coherent";
  }
  function saveState(v) {
    try { localStorage.setItem("wuys-heart-state", v); } catch (_) { /* ignore */ }
  }

  function applyStateClass() {
    stage.classList.toggle("is-coherent", state === "coherent");
    stage.classList.toggle("is-chaotic",  state === "chaotic");
    buttons.forEach((b) => b.classList.toggle("active", b.dataset.state === state));
  }

  // ----- HRV line generation ---------------------------------------------
  // Returns the y position [-1..1] at time t (seconds) for the current state.
  function hrvY(t) {
    if (state === "coherent") {
      // ~0.1 Hz sine — the resonant frequency. Steady, beautiful, slow.
      return Math.sin(2 * Math.PI * 0.1 * t) * 0.85;
    }
    // Chaotic: layered higher-frequency noise + small drift.
    const a = Math.sin(2 * Math.PI * 0.6 * t + 1.3) * 0.35;
    const b = Math.sin(2 * Math.PI * 1.9 * t + 2.7) * 0.30;
    const c = Math.sin(2 * Math.PI * 3.4 * t + 0.4) * 0.20;
    const d = (Math.sin(t * 17.13) + Math.sin(t * 11.7) + Math.sin(t * 29.3)) * 0.08;
    return Math.max(-0.95, Math.min(0.95, a + b + c + d));
  }

  // ----- Draw loop -------------------------------------------------------
  const SECONDS_VISIBLE = 30; // total width of the canvas window in "seconds"
  const SAMPLE_HZ = 30;       // samples per virtual second
  const SAMPLE_INTERVAL = 1 / SAMPLE_HZ;
  let nextSampleAt = 0;

  function loop(ts) {
    if (!ctx) return;
    if (lastTs == null) lastTs = ts;
    const dt = (ts - lastTs) / 1000;
    lastTs = ts;
    x += dt;

    while (x >= nextSampleAt) {
      history.push({ x: nextSampleAt, y: hrvY(nextSampleAt) });
      nextSampleAt += SAMPLE_INTERVAL;
    }
    // Trim history to what we need (a little extra for safety).
    const cutoff = x - SECONDS_VISIBLE - 1;
    while (history.length && history[0].x < cutoff) history.shift();

    render();
    rafId = requestAnimationFrame(loop);
  }

  function render() {
    const w = canvas.width  / (window.devicePixelRatio || 1);
    const h = canvas.height / (window.devicePixelRatio || 1);
    ctx.clearRect(0, 0, w, h);

    if (!history.length) return;

    // Translate so the right edge of the canvas == "now" (x).
    const startX = x - SECONDS_VISIBLE;
    const toCanvasX = (t) => ((t - startX) / SECONDS_VISIBLE) * w;
    const toCanvasY = (y) => h / 2 - y * (h / 2 - 6);

    // Subtle baseline.
    ctx.strokeStyle = "rgba(183, 104, 255, 0.10)";
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(0, h / 2);
    ctx.lineTo(w, h / 2);
    ctx.stroke();

    // HRV line.
    const grad = ctx.createLinearGradient(0, 0, w, 0);
    if (state === "coherent") {
      grad.addColorStop(0, "rgba(183, 104, 255, 0.0)");
      grad.addColorStop(0.15, "rgba(183, 104, 255, 0.85)");
      grad.addColorStop(1, "rgba(255, 158, 199, 1)");
    } else {
      grad.addColorStop(0, "rgba(220, 90, 90, 0.0)");
      grad.addColorStop(0.15, "rgba(220, 90, 90, 0.8)");
      grad.addColorStop(1, "rgba(255, 130, 110, 1)");
    }
    ctx.strokeStyle = grad;
    ctx.lineWidth = 2;
    ctx.lineJoin = "round";
    ctx.lineCap = "round";
    ctx.beginPath();
    let started = false;
    for (const p of history) {
      const cx = toCanvasX(p.x);
      const cy = toCanvasY(p.y);
      if (!started) { ctx.moveTo(cx, cy); started = true; }
      else ctx.lineTo(cx, cy);
    }
    ctx.stroke();
  }

  // ----- Wiring -----------------------------------------------------------
  buttons.forEach((b) => b.addEventListener("click", () => {
    if (b.dataset.state === state) return;
    state = b.dataset.state;
    saveState(state);
    applyStateClass();
  }));

  function start() {
    if (rafId) return;
    sizeCanvas();
    lastTs = null;
    rafId = requestAnimationFrame(loop);
  }
  function stop() {
    if (rafId) cancelAnimationFrame(rafId);
    rafId = null;
  }

  // Pause when off-screen or hidden.
  const heartView = document.querySelector('.view[data-view="heart"]');
  if (heartView && "MutationObserver" in window) {
    new MutationObserver(() => {
      if (heartView.classList.contains("active")) start(); else stop();
    }).observe(heartView, { attributes: true, attributeFilter: ["class"] });
    // If already active on first load, start now.
    if (heartView.classList.contains("active")) start();
  } else {
    start();
  }
  document.addEventListener("visibilitychange", () => {
    if (document.hidden) stop();
    else if (heartView && heartView.classList.contains("active")) start();
  });
  window.addEventListener("resize", sizeCanvas);

  applyStateClass();
  // Pre-populate ~one window so the line is already visible on first paint.
  for (let t = 0; t < SECONDS_VISIBLE; t += SAMPLE_INTERVAL) {
    history.push({ x: t, y: hrvY(t) });
  }
  x = SECONDS_VISIBLE;
  nextSampleAt = SECONDS_VISIBLE;
})();
