import { mkdir, rm, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { validateRevertSelection } from "./revert-validation.js";
import { normalizeProjectRelativePath } from "./snapshot-policy.js";
import { SnapshotService } from "./snapshot-service.js";
import { TurnSnapshotTracker } from "./turn-snapshot-tracker.js";
export function resolveRevertToChangeSets(allChangeSets, targetId) {
    if (!Number.isInteger(targetId) || targetId <= 0)
        throw new Error("Usage: /revert-to <change-set-id>.");
    const target = allChangeSets.find((changeSet) => changeSet.id === targetId);
    if (!target)
        throw new Error(`Change set #${targetId} was not found.`);
    const changeSets = [...allChangeSets]
        .filter((changeSet) => changeSet.id > targetId && changeSet.status === "active")
        .sort((a, b) => b.id - a.id);
    if (!changeSets.length)
        throw new Error(`Already at change set #${targetId}.`);
    return { targetId, changeSets };
}
export async function resolveLatestUnrevert(snapshotService) {
    const records = (await snapshotService.listRevertRecords())
        .filter((record) => record.status === "applied" && record.operation !== "unrevert")
        .sort((a, b) => b.id - a.id);
    if (!records.length)
        throw new Error("No applied revert is available to unrevert.");
    const revertRecord = records[0];
    if (!revertRecord.preRevertChangeSetId)
        throw new Error("The latest revert has no pre-revert change set to restore.");
    const allChangeSets = await snapshotService.listChangeSets();
    const changeSet = allChangeSets.find((entry) => entry.id === revertRecord.preRevertChangeSetId);
    if (!changeSet)
        throw new Error(`Pre-revert change set #${revertRecord.preRevertChangeSetId} was not found.`);
    if (changeSet.status !== "active")
        throw new Error("The latest revert has already been unreverted or is unavailable.");
    return { revertRecord, changeSet, reactivateChangeSetIds: revertRecord.changeSetIds };
}
export async function applyRevertSelection(options) {
    const snapshotService = options.snapshotService ?? new SnapshotService({ cwd: options.cwd });
    const changeSets = [...options.changeSets].sort((a, b) => b.id - a.id);
    const createdAt = options.createdAt ?? Date.now();
    const validation = await validateRevertSelection(options.cwd, changeSets);
    if (!validation.ok) {
        const record = await appendRevertRecord(snapshotService, {
            createdAt,
            sessionId: options.sessionId,
            changeSetIds: changeSets.map((changeSet) => changeSet.id),
            status: "blocked",
            operation: options.operation ?? "revert",
            message: validation.conflicts.map((conflict) => `${conflict.path}: ${conflict.message}`).join("\n"),
        });
        return { revertRecord: record, affectedFiles: collectAffectedPaths(changeSets) };
    }
    const tracker = new TurnSnapshotTracker({
        cwd: options.cwd,
        sessionId: options.sessionId,
        snapshotService,
    });
    await tracker.beginTurn(`revert:${changeSets.map((changeSet) => changeSet.id).join(",")}:${createdAt}`);
    const affectedFiles = collectAffectedPaths(changeSets);
    for (const path of affectedFiles) {
        await tracker.captureBeforePath(path);
    }
    let preRevertChangeSet;
    try {
        for (const changeSet of changeSets) {
            for (const file of changeSet.files) {
                await applyFileRevert(options.cwd, snapshotService, file);
            }
        }
        preRevertChangeSet = await tracker.persistChangeSet({ createdAt });
        await updateChangeSetStatuses(snapshotService, changeSets.map((changeSet) => changeSet.id), options.reactivateChangeSetIds ?? []);
        const record = await appendRevertRecord(snapshotService, {
            createdAt,
            sessionId: options.sessionId,
            changeSetIds: changeSets.map((changeSet) => changeSet.id),
            status: "applied",
            operation: options.operation ?? "revert",
            preRevertChangeSetId: preRevertChangeSet?.id,
        });
        return { revertRecord: record, preRevertChangeSet, affectedFiles };
    }
    catch (error) {
        preRevertChangeSet = await tracker.persistChangeSet({ createdAt }).catch(() => undefined);
        const record = await appendRevertRecord(snapshotService, {
            createdAt,
            sessionId: options.sessionId,
            changeSetIds: changeSets.map((changeSet) => changeSet.id),
            status: "failed",
            operation: options.operation ?? "revert",
            preRevertChangeSetId: preRevertChangeSet?.id,
            message: error instanceof Error ? error.message : String(error),
        });
        return { revertRecord: record, preRevertChangeSet, affectedFiles };
    }
    finally {
        tracker.finishTurn();
    }
}
async function applyFileRevert(cwd, snapshotService, file) {
    const relativePath = normalizeProjectRelativePath(cwd, file.path);
    if (!relativePath)
        throw new Error(`Unsafe revert path: ${file.path}`);
    if (file.status === "skipped")
        throw new Error(`Cannot revert skipped file: ${relativePath}`);
    const absolutePath = resolve(cwd, relativePath);
    if (file.status === "created") {
        await rm(absolutePath, { force: true });
        return;
    }
    if (!file.beforeHash)
        throw new Error(`Missing before snapshot for ${relativePath}`);
    const content = await snapshotService.readBlob(file.beforeHash);
    await mkdir(dirname(absolutePath), { recursive: true });
    await writeFile(absolutePath, content);
}
async function updateChangeSetStatuses(snapshotService, revertedIds, activeIds) {
    const reverted = new Set(revertedIds);
    const active = new Set(activeIds);
    const allChangeSets = await snapshotService.listChangeSets();
    await snapshotService.writeChangeSets(allChangeSets.map((changeSet) => {
        if (active.has(changeSet.id))
            return { ...changeSet, status: "active" };
        if (reverted.has(changeSet.id))
            return { ...changeSet, status: "reverted" };
        return changeSet;
    }));
}
async function appendRevertRecord(snapshotService, record) {
    const records = await snapshotService.listRevertRecords();
    const nextId = records.reduce((maxId, existing) => Math.max(maxId, existing.id), 0) + 1;
    const fullRecord = { id: nextId, ...record };
    await snapshotService.appendRevertRecord(fullRecord);
    return fullRecord;
}
function collectAffectedPaths(changeSets) {
    return [...new Set(changeSets.flatMap((changeSet) => changeSet.files.map((file) => file.path)))].sort((a, b) => a.localeCompare(b));
}
//# sourceMappingURL=revert-apply.js.map