import { createHash } from "node:crypto";
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { THREADWELL_CONFIG_DIR_NAME } from "../../config.js";
export function getSnapshotPaths(cwd) {
    const dir = join(cwd, THREADWELL_CONFIG_DIR_NAME, "snapshots");
    return {
        dir,
        blobsDir: join(dir, "blobs"),
        changeSetsLog: join(dir, "changesets.jsonl"),
        revertsLog: join(dir, "reverts.jsonl"),
    };
}
export function hashBuffer(content) {
    return createHash("sha256").update(content).digest("hex");
}
export function hashString(content) {
    return hashBuffer(Buffer.from(content, "utf8"));
}
function blobPath(paths, hash) {
    return join(paths.blobsDir, hash.slice(0, 2), hash);
}
async function appendJsonLine(path, record) {
    await mkdir(dirname(path), { recursive: true });
    await writeFile(path, `${JSON.stringify(record)}\n`, { encoding: "utf8", flag: "a" });
}
async function writeJsonLines(path, records) {
    await mkdir(dirname(path), { recursive: true });
    const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
    await writeFile(tempPath, `${records.map((record) => JSON.stringify(record)).join("\n")}\n`, "utf8");
    await rename(tempPath, path);
}
async function readJsonLines(path) {
    let raw;
    try {
        raw = await readFile(path, "utf8");
    }
    catch (error) {
        if (isFileNotFoundError(error))
            return [];
        throw error;
    }
    const records = [];
    for (const line of raw.split(/\r?\n/u)) {
        const trimmed = line.trim();
        if (!trimmed)
            continue;
        records.push(JSON.parse(trimmed));
    }
    return records;
}
function isFileNotFoundError(error) {
    return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
}
export class SnapshotService {
    paths;
    constructor(options) {
        this.paths = getSnapshotPaths(options.cwd);
    }
    async init() {
        await mkdir(this.paths.blobsDir, { recursive: true });
    }
    async storeBlob(content) {
        await this.init();
        const hash = hashBuffer(content);
        const target = blobPath(this.paths, hash);
        try {
            await readFile(target);
            return hash;
        }
        catch (error) {
            if (!isFileNotFoundError(error))
                throw error;
        }
        await mkdir(dirname(target), { recursive: true });
        const tempPath = `${target}.${process.pid}.${Date.now()}.tmp`;
        await writeFile(tempPath, content);
        await rename(tempPath, target);
        return hash;
    }
    async storeText(content) {
        return this.storeBlob(Buffer.from(content, "utf8"));
    }
    async readBlob(hash) {
        return readFile(blobPath(this.paths, hash));
    }
    async readText(hash) {
        return (await this.readBlob(hash)).toString("utf8");
    }
    async appendChangeSet(changeSet) {
        await appendJsonLine(this.paths.changeSetsLog, changeSet);
    }
    async listChangeSets() {
        return readJsonLines(this.paths.changeSetsLog);
    }
    async writeChangeSets(changeSets) {
        await writeJsonLines(this.paths.changeSetsLog, changeSets);
    }
    async getNextChangeSetId() {
        const changeSets = await this.listChangeSets();
        return changeSets.reduce((maxId, changeSet) => Math.max(maxId, changeSet.id), 0) + 1;
    }
    async appendRevertRecord(record) {
        await appendJsonLine(this.paths.revertsLog, record);
    }
    async listRevertRecords() {
        return readJsonLines(this.paths.revertsLog);
    }
}
//# sourceMappingURL=snapshot-service.js.map