/**
 * Centralized token usage extraction utilities
 * Handles multiple provider formats and optional fields
 *
 * Consolidates token extraction logic from:
 * - GenerationHandler.ts
 * - analytics.ts
 * - streamAnalytics.ts
 */
/**
 * Extract input token count from various provider formats
 * Priority: input > inputTokens > promptTokens
 */
export function extractInputTokens(usage) {
    if (typeof usage.input === "number") {
        return usage.input;
    }
    if (typeof usage.inputTokens === "number") {
        return usage.inputTokens;
    }
    if (typeof usage.promptTokens === "number") {
        return usage.promptTokens;
    }
    return 0;
}
/**
 * Extract output token count from various provider formats
 * Priority: output > outputTokens > completionTokens
 */
export function extractOutputTokens(usage) {
    if (typeof usage.output === "number") {
        return usage.output;
    }
    if (typeof usage.outputTokens === "number") {
        return usage.outputTokens;
    }
    if (typeof usage.completionTokens === "number") {
        return usage.completionTokens;
    }
    return 0;
}
/**
 * Extract total token count from various provider formats
 * Falls back to input + output if total is not provided
 */
export function extractTotalTokens(usage, input, output) {
    if (typeof usage.total === "number") {
        return usage.total;
    }
    if (typeof usage.totalTokens === "number") {
        return usage.totalTokens;
    }
    return input + output;
}
/**
 * Extract reasoning/thinking token count from various provider formats
 * Supports: reasoningTokens, thinkingTokens, reasoning_tokens, reasoning
 */
export function extractReasoningTokens(usage) {
    if (typeof usage.reasoningTokens === "number" && usage.reasoningTokens > 0) {
        return usage.reasoningTokens;
    }
    if (typeof usage.thinkingTokens === "number" && usage.thinkingTokens > 0) {
        return usage.thinkingTokens;
    }
    if (typeof usage.reasoning_tokens === "number" &&
        usage.reasoning_tokens > 0) {
        return usage.reasoning_tokens;
    }
    if (typeof usage.reasoning === "number" && usage.reasoning > 0) {
        return usage.reasoning;
    }
    return undefined;
}
/**
 * Extract cache creation token count from various provider formats
 * Supports: cacheCreationInputTokens, cacheCreationTokens, and the ai@6
 * normalized `inputTokenDetails.cacheWriteTokens` (the only shape the native
 * direct-Anthropic path reports through generateText).
 */
export function extractCacheCreationTokens(usage) {
    if (typeof usage.cacheCreationInputTokens === "number" &&
        usage.cacheCreationInputTokens > 0) {
        return usage.cacheCreationInputTokens;
    }
    if (typeof usage.cacheCreationTokens === "number" &&
        usage.cacheCreationTokens > 0) {
        return usage.cacheCreationTokens;
    }
    if (typeof usage.inputTokenDetails?.cacheWriteTokens === "number" &&
        usage.inputTokenDetails.cacheWriteTokens > 0) {
        return usage.inputTokenDetails.cacheWriteTokens;
    }
    return undefined;
}
/**
 * Extract cache read token count from various provider formats
 * Supports: cacheReadInputTokens, cacheReadTokens, and the ai@6 normalized
 * shapes — flat `cachedInputTokens` and nested
 * `inputTokenDetails.cacheReadTokens` (the only shapes the native
 * direct-Anthropic path reports through generateText).
 */
export function extractCacheReadTokens(usage) {
    if (typeof usage.cacheReadInputTokens === "number" &&
        usage.cacheReadInputTokens > 0) {
        return usage.cacheReadInputTokens;
    }
    if (typeof usage.cacheReadTokens === "number" && usage.cacheReadTokens > 0) {
        return usage.cacheReadTokens;
    }
    if (typeof usage.cachedInputTokens === "number" &&
        usage.cachedInputTokens > 0) {
        return usage.cachedInputTokens;
    }
    if (typeof usage.inputTokenDetails?.cacheReadTokens === "number" &&
        usage.inputTokenDetails.cacheReadTokens > 0) {
        return usage.inputTokenDetails.cacheReadTokens;
    }
    return undefined;
}
/**
 * Extract cache read token count from the OVERLAPPING-convention nested path
 * used by OpenAI / DeepSeek / NIM / OpenAI-compatible providers:
 * `usage.prompt_tokens_details.cached_tokens`.
 *
 * Unlike {@link extractCacheReadTokens} (non-overlapping Anthropic/Vertex
 * convention where cache tokens are reported SEPARATELY from input), the value
 * returned here is a SUBSET already included in `prompt_tokens`. Callers that
 * use this value MUST subtract it from `input` to avoid double-counting.
 */
export function extractCachedInputTokensOverlapping(usage) {
    const cached = usage.prompt_tokens_details?.cached_tokens;
    if (typeof cached === "number" && cached > 0) {
        return cached;
    }
    return undefined;
}
/**
 * Calculate cache savings percentage
 *
 * This represents the percentage of input tokens served from cache.
 * For Anthropic, cache read tokens cost 0.1x, so actual cost savings = cacheSavingsPercent * 0.9
 * For other providers, cost savings may vary based on their cache pricing.
 *
 * @param cacheReadTokens Number of tokens read from cache
 * @param inputTokens Number of non-cached input tokens
 * @returns Percentage of tokens served from cache (0-100), or undefined if no cache usage
 */
export function calculateCacheSavingsPercent(cacheReadTokens, inputTokens) {
    if (cacheReadTokens === undefined || cacheReadTokens <= 0) {
        return undefined;
    }
    const totalInputWithCache = inputTokens + cacheReadTokens;
    if (totalInputWithCache <= 0) {
        return undefined;
    }
    return Math.round((cacheReadTokens / totalInputWithCache) * 100);
}
/**
 * Extract token usage from various provider response formats
 *
 * Handles multiple input formats:
 * - BaseProvider normalized format (input/output/total)
 * - AI SDK format (inputTokens/outputTokens/totalTokens)
 * - OpenAI/Mistral format (promptTokens/completionTokens)
 * - Nested usage objects
 *
 * Also extracts optional fields:
 * - Cache creation and read tokens (Anthropic-style)
 * - Reasoning/thinking tokens (OpenAI o1, Anthropic, Google)
 * - Cache savings percentage
 *
 * @param result Raw usage object from provider response
 * @param options Extraction options
 * @returns Normalized TokenUsage object
 */
export function extractTokenUsage(result, options = {}) {
    const { calculateCacheSavings = true, missingOptionalBehavior = "undefined", } = options;
    // Handle null/undefined input
    if (!result) {
        return { input: 0, output: 0, total: 0 };
    }
    // Handle nested usage object (some providers wrap usage in a usage property)
    const usage = result.usage && typeof result.usage === "object" ? result.usage : result;
    // Extract base token counts
    let input = extractInputTokens(usage);
    const output = extractOutputTokens(usage);
    const total = extractTotalTokens(usage, input, output);
    // Extract optional token fields
    const reasoning = extractReasoningTokens(usage);
    const cacheCreationTokens = extractCacheCreationTokens(usage);
    let cacheReadTokens = extractCacheReadTokens(usage);
    // Overlapping-convention fallback (OpenAI/DeepSeek/NIM/OpenAI-compatible):
    // when no non-overlapping cache-read field was present, look for the nested
    // `prompt_tokens_details.cached_tokens`. That value is a SUBSET already
    // included in `input`, so we must subtract it from `input` to avoid
    // double-counting (and to let calculateCost apply the cheaper cacheRead rate
    // to the cached portion). Only do this when cached <= input so a malformed
    // response can never produce negative input or inflate the total.
    if (cacheReadTokens === undefined) {
        const overlappingCached = extractCachedInputTokensOverlapping(usage);
        if (overlappingCached !== undefined && overlappingCached <= input) {
            cacheReadTokens = overlappingCached;
            input = Math.max(0, input - overlappingCached);
        }
    }
    // Calculate cache savings if enabled
    const cacheSavingsPercent = calculateCacheSavings
        ? calculateCacheSavingsPercent(cacheReadTokens, input)
        : undefined;
    // Build result object
    const tokenUsage = {
        input,
        output,
        total,
    };
    // Add optional fields based on behavior setting
    if (missingOptionalBehavior === "zero") {
        tokenUsage.cacheCreationTokens = cacheCreationTokens ?? 0;
        tokenUsage.cacheReadTokens = cacheReadTokens ?? 0;
        tokenUsage.reasoning = reasoning ?? 0;
        tokenUsage.cacheSavingsPercent = cacheSavingsPercent ?? 0;
    }
    else {
        // Only include optional fields if they have values
        if (cacheCreationTokens !== undefined) {
            tokenUsage.cacheCreationTokens = cacheCreationTokens;
        }
        if (cacheReadTokens !== undefined) {
            tokenUsage.cacheReadTokens = cacheReadTokens;
        }
        if (reasoning !== undefined) {
            tokenUsage.reasoning = reasoning;
        }
        if (cacheSavingsPercent !== undefined) {
            tokenUsage.cacheSavingsPercent = cacheSavingsPercent;
        }
    }
    return tokenUsage;
}
/**
 * Create a default/empty TokenUsage object
 * Useful for error handling and fallback scenarios
 */
export function createEmptyTokenUsage() {
    return {
        input: 0,
        output: 0,
        total: 0,
    };
}
/**
 * Check if a TokenUsage object has any non-zero values
 */
export function hasTokenUsage(usage) {
    return usage.input > 0 || usage.output > 0 || usage.total > 0;
}
/**
 * Merge two TokenUsage objects by summing their values
 * Useful for aggregating usage across multiple calls
 */
export function mergeTokenUsage(a, b) {
    const merged = {
        input: a.input + b.input,
        output: a.output + b.output,
        total: a.total + b.total,
    };
    // Merge optional fields if present in either
    const cacheCreationTokens = (a.cacheCreationTokens ?? 0) + (b.cacheCreationTokens ?? 0);
    const cacheReadTokens = (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0);
    const reasoning = (a.reasoning ?? 0) + (b.reasoning ?? 0);
    if (cacheCreationTokens > 0) {
        merged.cacheCreationTokens = cacheCreationTokens;
    }
    if (cacheReadTokens > 0) {
        merged.cacheReadTokens = cacheReadTokens;
    }
    if (reasoning > 0) {
        merged.reasoning = reasoning;
    }
    // Recalculate cache savings for merged usage
    const cacheSavingsPercent = calculateCacheSavingsPercent(merged.cacheReadTokens, merged.input);
    if (cacheSavingsPercent !== undefined) {
        merged.cacheSavingsPercent = cacheSavingsPercent;
    }
    return merged;
}
