import { clamp01, lerp, remap, smoothstep, damp, seeded } from "./math";

describe("optimization/math — clamp01()", () => {
  it("clamps to [0,1]", () => {
    expect(clamp01(-2)).toBe(0);
    expect(clamp01(0)).toBe(0);
    expect(clamp01(0.37)).toBe(0.37);
    expect(clamp01(1)).toBe(1);
    expect(clamp01(5)).toBe(1);
  });
});

describe("optimization/math — lerp()", () => {
  it("interpolates linearly", () => {
    expect(lerp(0, 10, 0)).toBe(0);
    expect(lerp(0, 10, 1)).toBe(10);
    expect(lerp(0, 10, 0.5)).toBe(5);
    expect(lerp(2, 4, 0.25)).toBe(2.5);
  });

  it("extrapolates past the endpoints", () => {
    expect(lerp(0, 10, 2)).toBe(20);
    expect(lerp(0, 10, -1)).toBe(-10);
  });
});

describe("optimization/math — remap()", () => {
  it("remaps and clamps to the output range", () => {
    expect(remap(5, 0, 10, 0, 100)).toBe(50);
    expect(remap(-5, 0, 10, 0, 100)).toBe(0);
    expect(remap(15, 0, 10, 0, 100)).toBe(100);
  });

  it("defaults the output range to [0,1]", () => {
    expect(remap(2.5, 0, 10)).toBeCloseTo(0.25, 10);
  });

  it("guards against a zero-width input range (no NaN)", () => {
    const v = remap(5, 2, 2, 0, 1);
    expect(Number.isNaN(v)).toBe(false);
  });
});

describe("optimization/math — smoothstep()", () => {
  it("hits 0 and 1 at the edges and 0.5 at the middle", () => {
    expect(smoothstep(0, 1, 0)).toBe(0);
    expect(smoothstep(0, 1, 1)).toBe(1);
    expect(smoothstep(0, 1, 0.5)).toBeCloseTo(0.5, 10);
  });

  it("clamps outside the edges", () => {
    expect(smoothstep(0, 1, -1)).toBe(0);
    expect(smoothstep(0, 1, 2)).toBe(1);
  });

  it("is monotonically non-decreasing", () => {
    let prev = -Infinity;
    for (let x = -0.2; x <= 1.2; x += 0.1) {
      const v = smoothstep(0, 1, x);
      expect(v).toBeGreaterThanOrEqual(prev);
      prev = v;
    }
  });

  it("guards against equal edges (no NaN)", () => {
    expect(Number.isNaN(smoothstep(1, 1, 1))).toBe(false);
  });
});

describe("optimization/math — damp()", () => {
  it("does not move when dt is 0", () => {
    expect(damp(3, 10, 5, 0)).toBe(3);
  });

  it("moves toward the target and converges with large lambda*dt", () => {
    const once = damp(0, 10, 1, 0.5);
    expect(once).toBeGreaterThan(0);
    expect(once).toBeLessThan(10);
    expect(damp(0, 10, 50, 1)).toBeCloseTo(10, 5);
  });
});

describe("optimization/math — seeded()", () => {
  it("is deterministic for a given input", () => {
    expect(seeded(7)).toBe(seeded(7));
  });

  it("returns values within [0,1)", () => {
    for (let i = 0; i < 200; i++) {
      const v = seeded(i);
      expect(v).toBeGreaterThanOrEqual(0);
      expect(v).toBeLessThan(1);
    }
  });

  it("produces different values for different seeds", () => {
    expect(seeded(1)).not.toBe(seeded(2));
  });
});
