// Ocean — canvas-based wave system with tide loop, wash events, cursor ripples.
// Exposes window.__tides with live state so other components can read the water surface.

const Ocean = ({ tweaks }) => {
  const canvasRef = React.useRef(null);
  const stateRef = React.useRef({
    w: 0, h: 0, dpr: 1,
    t: 0,
    ripples: [],         // {x, y, t0, strength}
    washes: [],          // {t0, strength}
    lastWashAt: -99,
    mouse: { x: -999, y: -999, down: false },
  });

  // expose a sampler so wordmark letters can ask "how high is the water at x?"
  React.useEffect(() => {
    window.__tides = window.__tides || {};
    window.__tides.surfaceY = (x) => sampleSurface(stateRef.current, x, tweaksRef.current);
    window.__tides.state = stateRef.current;
  });

  const tweaksRef = React.useRef(tweaks);
  tweaksRef.current = tweaks;

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const st = stateRef.current;

    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      st.dpr = dpr;
      st.w = window.innerWidth;
      st.h = window.innerHeight;
      canvas.width = st.w * dpr;
      canvas.height = st.h * dpr;
      canvas.style.width = st.w + 'px';
      canvas.style.height = st.h + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    window.addEventListener('resize', resize);

    const onMove = (e) => {
      const prev = { x: st.mouse.x, y: st.mouse.y };
      st.mouse.x = e.clientX;
      st.mouse.y = e.clientY;
      // only splash if near/under water surface
      const surface = sampleSurface(st, e.clientX, tweaksRef.current);
      if (e.clientY > surface - 40) {
        // throttle ripple creation by distance
        const dx = e.clientX - prev.x, dy = e.clientY - prev.y;
        const speed = Math.hypot(dx, dy);
        if (speed > 6 && st.ripples.length < 60) {
          st.ripples.push({
            x: e.clientX,
            y: Math.max(e.clientY, surface),
            t0: st.t,
            strength: Math.min(1, speed / 40),
          });
        }
      }
    };
    const onDown = (e) => {
      st.mouse.down = true;
      const surface = sampleSurface(st, e.clientX, tweaksRef.current);
      st.ripples.push({
        x: e.clientX, y: Math.max(e.clientY, surface),
        t0: st.t, strength: 1.4,
      });
    };
    const onUp = () => { st.mouse.down = false; };
    const onLeave = () => { st.mouse.x = -999; st.mouse.y = -999; };

    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerdown', onDown);
    window.addEventListener('pointerup', onUp);
    window.addEventListener('pointerleave', onLeave);

    let raf;
    let last = performance.now();
    const loop = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000);
      last = now;
      st.t += dt * (tweaksRef.current.timeScale || 1);

      // spawn periodic wash events
      const tw = tweaksRef.current;
      const washInterval = tw.washInterval || 8;
      if (st.t - st.lastWashAt > washInterval) {
        st.lastWashAt = st.t;
        st.washes.push({ t0: st.t, strength: 0.9 + Math.random() * 0.4 });
      }
      // cull old washes (life extended to match the slower envelope)
      st.washes = st.washes.filter(w => st.t - w.t0 < 7.2);
      // cull old ripples
      st.ripples = st.ripples.filter(r => st.t - r.t0 < 2.2);

      draw(ctx, st, tw);
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', resize);
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerdown', onDown);
      window.removeEventListener('pointerup', onUp);
      window.removeEventListener('pointerleave', onLeave);
    };
  }, []);

  return (
    <canvas
      ref={canvasRef}
      style={{
        position: 'fixed', inset: 0, width: '100%', height: '100%',
        pointerEvents: 'none', zIndex: 1,
      }}
    />
  );
};

// --- pure math ---

// Returns y (in screen coords) of the water surface at screen-x `x`.
// Lower y = higher water.
function sampleSurface(st, x, tw) {
  const h = st.h || window.innerHeight;
  const t = st.t;
  // base tide level: slow sine, mapped to fraction of viewport from bottom
  const tideLoop = tw.tideLoop || 25;
  const tideAmp = tw.tideAmp ?? 0.08;      // fraction of viewport
  const baseFrac = (tw.baseLevel ?? 0.45); // 0.45 = 45% up from bottom = middle-ish
  const tide = baseFrac + Math.sin((t / tideLoop) * Math.PI * 2) * tideAmp;
  let baseY = h - tide * h;

  // layered surface waves
  const amp1 = tw.waveAmp ?? 14;
  const amp2 = (tw.waveAmp ?? 14) * 0.55;
  const k1 = 0.006, k2 = 0.013;
  const s1 = Math.sin(x * k1 + t * 0.9) * amp1;
  const s2 = Math.sin(x * k2 - t * 1.4 + 1.1) * amp2;

  // wash events — a crest that swells up slowly then recedes slowly.
  // Slower, more powerful feel: long sinusoidal ramp-up, broad peak, gentle recede.
  let washOffset = 0;
  for (const w of st.washes) {
    const age = t - w.t0;
    // phases: rise 0..2.4s, hold 2.4..3.6s, recede 3.6..7s
    let env;
    if (age < 2.4) {
      // smoothstep-ish — gentle at both ends, powerful in the middle
      env = easeInOutSine(age / 2.4);
    } else if (age < 3.6) {
      env = 1;
    } else if (age < 7.0) {
      env = 1 - easeInOutSine((age - 3.6) / 3.4);
    } else {
      env = 0;
    }
    // slight horizontal variation so the wash isn't a flat bar
    const horiz = 1 + 0.1 * Math.sin(x * 0.004 + w.t0);
    washOffset -= env * (tw.washHeight ?? 110) * w.strength * horiz;
  }

  return baseY + s1 + s2 + washOffset;
}

function easeOutCubic(x) { return 1 - Math.pow(1 - x, 3); }
function easeInCubic(x) { return x * x * x; }
function easeInOutSine(x) { return -(Math.cos(Math.PI * x) - 1) / 2; }

// --- draw ---

function draw(ctx, st, tw) {
  const { w, h, t } = st;
  ctx.clearRect(0, 0, w, h);

  // Build surface polyline
  const step = 6;
  const pts = [];
  for (let x = -step; x <= w + step; x += step) {
    pts.push([x, sampleSurface(st, x, tw)]);
  }

  // Back layer (deeper, further)
  drawWaterLayer(ctx, st, tw, pts, {
    offsetY: 18,
    color: hexToRgba(tw.shallowColor || '#9FC6D6', 0.55),
    foam: false,
  });

  // Front layer (main body)
  drawWaterLayer(ctx, st, tw, pts, {
    offsetY: 0,
    color: tw.deepColor || '#7FB3C9',
    foam: true,
  });

  // Ripples (drawn on top of water as light rings)
  for (const r of st.ripples) {
    const age = t - r.t0;
    if (age < 0) continue;
    const life = 2.2;
    const p = age / life;
    if (p >= 1) continue;
    const radius = Math.max(0.1, 6 + p * 110 * r.strength);
    const alpha = (1 - p) * 0.6 * r.strength;
    ctx.beginPath();
    ctx.ellipse(r.x, r.y, radius, radius * 0.35, 0, 0, Math.PI * 2);
    ctx.strokeStyle = `rgba(255,255,255,${alpha})`;
    ctx.lineWidth = 1.4;
    ctx.stroke();

    // inner
    ctx.beginPath();
    ctx.ellipse(r.x, r.y, radius * 0.55, radius * 0.2, 0, 0, Math.PI * 2);
    ctx.strokeStyle = `rgba(255,255,255,${alpha * 0.7})`;
    ctx.stroke();
  }

  // subtle horizon haze where water meets sky
  const surfaceAvg = pts.reduce((a, p) => a + p[1], 0) / pts.length;
  const grad = ctx.createLinearGradient(0, surfaceAvg - 60, 0, surfaceAvg + 2);
  grad.addColorStop(0, 'rgba(220,232,238,0)');
  grad.addColorStop(1, 'rgba(220,232,238,0.5)');
  ctx.fillStyle = grad;
  ctx.fillRect(0, surfaceAvg - 60, w, 62);
}

function drawWaterLayer(ctx, st, tw, pts, { offsetY, color, foam }) {
  const { w, h } = st;
  ctx.beginPath();
  ctx.moveTo(pts[0][0], pts[0][1] + offsetY);
  for (let i = 1; i < pts.length; i++) {
    ctx.lineTo(pts[i][0], pts[i][1] + offsetY);
  }
  ctx.lineTo(w + 10, h + 10);
  ctx.lineTo(-10, h + 10);
  ctx.closePath();
  ctx.fillStyle = color;
  ctx.fill();

  // highlight line along surface
  ctx.beginPath();
  ctx.moveTo(pts[0][0], pts[0][1] + offsetY);
  for (let i = 1; i < pts.length; i++) {
    ctx.lineTo(pts[i][0], pts[i][1] + offsetY);
  }
  ctx.strokeStyle = foam ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.3)';
  ctx.lineWidth = foam ? 1.5 : 1;
  ctx.stroke();

  if (foam) {
    // foam speckles near crests
    for (let i = 2; i < pts.length - 2; i += 2) {
      const [x, y] = pts[i];
      const slope = pts[i + 1][1] - pts[i - 1][1];
      // show foam where the wave is cresting (going from up to down-ish)
      if (Math.abs(slope) < 2 && Math.random() < 0.12) {
        ctx.beginPath();
        ctx.arc(x + (Math.random() - 0.5) * 8, y + offsetY + Math.random() * 2, Math.random() * 1.8 + 0.3, 0, Math.PI * 2);
        ctx.fillStyle = 'rgba(255,255,255,0.7)';
        ctx.fill();
      }
    }
  }
}

function hexToRgba(hex, a) {
  const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  if (!m) return hex;
  return `rgba(${parseInt(m[1],16)},${parseInt(m[2],16)},${parseInt(m[3],16)},${a})`;
}

window.Ocean = Ocean;
