'use strict';

/**
 * Reducer-shape stats helpers shared across the platform.
 *
 * These were duplicated as static helpers on `Channel` and as instance
 * methods on the older `measurement/specificClass.js`. Consolidated here so
 * any consumer (outlier detection, monster summaries, future analytics)
 * can import a single canonical implementation.
 *
 * Stream-shape filters (low/high/band-pass, kalman, savitzky-golay) stay
 * on Channel as static helpers — they're pipeline state, not reducers.
 */

function mean(arr) {
    if (!arr.length) return 0;
    return arr.reduce((a, b) => a + b, 0) / arr.length;
}

// Sample std dev (n-1 denominator). A single sample has no variance to
// estimate, so we return 0 rather than NaN — callers (e.g. z-score) treat
// 0 as "no spread yet" and skip rejection.
function stdDev(arr) {
    if (arr.length <= 1) return 0;
    const m = mean(arr);
    const variance = arr.reduce((s, v) => s + (v - m) ** 2, 0) / (arr.length - 1);
    return Math.sqrt(variance);
}

function median(arr) {
    if (!arr.length) return 0;
    const sorted = [...arr].sort((a, b) => a - b);
    const mid = Math.floor(sorted.length / 2);
    return sorted.length % 2 !== 0
        ? sorted[mid]
        : (sorted[mid - 1] + sorted[mid]) / 2;
}

function mad(arr) {
    if (!arr.length) return 0;
    const med = median(arr);
    return median(arr.map((v) => Math.abs(v - med)));
}

// Degenerate-range pass-through matches Channel._lerp: callers rely on it
// for early-warmup paths where input bounds haven't separated yet.
function lerp(value, iMin, iMax, oMin, oMax) {
    if (iMin >= iMax) return value;
    return oMin + ((value - iMin) * (oMax - oMin)) / (iMax - iMin);
}

module.exports = { mean, stdDev, median, mad, lerp };
