import { ZERO_COST } from '../../utils/cost.js';
import { buildToolResultHashes } from '../../utils/hash.js';
import { generateCheckpointId } from '../../utils/id.js';
function toCheckpointListEntry(cp) {
    return {
        id: cp.id,
        runId: cp.runId,
        iteration: cp.iteration,
        createdAt: cp.createdAt,
        messageCount: cp.messages.length,
    };
}
/**
 * Project an {@link EmergencySaveData} dump to an {@link IterationCheckpoint}
 * shape so `replay({ fromCheckpoint: 'emergency' })` can consume it through
 * the same restore path as any other checkpoint.
 *
 * The projection is lossy: `costInfo`, `guardState.elapsedMs`,
 * `toolResultHashes`, `branchStack`, and `activeNode` are not captured at
 * emergency-save time and default to zero/empty values. The synthetic
 * checkpoint id is derived deterministically from the emergency save id so
 * re-projecting the same dump yields the same {@link CheckpointId}.
 *
 * See ses_005-deterministic-replay design §2 + §5.2.
 */
export function projectEmergencyToCheckpoint(dump) {
    const emergencySuffix = dump.id.replace(/^esave_/, '');
    return {
        id: `cp_emergency_${emergencySuffix}`,
        runId: dump.runId,
        iteration: dump.currentIteration,
        messages: dump.messages,
        tokenUsage: dump.tokenUsage,
        costInfo: { ...ZERO_COST },
        guardState: {
            iterationCount: dump.currentIteration,
            elapsedMs: Math.max(0, dump.savedAt - dump.startedAt),
        },
        createdAt: dump.savedAt,
    };
}
export class CheckpointManager {
    store;
    constructor(store) {
        this.store = store;
    }
    async create(runMgr, iteration, extra) {
        const checkpoint = {
            id: generateCheckpointId(),
            runId: runMgr.id,
            iteration,
            messages: [...runMgr.messages],
            tokenUsage: { ...runMgr.tokenUsage },
            costInfo: { ...runMgr.costInfo },
            guardState: {
                iterationCount: runMgr.currentIteration,
                elapsedMs: Date.now() - (runMgr.getSession().startedAt ?? Date.now()),
            },
            createdAt: Date.now(),
            toolResultHashes: extra?.toolResults ? buildToolResultHashes(extra.toolResults) : undefined,
            branchStack: extra?.branchStack,
            activeNode: extra?.activeNode,
        };
        await this.store.writeCheckpoint(checkpoint);
        return checkpoint;
    }
    async restore(checkpointId) {
        const checkpoint = await this.store.readCheckpoint(checkpointId);
        if (!checkpoint) {
            throw new Error(`Checkpoint not found: ${checkpointId}`);
        }
        return checkpoint;
    }
    async list() {
        return this.store.listCheckpoints();
    }
    /**
     * Listing projection used by the public `listCheckpoints` API. Returns
     * only the fields a consumer needs to pick a fork point for
     * {@link import('./replay/prepare.js').prepareReplayState} — not the
     * full checkpoint payload. See ses_005-deterministic-replay design §3.1.
     */
    async listEntries() {
        const checkpoints = await this.store.listCheckpoints();
        return checkpoints.map(toCheckpointListEntry);
    }
    async prune(keepLast) {
        const all = await this.list();
        if (all.length <= keepLast)
            return;
        const toDelete = all.sort((a, b) => a.createdAt - b.createdAt).slice(0, all.length - keepLast);
        for (const cp of toDelete) {
            await this.store.deleteCheckpoint(cp.id);
        }
    }
    static buildSummary(runMgr, iteration) {
        const lastAssistant = [...runMgr.messages]
            .reverse()
            .find((m) => m.role === 'assistant' && m.content !== null);
        return {
            iteration,
            messageCount: runMgr.messages.length,
            tokenUsage: { ...runMgr.tokenUsage },
            costInfo: { ...runMgr.costInfo },
            lastAssistantMessage: lastAssistant?.content ?? undefined,
        };
    }
}
//# sourceMappingURL=checkpoint.js.map