/**
 * Server-saved diagnostics (GENC-1351 §5.8) — the forward-only stream that lets
 * the debug log persist across a whole session lifetime, not just the current
 * page load. This module owns the *pure* pieces: the entry type and the
 * reassembly back into the `{ readme, sessionUsage, timeline, meta }` debug-log
 * shape. The element owns capture/cadence; the provider owns storage layout.
 *
 * @packageDocumentation
 */
/**
 * Stable identity for a message diagnostic entry — `timestamp::role::subAgentOf::category`.
 * Used by the forward-capture delta (skip already-emitted messages) and by the
 * restore watermark (skip messages already present in a prior load's stream).
 * `null` when the entry has no timestamp → treat as always-new. Shared so the
 * element's diagnostics capture and the `SessionPersister`'s restore watermark
 * compute identity identically (GENC-1351 §5.8/§5.10).
 *
 * `category` is part of the key because the reasoning/narration/answer split (GENC-1411) emits
 * SEVERAL messages from one model response — the thought summary, any interstitial narration, and
 * the answer/tool-call message — which share `timestamp` + `role` + `subAgentOf`. Keying on those
 * alone collapsed them to one, so the forward-capture dropped all but the first: a server
 * generation's answer message vanished from the persisted debug log while its reasoning survived
 * (GENC-1461). `category` is stable across a message's in-place annotation (only `toolCalls` change,
 * which is deliberately excluded), so a genuine re-append of the same message still dedups.
 *
 * @internal
 */
export function diagnosticsMessageKey(entry) {
    var _a, _b, _c;
    return typeof entry.timestamp === 'string'
        ? `${entry.timestamp}::${(_a = entry.role) !== null && _a !== void 0 ? _a : ''}::${(_b = entry.subAgentOf) !== null && _b !== void 0 ? _b : ''}::${(_c = entry.category) !== null && _c !== void 0 ? _c : ''}`
        : null;
}
/**
 * Identity of a `turn` entry — `turnIndex::timestamp::agentName`.
 *
 * Deliberately NOT the serialized entry, which two copies of the same call do not always
 * share. `buildTimelineEntries` renders `systemPrompt` *relative to the preceding
 * snapshots* (collapsing it to `<repeated — identical to turn N>` when it matches the
 * previous full prompt), so if the snapshot holding that full prompt is evicted from the
 * ring buffer between the unpriced flush and the priced one, the surviving turn renders
 * its prompt differently and the two serializations diverge. Both copies are in the
 * stream by then, so nothing would collapse them — and the log whose purpose is per-call
 * cost would show one model call as two turns, one of them unpriced.
 *
 * All three components are fixed when the snapshot is created and never re-derived.
 * `turnIndex` alone is not enough: it restarts at `'0'` on each page load, and a lifetime
 * log legitimately holds one turn `'0'` per load. `agentName` separates two sub-agents
 * invoked in the same parent turn, which share a `turnIndex` prefix by construction (see
 * `forwardSubAgentSnapshots`).
 */
function turnIdentity(entry) {
    var _a, _b, _c;
    return `turn::${String((_a = entry.turnIndex) !== null && _a !== void 0 ? _a : '')}::${(_b = entry.timestamp) !== null && _b !== void 0 ? _b : ''}::${String((_c = entry.agentName) !== null && _c !== void 0 ? _c : '')}`;
}
// Tie-break co-timestamped entries by cause → call → output (event, turn, message),
// matching the original in-place `getDebugLog` sort.
const KIND_RANK = { event: 0, turn: 1, message: 2 };
// Any non-{event,turn,message} kind (shouldn't reach the timeline — meta-snapshots
// are filtered out first) sorts last.
const UNKNOWN_KIND_RANK = 99;
/**
 * Reassemble diagnostic entries into the `{ readme, sessionUsage, timeline, meta }`
 * debug-log shape (GENC-1351 §5.8). `timeline` = the `message`/`turn`/`event` entries sorted
 * by ISO `timestamp` (kind-rank tie-break). `meta` = the **newest** `meta-snapshot`'s
 * block only — a "state at export" photo, matching the single-page log. The older
 * snapshots are intentionally dropped: the per-turn evolution they would show is
 * already in the timeline (`turn.agentSnapshot` + `context.updated` events), so
 * keeping them would just repeat the bulky, near-static `agentSummary`. `readme` =
 * the passed (current) constant. `sessionUsage` = that same newest snapshot's
 * `meta.context.sessionUsage`, lifted to the top level. Pure — reused for both the
 * live current-page log and the reassembled lifetime log stitched from the persisted
 * stream, so both come out shape-identical.
 *
 * NOTE for a caller stitching a STORED stream (a headless consumer harvesting its own
 * lifetime log, say): the newest stored `meta-snapshot` is frozen at the last
 * config-signature change, so `meta` and `sessionUsage` are as old as that unless you
 * pass a fresh snapshot of your own — {@link withFreshMetaSnapshot} does exactly that,
 * and the element's download path goes through it.
 */
export function assembleDebugLog(entries, readme) {
    var _a, _b, _c;
    // Safety net for exact-duplicate entries: two element instances sharing one
    // session (bubble + popped-out panel) can each append the same entry, and a
    // reload's second instance can re-append restored history. Such copies are
    // byte-identical, so collapse by serialized identity — genuinely-distinct entries
    // (e.g. two real `assistant.connected` from two instances) differ in index /
    // placement and are kept. (The shared cursor store prevents most of this at
    // write time; this guards the read path regardless of what's in the stream.)
    //
    // A `turn` is identified by `turnIndex` + `timestamp` + `agentName` instead (see
    // `turnIdentity`), so the unpriced copy a mid-call flush persisted and the priced copy
    // that follows it collapse into ONE turn — the priced one — rather than reading as two
    // model calls.
    const seenAt = new Map();
    let latestMeta;
    const timeline = [];
    for (const entry of entries) {
        if (entry.kind === 'meta-snapshot') {
            if (!latestMeta || ((_a = entry.timestamp) !== null && _a !== void 0 ? _a : '') >= ((_b = latestMeta.timestamp) !== null && _b !== void 0 ? _b : '')) {
                latestMeta = entry;
            }
            continue;
        }
        const id = entry.kind === 'turn' ? turnIdentity(entry) : JSON.stringify(entry);
        const at = seenAt.get(id);
        if (at !== undefined) {
            // Same entry seen twice — keep the richer copy. `usage` on a turn is the only
            // field that can arrive late, so it is the only upgrade there is to make.
            if (entry.usage && !timeline[at].usage)
                timeline[at] = entry;
            continue;
        }
        seenAt.set(id, timeline.length);
        timeline.push(entry);
    }
    timeline.sort((a, b) => {
        var _a, _b, _c, _d;
        const ta = (_a = a.timestamp) !== null && _a !== void 0 ? _a : '';
        const tb = (_b = b.timestamp) !== null && _b !== void 0 ? _b : '';
        if (ta < tb)
            return -1;
        if (ta > tb)
            return 1;
        return ((_c = KIND_RANK[a.kind]) !== null && _c !== void 0 ? _c : UNKNOWN_KIND_RANK) - ((_d = KIND_RANK[b.kind]) !== null && _d !== void 0 ? _d : UNKNOWN_KIND_RANK);
    });
    // The session totals live inside the newest snapshot's `meta`, which is `unknown` by
    // design (a provider stores these opaquely) — so this narrows just the one path it
    // reads rather than typing the whole block.
    const context = (_c = latestMeta === null || latestMeta === void 0 ? void 0 : latestMeta.meta) === null || _c === void 0 ? void 0 : _c.context;
    return { readme, sessionUsage: context === null || context === void 0 ? void 0 : context.sessionUsage, timeline, meta: latestMeta === null || latestMeta === void 0 ? void 0 : latestMeta.meta };
}
/**
 * A stored diagnostics stream with its `meta-snapshot`s replaced by `fresh` — what to
 * pass {@link assembleDebugLog} when reassembling a persisted stream for export.
 *
 * The stored snapshots are dropped rather than out-timestamped. `assembleDebugLog` keeps
 * whichever claims the later time, and a stream written on another machine can carry a
 * skewed clock; `fresh` is built from live state and so is the more current by
 * construction. Only one snapshot ever survives reassembly, so the count is unchanged —
 * though a snapshot taken while the driver is unwired (mid-popout) can leave
 * `activeFoldStack`/`activeDebugSnapshot` unset where a stored one had them. The figures
 * this exists for — the context block and `sessionUsage` — read the session store and
 * element props, which survive an unwire.
 *
 * Pure, and separate from the element for that reason: the swap it performs is the fix
 * for a silent bug (a lifetime log reporting near-zero spend against a transcript full of
 * priced messages), so it needs to be testable without mounting anything.
 */
export function withFreshMetaSnapshot(stored, fresh) {
    return [...stored.filter((e) => e.kind !== 'meta-snapshot'), fresh];
}
