/* ============================================================
   utils.js — math helpers, easing, simple geometry
   ============================================================ */

const Utils = {
  clamp(v, min, max) { return v < min ? min : v > max ? max : v; },
  lerp(a, b, t) { return a + (b - a) * t; },
  rand(min, max) { return min + Math.random() * (max - min); },
  randInt(min, max) { return Math.floor(this.rand(min, max + 1)); },
  sign(v) { return v < 0 ? -1 : v > 0 ? 1 : 0; },
  dist(ax, ay, bx, by) { return Math.hypot(bx - ax, by - ay); },

  // axis-aligned box overlap. boxes = {x,y,w,h} with x,y at top-left
  aabb(a, b) {
    return a.x < b.x + b.w && a.x + a.w > b.x &&
           a.y < b.y + b.h && a.y + a.h > b.y;
  },

  // angle helpers (radians). 0 points right, grows clockwise on canvas
  pointFrom(x, y, ang, len) {
    return { x: x + Math.cos(ang) * len, y: y + Math.sin(ang) * len };
  },

  // easing
  easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); },
  easeOutBack(t) {
    const c1 = 1.70158, c3 = c1 + 1;
    return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
  },
  easeInOut(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; },
};

// tiny synthesized sound engine (no asset files needed)
const Sound = (() => {
  let ctx = null;
  let enabled = true;
  function ac() {
    if (!ctx) {
      try { ctx = new (window.AudioContext || window.webkitAudioContext)(); }
      catch (e) { enabled = false; }
    }
    return ctx;
  }
  function blip(freq, dur, type = "square", vol = 0.15, slideTo = null) {
    if (!enabled) return;
    const a = ac(); if (!a) return;
    const o = a.createOscillator();
    const g = a.createGain();
    o.type = type;
    o.frequency.setValueAtTime(freq, a.currentTime);
    if (slideTo) o.frequency.exponentialRampToValueAtTime(slideTo, a.currentTime + dur);
    g.gain.setValueAtTime(vol, a.currentTime);
    g.gain.exponentialRampToValueAtTime(0.0001, a.currentTime + dur);
    o.connect(g); g.connect(a.destination);
    o.start(); o.stop(a.currentTime + dur);
  }
  function noise(dur, vol = 0.2) {
    if (!enabled) return;
    const a = ac(); if (!a) return;
    const buf = a.createBuffer(1, a.sampleRate * dur, a.sampleRate);
    const d = buf.getChannelData(0);
    for (let i = 0; i < d.length; i++) d[i] = (Math.random() * 2 - 1) * (1 - i / d.length);
    const src = a.createBufferSource(); src.buffer = buf;
    const g = a.createGain(); g.gain.value = vol;
    src.connect(g); g.connect(a.destination); src.start();
  }
  return {
    resume() { const a = ac(); if (a && a.state === "suspended") a.resume(); },
    hitLight() { blip(420, 0.07, "square", 0.12); },
    hitHeavy() { noise(0.12, 0.25); blip(180, 0.12, "sawtooth", 0.18, 80); },
    block()    { blip(900, 0.05, "square", 0.1); noise(0.05, 0.1); },
    whiff()    { blip(300, 0.06, "sine", 0.06, 140); },
    jump()     { blip(300, 0.12, "square", 0.08, 600); },
    fireball() { blip(160, 0.25, "sawtooth", 0.15, 520); },
    super()    { blip(120, 0.5, "sawtooth", 0.2, 900); noise(0.4, 0.15); },
    ko()       { blip(140, 0.6, "sawtooth", 0.25, 60); noise(0.5, 0.2); },
    bell()     { blip(880, 0.18, "sine", 0.15); blip(1320, 0.18, "sine", 0.1); },
  };
})();
