// engine/telemetry/tdigest.ts — Minimal t-digest implementation (SPEC-741)
// Pure TypeScript, no external dependencies.
// Uses compression factor k=100 (centroid count limit).
/** Compression parameter — maximum number of centroids before merge. */
const K = 100;
/**
 * Create an empty t-digest.
 */
export function emptyDigest() {
    return { count: 0, centroids: [] };
}
/**
 * Merge a single sample into an existing digest.
 * Uses the simple sorted-centroid merge algorithm.
 * Returns a new digest (immutable).
 */
export function mergeDigest(digest, sample) {
    const newCentroid = { mean: sample, weight: 1 };
    const newCentroids = [...digest.centroids, newCentroid];
    newCentroids.sort((a, b) => a.mean - b.mean);
    const newCount = digest.count + 1;
    const compressed = compress(newCentroids, newCount);
    return { count: newCount, centroids: compressed };
}
/**
 * Compress centroids using the t-digest merge algorithm.
 * Merges adjacent centroids while respecting the size limit k.
 */
function compress(centroids, totalCount) {
    if (centroids.length <= K) {
        return centroids;
    }
    const result = [];
    let cumWeight = 0;
    for (const c of centroids) {
        if (result.length === 0) {
            result.push({ ...c });
            cumWeight = c.weight;
            continue;
        }
        const last = result[result.length - 1];
        if (last === undefined) {
            result.push({ ...c });
            cumWeight += c.weight;
            continue;
        }
        // Calculate quantile position of the last centroid
        const q = cumWeight / totalCount;
        // Max allowed weight at this quantile position (t-digest size function)
        const maxWeight = (4 * totalCount * q * (1 - q)) / K;
        if (last.weight + c.weight <= Math.max(1, maxWeight)) {
            // Merge into last centroid (weighted average)
            const combinedWeight = last.weight + c.weight;
            last.mean = (last.mean * last.weight + c.mean * c.weight) / combinedWeight;
            last.weight = combinedWeight;
            cumWeight += c.weight;
        }
        else {
            result.push({ ...c });
            cumWeight += c.weight;
        }
    }
    return result;
}
/**
 * Compute the quantile q (0..1) from the digest.
 * Returns the estimated value at quantile q.
 * For empty digest, returns 0.
 */
export function quantile(digest, q) {
    if (digest.centroids.length === 0) {
        return 0;
    }
    const target = q * digest.count;
    let cumWeight = 0;
    for (let i = 0; i < digest.centroids.length; i++) {
        const c = digest.centroids[i];
        if (c === undefined) {
            continue;
        }
        const next = digest.centroids[i + 1];
        cumWeight += c.weight;
        if (cumWeight >= target) {
            if (next === undefined) {
                return c.mean;
            }
            // Interpolate between this and next centroid
            const excess = cumWeight - target;
            const fraction = excess / c.weight;
            return c.mean * fraction + next.mean * (1 - fraction);
        }
    }
    // Return the last centroid mean if we exhausted the list
    const last = digest.centroids[digest.centroids.length - 1];
    return last?.mean ?? 0;
}
/**
 * Serialize a digest to a Buffer (JSON-encoded for portability).
 */
export function serializeDigest(digest) {
    return Buffer.from(JSON.stringify(digest), 'utf-8');
}
/**
 * Deserialize a digest from a Buffer.
 */
export function deserializeDigest(buf) {
    const parsed = JSON.parse(buf.toString('utf-8'));
    return parsed;
}
//# sourceMappingURL=tdigest.js.map