import { vendorOfModel } from '@genesislcap/foundation-ai';
/** Whether a message carries anything a ledger would record. */
function hasUsage(m) {
    return (m.cost != null ||
        m.externalCostUsd != null ||
        m.inputTokens != null ||
        m.outputTokens != null ||
        m.cacheReadTokens != null ||
        m.cacheWriteTokens != null);
}
/**
 * Project a transcript into one row per billable unit of spend.
 *
 * @remarks
 * Reads the messages and returns a table; it stores nothing, mutates nothing, and
 * adds nothing to history. Use it where an **aggregate is not enough** — writing
 * usage rows for per-project attribution, a cost dashboard, or auditing which turns
 * came back unpriced. For a single total, `sumUsage` is the answer and this is the
 * wrong tool.
 *
 * Walks the same shape `sumUsage` does, and is guaranteed to agree with it: summing
 * `costUsd` and `externalCostUsd` across these rows equals `sumUsage(...).costUsd`
 * over the same input, and the four token buckets reconcile likewise. That property
 * is the point of the function — a per-call view that quietly disagrees with the
 * total is worse than no per-call view, because both look right in isolation.
 *
 * Covers the two places spend hides:
 *
 * - **Sub-agent conversations.** A delegating tool call carries the child's entire
 *   conversation on `toolCall.subAgentTrace`, so a walk of top-level messages alone
 *   misses everything a delegating agent spent. A consumer that reimplemented this
 *   and omitted the branch reported $0.106 on a run that cost $0.234.
 * - **Compactions.** A compaction deletes the messages it summarises, banking their
 *   spend on the summary. Those turns produce a single `source: 'compaction'` row.
 *
 * Messages carrying no usage at all (a user turn, a narration) produce no row.
 *
 * **Do not pass the result to `sumUsage`** — the types prevent it, and the reason is
 * that both recurse, so a pre-flattened list would be counted twice. Both functions
 * take raw history.
 *
 * @beta
 */
export function usageRows(messages) {
    return collectRows(messages, 0, undefined);
}
/** Map an already-bucketed {@link AggregateUsage} onto a row's token fields. */
function bucketsOf(usage) {
    return {
        uncachedInputTokens: usage.uncachedInputTokens,
        cacheReadTokens: usage.cacheReadTokens,
        cacheWriteTokens: usage.cacheWriteTokens,
        outputTokens: usage.outputTokens,
    };
}
function collectRows(messages, depth, subAgentOf) {
    var _a, _b, _c, _d, _e, _f, _g;
    const rows = [];
    for (const m of messages) {
        if (hasUsage(m)) {
            const cacheReadTokens = (_a = m.cacheReadTokens) !== null && _a !== void 0 ? _a : 0;
            const cacheWriteTokens = (_b = m.cacheWriteTokens) !== null && _b !== void 0 ? _b : 0;
            rows.push(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ source: 'request', model: m.model, 
                // The driver stamps `ChatMessage.provider` from the resolved provider's own
                // status, so it is AUTHORITATIVE and covers vendors the model allowlists do
                // not — a server-proxied `gpt-5` turn is `provider: 'openai'` on the message
                // and unknown to `vendorOfModel`. Prefer it; fall back to resolving the model
                // id only for messages persisted before the field existed. Still left
                // undefined when neither knows, rather than guessed at.
                provider: (_c = m.provider) !== null && _c !== void 0 ? _c : (m.model ? vendorOfModel(m.model) : undefined) }, (m.cost != null && { costUsd: m.cost })), (m.externalCostUsd != null && { externalCostUsd: m.externalCostUsd })), { 
                // Uncached input is the REMAINDER, matching `sumUsage`: `inputTokens` is the
                // whole prompt and the cache fields break it down. Clamped for the same
                // reason — hand-edited history must not drive a total negative.
                uncachedInputTokens: Math.max(0, ((_d = m.inputTokens) !== null && _d !== void 0 ? _d : 0) - cacheReadTokens - cacheWriteTokens), cacheReadTokens,
                cacheWriteTokens, outputTokens: (_e = m.outputTokens) !== null && _e !== void 0 ? _e : 0 }), (m.agentName != null && { agentName: m.agentName })), { subAgentDepth: depth }), (subAgentOf != null && { subAgentOf })));
        }
        // Spend the compaction banked on this summary's behalf. Its buckets are already
        // disjoint (it is an `AggregateUsage`), so they map across without the
        // subtraction a per-message record needs.
        const rolled = (_f = m.compaction) === null || _f === void 0 ? void 0 : _f.rolledUpUsage;
        if (rolled) {
            rows.push(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ source: 'compaction' }, (rolled.costUsd != null && { costUsd: rolled.costUsd })), bucketsOf(rolled)), (m.agentName != null && { agentName: m.agentName })), { subAgentDepth: depth }), (subAgentOf != null && { subAgentOf })));
        }
        for (const tc of (_g = m.toolCalls) !== null && _g !== void 0 ? _g : []) {
            if (tc.subAgentTrace)
                rows.push(...collectRows(tc.subAgentTrace, depth + 1, tc.id));
        }
    }
    return rows;
}
