import { readFile } from "node:fs/promises";
import { detectChangedFiles } from "./snapshot-change-detector.js";
import { createSnapshotPolicy, evaluateSnapshotFile, isLikelyBinaryContent, } from "./snapshot-policy.js";
import { SnapshotService } from "./snapshot-service.js";
export class TurnSnapshotTracker {
    cwd;
    sessionId;
    snapshotService;
    policy;
    activeBaseline;
    snapshotsByPath = new Map();
    constructor(options) {
        this.cwd = options.cwd;
        this.sessionId = options.sessionId;
        this.snapshotService = options.snapshotService ?? new SnapshotService({ cwd: options.cwd });
        this.policy = options.policy ?? createSnapshotPolicy();
    }
    get baseline() {
        return this.activeBaseline;
    }
    async beginTurn(turnId = `${this.sessionId}:${Date.now()}`) {
        this.snapshotsByPath.clear();
        this.activeBaseline = { turnId, createdAt: Date.now(), files: [], errors: [] };
        try {
            const changedFiles = await detectChangedFiles(this.cwd, { policy: this.policy });
            for (const file of changedFiles) {
                await this.captureBeforePath(file.path);
            }
        }
        catch (error) {
            this.activeBaseline.errors.push(formatSnapshotError(error));
        }
        return this.activeBaseline;
    }
    async captureBeforePath(path) {
        if (!this.activeBaseline)
            await this.beginTurn();
        const existing = this.snapshotsByPath.get(path);
        if (existing)
            return existing;
        const snapshot = await this.capturePath(path);
        this.snapshotsByPath.set(snapshot.path, snapshot);
        this.activeBaseline?.files.push(snapshot);
        return snapshot;
    }
    listBeforeSnapshots() {
        return [...this.snapshotsByPath.values()].sort((a, b) => a.path.localeCompare(b.path));
    }
    async persistChangeSet(options = {}) {
        const baseline = this.activeBaseline;
        if (!baseline)
            return undefined;
        const detectedFiles = await detectChangedFiles(this.cwd, {
            candidatePaths: this.snapshotsByPath.keys(),
            policy: this.policy,
        });
        const candidatePaths = new Set([
            ...this.snapshotsByPath.keys(),
            ...detectedFiles.map((file) => file.path),
        ]);
        const files = [];
        for (const path of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
            const before = this.snapshotsByPath.get(path);
            const changedFile = await this.createChangedFile(path, before);
            if (changedFile)
                files.push(changedFile);
        }
        if (!files.length || files.every((file) => file.status === "skipped"))
            return undefined;
        const changeSet = {
            id: await this.snapshotService.getNextChangeSetId(),
            sessionId: this.sessionId,
            turnId: baseline.turnId,
            userMessageId: options.userMessageId,
            assistantMessageId: options.assistantMessageId,
            createdAt: options.createdAt ?? Date.now(),
            status: "active",
            files,
            summary: summarizeChangedFiles(files),
        };
        await this.snapshotService.appendChangeSet(changeSet);
        return changeSet;
    }
    finishTurn() {
        const baseline = this.activeBaseline;
        this.activeBaseline = undefined;
        this.snapshotsByPath.clear();
        return baseline;
    }
    async createChangedFile(path, before) {
        if (before?.reason === "excluded-path")
            return undefined;
        if (before?.status === "skipped" || before?.status === "error") {
            return { path, status: "skipped", beforeHash: before.beforeHash, skippedReason: before.reason };
        }
        const after = await this.capturePath(path);
        if (after.reason === "excluded-path")
            return undefined;
        if (after.status === "skipped" || after.status === "error") {
            return { path: after.path, status: "skipped", beforeHash: before?.beforeHash, skippedReason: after.reason };
        }
        if (before?.status === "missing" && after.status === "missing")
            return undefined;
        if (after.status === "missing") {
            return before?.beforeHash ? { path: after.path, status: "deleted", beforeHash: before.beforeHash } : undefined;
        }
        if (!before || before.status === "missing") {
            return {
                path: after.path,
                status: "created",
                afterHash: after.beforeHash,
                currentExpectedHash: after.beforeHash,
            };
        }
        if (before.beforeHash === after.beforeHash)
            return undefined;
        return {
            path: after.path,
            status: "modified",
            beforeHash: before.beforeHash,
            afterHash: after.beforeHash,
            currentExpectedHash: after.beforeHash,
        };
    }
    async capturePath(path) {
        try {
            const decision = await evaluateSnapshotFile(this.cwd, path, this.policy);
            if (!decision.ok) {
                return {
                    path: decision.relativePath ?? path,
                    status: decision.reason === "missing" ? "missing" : "skipped",
                    reason: decision.reason,
                };
            }
            const content = await readFile(decision.absolutePath);
            const binary = isLikelyBinaryContent(content);
            const beforeHash = binary ? undefined : await this.snapshotService.storeBlob(content);
            return {
                path: decision.relativePath,
                status: binary ? "skipped" : "captured",
                beforeHash,
                binary,
                reason: binary ? "binary-file" : undefined,
            };
        }
        catch (error) {
            return { path, status: "error", reason: formatSnapshotError(error) };
        }
    }
}
function summarizeChangedFiles(files) {
    const summary = { created: 0, modified: 0, deleted: 0, skipped: 0 };
    for (const file of files) {
        summary[file.status]++;
    }
    return summary;
}
function formatSnapshotError(error) {
    return error instanceof Error ? error.message : String(error);
}
//# sourceMappingURL=turn-snapshot-tracker.js.map