"use strict";
/**
 * CLIProxy Usage Transformer
 *
 * Transforms CLIProxy's usage API response into DailyUsage/HourlyUsage/MonthlyUsage
 * types compatible with the CCS analytics dashboard.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildCliproxyUsageHistoryAggregates = exports.transformCliproxyToMonthlyUsage = exports.transformCliproxyToHourlyUsage = exports.transformCliproxyToDailyUsage = exports.pruneCliproxyUsageHistoryDetails = exports.mergeCliproxyUsageHistoryDetails = exports.extractCliproxyUsageHistoryDetails = void 0;
const model_pricing_1 = require("../model-pricing");
const model_identity_1 = require("./model-identity");
/** Build ModelBreakdown from accumulated token counts */
function buildModelBreakdown(modelName, provider, acc) {
    const { inputTokens, outputTokens, cacheReadTokens, cost } = acc;
    return {
        modelName,
        ...(provider && { provider }),
        inputTokens,
        outputTokens,
        cacheCreationTokens: 0,
        cacheReadTokens,
        cost,
    };
}
function createHistoryDetail(provider, model, detail) {
    const pricingProvider = (0, model_identity_1.normalizeUsageProvider)(provider) ?? provider.trim().toLowerCase();
    return {
        model,
        provider: pricingProvider,
        timestamp: detail.timestamp,
        source: detail.source,
        authIndex: String(detail.auth_index),
        inputTokens: detail.tokens?.input_tokens ?? 0,
        outputTokens: detail.tokens?.output_tokens ?? 0,
        cacheReadTokens: detail.tokens?.cached_tokens ?? 0,
        requestCount: 1,
        cost: (0, model_pricing_1.calculateCost)({
            inputTokens: detail.tokens?.input_tokens ?? 0,
            outputTokens: detail.tokens?.output_tokens ?? 0,
            cacheCreationTokens: 0,
            cacheReadTokens: detail.tokens?.cached_tokens ?? 0,
        }, model, { provider: pricingProvider }),
        failed: detail.failed,
    };
}
// ============================================================================
// FLATTEN
// ============================================================================
function hasTrackedUsage(detail) {
    const tokens = detail.tokens;
    return ((tokens?.input_tokens ?? 0) > 0 ||
        (tokens?.output_tokens ?? 0) > 0 ||
        (tokens?.cached_tokens ?? 0) > 0);
}
/**
 * Flatten the nested response.usage.apis[provider].models[model].details[]
 * structure into normalized history details. Failed requests are retained only
 * when they still report tracked token usage that analytics can account for.
 */
function extractCliproxyUsageHistoryDetails(response) {
    const apis = response?.usage?.apis;
    if (!apis)
        return [];
    const results = [];
    for (const [provider, providerData] of Object.entries(apis)) {
        const models = providerData?.models;
        if (!models)
            continue;
        for (const [model, modelData] of Object.entries(models)) {
            const details = modelData?.details;
            if (!details)
                continue;
            for (const detail of details) {
                if (detail.failed && !hasTrackedUsage(detail))
                    continue;
                results.push(createHistoryDetail(provider, model, detail));
            }
        }
    }
    return results;
}
exports.extractCliproxyUsageHistoryDetails = extractCliproxyUsageHistoryDetails;
function createHistorySignature(detail) {
    return [
        detail.model,
        detail.provider ?? '',
        detail.timestamp,
        detail.source,
        detail.authIndex,
        detail.inputTokens,
        detail.outputTokens,
        detail.cacheReadTokens,
        detail.requestCount,
        detail.failed ? '1' : '0',
    ].join('|');
}
function mergeCliproxyUsageHistoryDetails(existing, incoming) {
    const existingCounts = new Map();
    for (const detail of existing) {
        const signature = createHistorySignature(detail);
        const entry = existingCounts.get(signature);
        if (entry) {
            entry.count += 1;
        }
        else {
            existingCounts.set(signature, { detail, count: 1 });
        }
    }
    const incomingCounts = new Map();
    for (const detail of incoming) {
        const signature = createHistorySignature(detail);
        const entry = incomingCounts.get(signature);
        if (entry) {
            entry.count += 1;
        }
        else {
            incomingCounts.set(signature, { detail, count: 1 });
        }
    }
    for (const [signature, incomingEntry] of incomingCounts) {
        const existingEntry = existingCounts.get(signature);
        if (!existingEntry || incomingEntry.count > existingEntry.count) {
            existingCounts.set(signature, {
                detail: incomingEntry.detail,
                count: incomingEntry.count,
            });
        }
    }
    const merged = [];
    for (const { detail, count } of existingCounts.values()) {
        for (let index = 0; index < count; index++) {
            merged.push({ ...detail });
        }
    }
    return merged.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
}
exports.mergeCliproxyUsageHistoryDetails = mergeCliproxyUsageHistoryDetails;
function pruneCliproxyUsageHistoryDetails(details, oldestTimestamp) {
    return details.filter((detail) => {
        const timestamp = Date.parse(detail.timestamp);
        return Number.isFinite(timestamp) && timestamp >= oldestTimestamp;
    });
}
exports.pruneCliproxyUsageHistoryDetails = pruneCliproxyUsageHistoryDetails;
// ============================================================================
// GENERIC AGGREGATOR
// ============================================================================
/** Group flat details by a time key extractor, return sorted DailyUsage-like records */
function aggregateByKey(flat, keyFn, buildRecord, sortFn) {
    // bucket: timeKey -> provider/model key -> accumulator
    const buckets = new Map();
    const requestCounts = new Map();
    for (const detail of flat) {
        const key = keyFn(detail.timestamp);
        if (!buckets.has(key))
            buckets.set(key, new Map());
        requestCounts.set(key, (requestCounts.get(key) ?? 0) + detail.requestCount);
        const modelMap = buckets.get(key);
        const modelKey = `${detail.provider ?? ''}\u0000${detail.model}`;
        if (!modelMap.has(modelKey)) {
            modelMap.set(modelKey, {
                modelName: detail.model,
                provider: detail.provider,
                acc: {
                    inputTokens: 0,
                    outputTokens: 0,
                    cacheReadTokens: 0,
                    cost: 0,
                },
            });
        }
        const acc = modelMap.get(modelKey).acc;
        acc.inputTokens += detail.inputTokens;
        acc.outputTokens += detail.outputTokens;
        acc.cacheReadTokens += detail.cacheReadTokens;
        acc.cost += detail.cost;
    }
    const records = [];
    Array.from(buckets.entries()).forEach(([key, modelMap]) => {
        const breakdowns = Array.from(modelMap.values()).map((entry) => buildModelBreakdown(entry.modelName, entry.provider, entry.acc));
        records.push(buildRecord(key, breakdowns, requestCounts.get(key) ?? 0));
    });
    return records.sort(sortFn);
}
/** Sum token field across all breakdowns */
function sumField(breakdowns, field) {
    return breakdowns.reduce((acc, b) => acc + b[field], 0);
}
// ============================================================================
// TRANSFORMS
// ============================================================================
/** Transform CLIProxy usage response into DailyUsage array (sorted descending by date) */
function transformCliproxyToDailyUsage(response) {
    const flat = extractCliproxyUsageHistoryDetails(response);
    return aggregateByKey(flat, (ts) => ts.slice(0, 10), (date, breakdowns) => {
        const totalCost = sumField(breakdowns, 'cost');
        return {
            date,
            source: 'cliproxy',
            inputTokens: sumField(breakdowns, 'inputTokens'),
            outputTokens: sumField(breakdowns, 'outputTokens'),
            cacheCreationTokens: 0,
            cacheReadTokens: sumField(breakdowns, 'cacheReadTokens'),
            cost: totalCost,
            totalCost,
            modelsUsed: (0, model_identity_1.getModelsUsed)(breakdowns),
            modelBreakdowns: breakdowns,
        };
    }, (a, b) => b.date.localeCompare(a.date));
}
exports.transformCliproxyToDailyUsage = transformCliproxyToDailyUsage;
/** Transform CLIProxy usage response into HourlyUsage array (sorted descending by hour) */
function transformCliproxyToHourlyUsage(response) {
    const flat = extractCliproxyUsageHistoryDetails(response);
    return aggregateByKey(flat, (ts) => {
        const date = ts.slice(0, 10);
        const hour = ts.slice(11, 13) || '00';
        return `${date} ${hour}:00`;
    }, (hour, breakdowns, requestCount) => {
        const totalCost = sumField(breakdowns, 'cost');
        return {
            hour,
            source: 'cliproxy',
            inputTokens: sumField(breakdowns, 'inputTokens'),
            outputTokens: sumField(breakdowns, 'outputTokens'),
            cacheCreationTokens: 0,
            cacheReadTokens: sumField(breakdowns, 'cacheReadTokens'),
            cost: totalCost,
            totalCost,
            modelsUsed: (0, model_identity_1.getModelsUsed)(breakdowns),
            modelBreakdowns: breakdowns,
            requestCount,
        };
    }, (a, b) => b.hour.localeCompare(a.hour));
}
exports.transformCliproxyToHourlyUsage = transformCliproxyToHourlyUsage;
/** Transform CLIProxy usage response into MonthlyUsage array (sorted descending by month) */
function transformCliproxyToMonthlyUsage(response) {
    const flat = extractCliproxyUsageHistoryDetails(response);
    return aggregateByKey(flat, (ts) => ts.slice(0, 7), (month, breakdowns) => ({
        month,
        source: 'cliproxy',
        inputTokens: sumField(breakdowns, 'inputTokens'),
        outputTokens: sumField(breakdowns, 'outputTokens'),
        cacheCreationTokens: 0,
        cacheReadTokens: sumField(breakdowns, 'cacheReadTokens'),
        totalCost: sumField(breakdowns, 'cost'),
        modelsUsed: (0, model_identity_1.getModelsUsed)(breakdowns),
        modelBreakdowns: breakdowns,
    }), (a, b) => b.month.localeCompare(a.month));
}
exports.transformCliproxyToMonthlyUsage = transformCliproxyToMonthlyUsage;
function buildCliproxyUsageHistoryAggregates(details) {
    return {
        daily: aggregateByKey(details, (timestamp) => timestamp.slice(0, 10), (date, breakdowns) => {
            const totalCost = sumField(breakdowns, 'cost');
            return {
                date,
                source: 'cliproxy',
                inputTokens: sumField(breakdowns, 'inputTokens'),
                outputTokens: sumField(breakdowns, 'outputTokens'),
                cacheCreationTokens: 0,
                cacheReadTokens: sumField(breakdowns, 'cacheReadTokens'),
                cost: totalCost,
                totalCost,
                modelsUsed: (0, model_identity_1.getModelsUsed)(breakdowns),
                modelBreakdowns: breakdowns,
            };
        }, (a, b) => b.date.localeCompare(a.date)),
        hourly: aggregateByKey(details, (timestamp) => {
            const date = timestamp.slice(0, 10);
            const hour = timestamp.slice(11, 13) || '00';
            return `${date} ${hour}:00`;
        }, (hour, breakdowns, requestCount) => {
            const totalCost = sumField(breakdowns, 'cost');
            return {
                hour,
                source: 'cliproxy',
                inputTokens: sumField(breakdowns, 'inputTokens'),
                outputTokens: sumField(breakdowns, 'outputTokens'),
                cacheCreationTokens: 0,
                cacheReadTokens: sumField(breakdowns, 'cacheReadTokens'),
                cost: totalCost,
                totalCost,
                modelsUsed: (0, model_identity_1.getModelsUsed)(breakdowns),
                modelBreakdowns: breakdowns,
                requestCount,
            };
        }, (a, b) => b.hour.localeCompare(a.hour)),
        monthly: aggregateByKey(details, (timestamp) => timestamp.slice(0, 7), (month, breakdowns) => ({
            month,
            source: 'cliproxy',
            inputTokens: sumField(breakdowns, 'inputTokens'),
            outputTokens: sumField(breakdowns, 'outputTokens'),
            cacheCreationTokens: 0,
            cacheReadTokens: sumField(breakdowns, 'cacheReadTokens'),
            totalCost: sumField(breakdowns, 'cost'),
            modelsUsed: (0, model_identity_1.getModelsUsed)(breakdowns),
            modelBreakdowns: breakdowns,
        }), (a, b) => b.month.localeCompare(a.month)),
    };
}
exports.buildCliproxyUsageHistoryAggregates = buildCliproxyUsageHistoryAggregates;
//# sourceMappingURL=cliproxy-usage-transformer.js.map