/**
 * Module-level append-only timeline of meta/lifecycle events, keyed by session
 * identity (the same `stateKey` space used by the driver registry and the
 * session store).
 *
 * Why a module registry rather than the redux store or the driver:
 * - **Survives the pop-in/pop-out element churn.** A single session can be
 *   emitted from up to two element instances (an `expand` bubble + a `collapse`
 *   panel) sharing one driver; keying by `stateKey` unifies them into one
 *   timeline.
 * - **Survives driver rebuilds.** The driver is torn down and recreated when the
 *   host swaps the agents array (see `agentsChanged` / `deleteDriver`), so a
 *   driver-owned buffer would lose the timeline mid-session. The `stateKey`
 *   outlives the driver.
 * - **Stays out of redux reactivity.** These appends never drive the UI, so
 *   there's no reason to pay immutable-reducer cost or flood the Redux DevTools
 *   action log with debug noise.
 *
 * Surfaced in `getDebugLog().timeline`, merged chronologically with messages and
 * turn snapshots. Ring-buffered — the oldest non-`high` events are evicted first
 * while `high`-importance failure events are retained — so a long-lived session
 * stays bounded in normal use without ever dropping a failure signal.
 *
 * @internal
 */
/**
 * Importance is intrinsic to the event *type*, not the instance, so it's
 * defined once here and stamped automatically by {@link recordMetaEvent} — call
 * sites never pass it. The `Record` is exhaustive: adding a `MetaEventType`
 * without a level here is a compile error.
 *
 * - `high`   — failures and hard limits you almost always want to see.
 * - `normal` — meaningful session flow you read to follow what happened.
 * - `low`    — frequent UI / bookkeeping noise, usually safe to skip.
 */
export const META_EVENT_IMPORTANCE = {
    'turn.error': 'high',
    'tool.failed': 'high',
    'subagent.failed': 'high',
    'file.read-failed': 'high',
    'suggestions.failed': 'high',
    'context.threshold-crossed': 'high',
    'context.compacted': 'high',
    'persistence.restore-orphan': 'high',
    'assistant.connected': 'normal',
    'assistant.disconnected': 'normal',
    'assistant.popout': 'normal',
    'assistant.popin': 'normal',
    'driver.created': 'normal',
    'state.changed': 'normal',
    'turn.start': 'normal',
    'turn.end': 'normal',
    'turn.retry': 'normal',
    'tool.unresolved': 'normal',
    'subagent.started': 'normal',
    'subagent.completed': 'normal',
    'agent.handoff': 'normal',
    'agent.pinned': 'normal',
    'agent.unpinned': 'normal',
    'provider.selected': 'normal',
    'interaction.requested': 'normal',
    'interaction.resolved': 'normal',
    'external-diagnostics.folded': 'normal',
    'session.cleared': 'normal',
    'session.switched': 'low',
    'session.switch-deferred': 'low',
    'persistence.restored': 'normal',
    'persistence.suppressed': 'normal',
    'persistence.not-resumable': 'normal',
    'driver.wired': 'low',
    'driver.unwired': 'low',
    'context.updated': 'low',
    'context.condensed': 'low',
    'panel.toggled': 'low',
    'persistence.toggled': 'low',
    'persistence.saved': 'low',
    'attachment.added': 'low',
};
/**
 * Soft ring-buffer cap. Once exceeded, the oldest *non-`high`* event is evicted;
 * `high`-importance events (failures and hard limits) are never dropped — they're
 * the whole reason the log exists. So a buffer dominated by `high` events is
 * allowed to float above this cap rather than lose a failure signal; in normal
 * use the frequent `low`/`normal` events keep it near the cap. Entries are cheap.
 */
const DEFAULT_MAX_META_EVENTS = 800;
const registry = new Map();
/**
 * Append a meta event to the timeline for `key`. Once the buffer exceeds
 * {@link DEFAULT_MAX_META_EVENTS}, evicts the oldest *non-`high`* event;
 * `high`-importance events are never evicted. An empty `key` is bucketed under
 * `''`, matching how drivers/stores handle an absent session identity, so
 * callers never need to guard.
 */
export function recordMetaEvent(key, type, detail) {
    let buffer = registry.get(key);
    if (!buffer) {
        buffer = { events: [], next: 0 };
        registry.set(key, buffer);
    }
    buffer.events.push({
        index: buffer.next,
        timestamp: new Date().toISOString(),
        type,
        importance: META_EVENT_IMPORTANCE[type],
        detail,
    });
    buffer.next += 1;
    if (buffer.events.length > DEFAULT_MAX_META_EVENTS) {
        // Drop the oldest event that isn't `high` importance. `high` events
        // (failures/limits) are retained even past the cap — losing a failure
        // signal is worse than briefly exceeding the buffer size. If every retained
        // event is `high` (all-failures session), nothing is evicted and the buffer
        // grows; in practice frequent `low`/`normal` events keep it bounded.
        const evictAt = buffer.events.findIndex((e) => e.importance !== 'high');
        if (evictAt !== -1) {
            buffer.events.splice(evictAt, 1);
        }
    }
}
/**
 * Record a turn-ending failure (`turn.error`, importance `high`). The `reason`
 * is typed so the high-importance triage surface stays consistent; pass any
 * diagnostic specifics (attempt counts, offending tool names, etc.) in `detail`.
 */
export function recordTurnError(key, reason, detail) {
    recordMetaEvent(key, 'turn.error', Object.assign({ reason }, detail));
}
/**
 * Record a recoverable retry *within* a turn (`turn.retry`, importance `normal`)
 * — e.g. one malformed/empty attempt that will be retried, as distinct from the
 * final attempt that bails out as a `turn.error`. Include `attempt`/`maxAttempts`
 * in `detail` so the timeline shows which try this was.
 */
export function recordTurnRetry(key, reason, detail) {
    recordMetaEvent(key, 'turn.retry', Object.assign({ reason }, detail));
}
/** Returns the meta-event timeline for `key`, or an empty array if none recorded. */
export function getMetaEvents(key) {
    var _a, _b;
    return (_b = (_a = registry.get(key)) === null || _a === void 0 ? void 0 : _a.events) !== null && _b !== void 0 ? _b : [];
}
/**
 * Merge pre-built meta events into the timeline for `targetKey`, **preserving each
 * event's original `timestamp`** (so it interleaves chronologically on export)
 * while re-indexing onto the target buffer's monotonic counter. Used to fold a
 * sub-agent's harvested events into the parent session — see
 * `ChatDriver.invokeSubAgent`. Unlike {@link recordMetaEvent} it does not stamp a
 * fresh timestamp; the same non-`high` eviction policy is applied once after the
 * batch so the buffer stays bounded.
 */
export function mergeMetaEvents(targetKey, events) {
    if (events.length === 0)
        return;
    let buffer = registry.get(targetKey);
    if (!buffer) {
        buffer = { events: [], next: 0 };
        registry.set(targetKey, buffer);
    }
    for (const event of events) {
        buffer.events.push(Object.assign(Object.assign({}, event), { index: buffer.next }));
        buffer.next += 1;
    }
    // Evict oldest non-`high` events until back under the cap — same policy as
    // recordMetaEvent; `high` events (failures/limits) are never dropped. Single
    // pass: take the overflow count, then drop that many of the oldest non-`high`
    // events in order (if there aren't enough non-`high`, the buffer floats above
    // the cap rather than losing a failure signal).
    let toEvict = buffer.events.length - DEFAULT_MAX_META_EVENTS;
    if (toEvict > 0) {
        buffer.events = buffer.events.filter((e) => {
            if (toEvict > 0 && e.importance !== 'high') {
                toEvict -= 1;
                return false;
            }
            return true;
        });
    }
}
/**
 * Drop the entire timeline for `key`. Used to discard a sub-agent's transient
 * per-invocation session once its events have been harvested into the parent, so
 * the module registry doesn't accumulate one orphaned bucket per sub-agent run.
 */
export function clearSession(key) {
    registry.delete(key);
}
/**
 * Human/agent-facing guide emitted as the first key of the exported debug log,
 * so whoever opens the JSON (often an AI agent) knows how to read it without
 * reverse-engineering the shape. Kept here next to the event catalogue it
 * describes so the two stay in sync.
 */
export const DEBUG_LOG_README = [
    'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',
    '`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
    'Timestamps are millisecond-resolution; entries that share the same millisecond are ordered by a fixed kind rank (event, then turn, then message), which is a heuristic and may not reflect exact causal order within that millisecond — e.g. a user message and the turn it triggered, or a final assistant message and its turn.end event, can appear in either order depending on whether they landed in the same millisecond. Read the logical structure of a turn rather than over-interpreting the micro-ordering of co-timestamped entries of different kinds.',
    "kind:'message' — the conversation. `role` is user/assistant/tool/system-event/synthetic-user; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message LLM usage, where `inputTokens` is the WHOLE prompt for that request and `cacheReadTokens`/`cacheWriteTokens` BREAK IT DOWN rather than add to it — uncached input is `inputTokens` minus those two, and adding the cache fields to `inputTokens` counts the prompt twice. The cache fields are absent on providers that report no cache split (Gemini reports reads only, since implicit caching bills no write) and on messages persisted before they existed, so read them as 0 when missing. Each bucket bills at a different rate — cache reads a fraction of uncached input, cache writes a premium, output highest — so a large token count at a small cost means the prompt was mostly cache hits. `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). On model-produced assistant messages, `model` is the concrete model id that generated it (e.g. 'gemini-2.5-flash-lite') and `providerName` is the registry slot it resolved under (e.g. a tier name like 'high'/'low', or the default); together they attribute the message — and any tool calls it carries — to an exact model even across a mid-session vendor/tier switch, where one slot name can map to different models before and after the switch. Both are undefined on any entry that is NOT an LLM response: non-assistant roles (user/tool/system-event) and 'synthetic-user' echoes; assistant interaction/widget entries (empty content carrying an `interaction` — a rendered widget, not a model turn); driver-authored assistant fallbacks (the timeout, repeated-malformed-call, and empty-response apology messages); and messages restored from a session persisted before these fields existed. One partial case: on a genuine model turn whose provider exposes no `getStatus` (or reports no model), `providerName` is still set but `model` alone is undefined. A 'synthetic-user' message is a display-only echo of an interaction outcome (e.g. the answer a widget reported): it renders on the user's side of the chat and `agentName` is the agent that created it, but it is never sent to the LLM — so it has no matching 'turn' and the model learns the outcome only from the corresponding tool result.",
    "Sub-agent messages appear inline. When a tool delegates to a sub-agent (via `requestSubAgent`), the sub-agent's whole conversation — its own assistant/tool messages, each with their own `content`/`thinking`/`toolCalls`/`toolResult` and per-message `model`/`providerName`/`inputTokens`/`outputTokens`/`cacheReadTokens`/`cacheWriteTokens`/`cost` — is hoisted into the timeline as ordinary kind:'message' entries, interleaved by timestamp right after the tool call that spawned them (so you read the delegation top-to-bottom). A hoisted entry is marked: `subAgentDepth` is its delegation depth (1 for a sub-agent, 2 for a sub-agent's sub-agent, …), `subAgentOf` is the id of the parent tool call that spawned it (correlates it back even when two sub-agents run in one parent turn), `subAgentName` is the sub-agent's own name, and `agentName` is rewritten to a `\"<parent> › <sub-agent>\"` breadcrumb (composing when nested, e.g. `\"UI Builder › Planner › Grounding\"`). The sub-agent's per-LLM-call snapshots also surface as kind:'turn' entries with an N-M `turnIndex`, and subagent.started/completed (or subagent.failed) events bracket the run. Per-message `cost` on hoisted entries is already part of the session total (it is summed from the un-flattened history), so summing the top-level timeline does NOT double-count.",
    "kind:'turn' — one LLM call. `turnIndex` is a string: a top-level turn is the bare counter ('0', '1', …); a sub-agent's turns are numbered under the parent turn that activated them ('3-1', '3-2', …, and a nested sub-agent contributes '3-2-1', …), and `agentName` names the agent that ran the turn. `systemPrompt` and `toolNames` are what the model saw. A systemPrompt of '<repeated — identical to turn N>' was byte-identical to turn N and de-duplicated; the full prompt is shown whenever it changes (often because a stateful agent advanced), so prompt evolution is visible.",
    "kind:'turn'.`agentSnapshot` — the active agent's own view of its internal state, captured at that turn. An agent opts into this by exposing a `getDebugSnapshot()` that returns JSON-serializable per-state info; stateful/flow agents wire it automatically, so you can watch a flow advance turn-by-turn (e.g. current step, cursor, collected fields, pending changes). Absent for agents that don't expose one.",
    "kind:'event' — a meta/lifecycle event. `type` names it (see below); `detail` carries structured data. `detail.placement` is the emitting UI instance: 'bubble' (collapsed), 'panel' (popped-out), or 'standalone'.",
    "Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, subagent.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, retries, handoffs, agent/provider changes, interactions, sub-agent start/complete), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated, context.condensed). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. A 'high' turn.error is often preceded by one or more 'normal' turn.retry events for the same reason — read them together to see how many attempts were made before bailing. 'message' and 'turn' entries carry no importance — they are the substance, always read them.",
    'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations/response-truncated, plus reason-specific diagnostics: attempts (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, for response-truncated the model + maxTokens + outputTokens + tools, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout/response_truncated), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), provider.selected (model/provider for the upcoming turns), interaction.requested/resolved (blocking user widgets — explain quiet gaps; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver), context.updated/threshold-crossed (token + cost), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
    'Sub-agent meta events: a sub-agent\'s own turn.retry/turn.error/tool.failed/tool.unresolved events are merged into this same timeline, tagged with `detail.subAgent` — a `"<parent> › <sub-agent>"` breadcrumb that composes when nested (e.g. `"UI Builder › Planner › Grounding"`) — and interleaved by their original timestamps within the subagent.started→completed/failed bracket. These are the per-attempt/per-failure signals that do NOT appear among the sub-agent\'s (hoisted) messages: a malformed/empty attempt that gets retried produces no message, and the stale-vs-hallucinated split and streak counts live only on the event. A sub-agent\'s high-volume, message-derivable events (turn.start/turn.end, provider.selected, context.updated) are intentionally NOT merged — read its hoisted messages for model/tokens/cost and turn-by-turn activity, and the bracketing subagent.* events for the run\'s span.',
    "`meta` holds context captured at export time: agentSummary (full agent configs), context (active model, token usage, session cost), activeDebugSnapshot (the active agent's `getDebugSnapshot()` taken fresh at export — reflects state NOW, which may have advanced beyond the last turn's agentSnapshot), debug (optional host-supplied debug state), host, and the export timestamp.",
    'To debug a failure: find the last turn.error or tool.failed, then read upward for the user message, the turn(s), and the agent/provider/state events that led into it.',
];
/**
 * Removes all entries. Exposed for test isolation only — not part of the
 * public API.
 *
 * @internal
 */
export function clearMetaEventRegistry() {
    registry.clear();
}
