// Turn the backend's sparse, ordered metric buckets into the three
// continuous point series the trend charts render. Empty intervals become
// explicit points so the chart shows real gaps rather than interpolating
// across missing time: throughput is 0 (no ops happened), while success-rate
// and duration are null (no denominator / no samples) and render as a gap.

import type { MetricsBucket } from '$lib/api/admin';

export type SeriesPoint = { t: string; value: number | null };

export type TrendSeries = {
    throughput: SeriesPoint[];
    success: SeriesPoint[];
    duration: SeriesPoint[];
};

/** Bucket unit the backend uses for a given window (kept in lockstep with
 *  `resolve_bucket` on the server: short windows → hour, else day). */
export function unitForDays(days: number): 'hour' | 'day' {
    return days > 0 && days <= 2 ? 'hour' : 'day';
}

function stepMs(unit: 'hour' | 'day'): number {
    return unit === 'hour' ? 3_600_000 : 86_400_000;
}

/** Truncate a UTC timestamp to the start of its hour/day, matching Postgres
 *  `date_trunc` (which the bucket `t` values already are). */
export function truncUtc(ms: number, unit: 'hour' | 'day'): number {
    const d = new Date(ms);
    if (unit === 'hour') {
        d.setUTCMinutes(0, 0, 0);
    } else {
        d.setUTCHours(0, 0, 0, 0);
    }
    return d.getTime();
}

export function buildSeries(
    buckets: MetricsBucket[],
    days: number,
    now: number = Date.now()
): TrendSeries {
    const unit = unitForDays(days);
    const step = stepMs(unit);
    const byT = new Map<number, MetricsBucket>();
    for (const b of buckets) byT.set(truncUtc(new Date(b.t).getTime(), unit), b);

    let startMs: number;
    let endMs: number;
    if (days > 0) {
        endMs = truncUtc(now, unit);
        startMs = truncUtc(now - days * 86_400_000, unit);
    } else {
        // All-time: span only the data we have.
        if (buckets.length === 0) return { throughput: [], success: [], duration: [] };
        const times = buckets.map((b) => truncUtc(new Date(b.t).getTime(), unit));
        startMs = Math.min(...times);
        endMs = Math.max(...times);
    }

    const throughput: SeriesPoint[] = [];
    const success: SeriesPoint[] = [];
    const duration: SeriesPoint[] = [];
    // Guard against an absurd point count (defensive; backend caps days at 365).
    let guard = 0;
    for (let t = startMs; t <= endMs && guard < 5000; t += step, guard++) {
        const b = byT.get(t);
        const iso = new Date(t).toISOString();
        throughput.push({ t: iso, value: b ? b.n : 0 });
        success.push({ t: iso, value: b && b.n > 0 ? (b.ok / b.n) * 100 : null });
        duration.push({ t: iso, value: b && b.avg_ms != null ? b.avg_ms : null });
    }
    return { throughput, success, duration };
}
