/* ============================================================
   helpers.jsx — spring physics + easing (Framer-Motion-equivalent)
   Exposed on window for the babel scripts that follow.
   ============================================================ */
const { useState, useRef, useEffect, useCallback } = React;

/* ---- easing ---- */
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
const lerp  = (a, b, t) => a + (b - a) * t;

const Ease = {
  inOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
  outCubic:  (t) => 1 - Math.pow(1 - t, 3),
  inOutCubic:(t) => (t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2,3)/2),
  outQuart:  (t) => 1 - Math.pow(1 - t, 4),
  // spring-like overshoot for the drip landing
  outBack:   (t, s = 1.70158) => {
    const c3 = s + 1;
    return 1 + c3 * Math.pow(t - 1, 3) + s * Math.pow(t - 1, 2);
  },
};

/* ---- imperative spring integrator (semi-implicit Euler) ----
   Matches Framer Motion's stiffness/damping/mass model.
   Mutates the passed state {value, velocity} toward target.       */
function stepSpring(state, target, cfg, dt) {
  const stiffness = cfg.stiffness ?? 260;
  const damping   = cfg.damping   ?? 18;
  const mass      = cfg.mass      ?? 0.9;
  // substep for stability at low frame rates
  const steps = Math.max(1, Math.ceil(dt / (1 / 120)));
  const h = dt / steps;
  for (let i = 0; i < steps; i++) {
    const force = -stiffness * (state.value - target);
    const drag  = -damping * state.velocity;
    const accel = (force + drag) / mass;
    state.velocity += accel * h;
    state.value    += state.velocity * h;
  }
  return state.value;
}

/* ---- prefers-reduced-motion hook ---- */
function useReducedMotion() {
  const [reduced, setReduced] = useState(
    () => typeof window !== "undefined" &&
          window.matchMedia &&
          window.matchMedia("(prefers-reduced-motion: reduce)").matches
  );
  useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    const on = () => setReduced(mq.matches);
    mq.addEventListener ? mq.addEventListener("change", on) : mq.addListener(on);
    return () => mq.removeEventListener
      ? mq.removeEventListener("change", on) : mq.removeListener(on);
  }, []);
  return reduced;
}

/* ---- coarse-pointer (touch) detection ---- */
const isTouch = () =>
  typeof window !== "undefined" &&
  window.matchMedia &&
  window.matchMedia("(hover: none), (pointer: coarse)").matches;

/* ---- number formatting ---- */
const fmtSGD = (n) =>
  "S$" + n.toLocaleString("en-SG", {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });

Object.assign(window, {
  React, useState, useRef, useEffect, useCallback,
  clamp, lerp, Ease, stepSpring, useReducedMotion, isTouch, fmtSGD,
});
