'use strict';

const { test } = require('node:test');
const assert = require('node:assert/strict');

const { mean, stdDev, median, mad, lerp } = require('../../src/stats');

const EPS = 1e-9;

function near(a, b, eps = EPS) {
    assert.ok(Math.abs(a - b) <= eps, `expected ${a} ≈ ${b} (eps ${eps})`);
}

test('mean: basic and empty', () => {
    assert.equal(mean([1, 2, 3, 4]), 2.5);
    assert.equal(mean([]), 0);
});

test('stdDev: zero-variance, classic sample, single-element, empty', () => {
    assert.equal(stdDev([1, 1, 1, 1]), 0);
    near(stdDev([1, 2, 3, 4, 5]), 1.5811388300841898);
    assert.equal(stdDev([5]), 0);
    assert.equal(stdDev([]), 0);
});

test('median: odd, even, empty', () => {
    assert.equal(median([1, 2, 3, 4, 5]), 3);
    assert.equal(median([1, 2, 3, 4]), 2.5);
    assert.equal(median([]), 0);
});

test('mad: hand-checked sample and constant array', () => {
    // [1,1,2,2,4,6,9] -> median 2 -> |dev| [1,1,0,0,2,4,7] -> sorted
    // [0,0,1,1,2,4,7] -> mad = 1.
    assert.equal(mad([1, 1, 2, 2, 4, 6, 9]), 1);
    assert.equal(mad([5, 5, 5]), 0);
    assert.equal(mad([]), 0);
});

test('lerp: in-range mapping and degenerate pass-through', () => {
    assert.equal(lerp(2, 0, 4, 0, 100), 50);
    assert.equal(lerp(2, 0, 0, 0, 100), 2);
    // iMin > iMax also degenerate (defensive against swapped bounds).
    assert.equal(lerp(2, 4, 0, 0, 100), 2);
});

test('lerp: float arithmetic stays within epsilon', () => {
    near(lerp(0.1, 0, 1, 0, 10), 1);
    near(lerp(1 / 3, 0, 1, 0, 30), 10);
});
