import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
import { createId } from "./ids.js";
const fileLocks = new Map();
export async function ensureDir(dir) {
    await mkdir(dir, { recursive: true });
}
export async function readJsonFile(file) {
    try {
        return JSON.parse(await readFile(file, "utf8"));
    }
    catch (error) {
        if (isNodeError(error) && error.code === "ENOENT")
            return undefined;
        throw error;
    }
}
export async function writeJsonFile(file, value) {
    await ensureDir(path.dirname(file));
    await writeFileAtomic(file, `${JSON.stringify(value, null, 2)}\n`);
}
export async function writeTextFile(file, value) {
    await ensureDir(path.dirname(file));
    await writeFileAtomic(file, value);
}
export async function writeBufferFile(file, value) {
    await ensureDir(path.dirname(file));
    await writeFileAtomic(file, value);
}
export async function withFileLock(file, work) {
    const key = path.resolve(file);
    const previous = fileLocks.get(key) ?? Promise.resolve();
    let release;
    const current = new Promise((resolve) => {
        release = resolve;
    });
    fileLocks.set(key, previous.then(() => current, () => current));
    await previous;
    try {
        return await work();
    }
    finally {
        release();
        if (fileLocks.get(key) === current)
            fileLocks.delete(key);
    }
}
async function writeFileAtomic(file, value) {
    const temp = `${file}.tmp-${process.pid}-${Date.now()}-${createId("write")}`;
    if (typeof value === "string") {
        await writeFile(temp, value, "utf8");
    }
    else {
        await writeFile(temp, value);
    }
    try {
        await replaceFileWithRetry(temp, file);
    }
    catch (error) {
        await rm(temp, { force: true }).catch(() => undefined);
        throw error;
    }
}
async function replaceFileWithRetry(temp, file) {
    const delays = process.platform === "win32" ? [10, 25, 50, 100, 200, 400, 800] : [10, 25];
    let lastError;
    for (let attempt = 0; attempt <= delays.length; attempt += 1) {
        try {
            await rename(temp, file);
            return;
        }
        catch (error) {
            if (!isRetryableReplaceError(error))
                throw error;
            lastError = error;
        }
        if (process.platform === "win32") {
            try {
                await rm(file, { force: true });
                await rename(temp, file);
                return;
            }
            catch (error) {
                if (!isRetryableReplaceError(error))
                    throw error;
                lastError = error;
            }
        }
        if (attempt < delays.length)
            await sleep(delays[attempt]);
    }
    throw lastError;
}
function isNodeError(error) {
    return typeof error === "object" && error !== null && "code" in error;
}
function isRetryableReplaceError(error) {
    return isNodeError(error) && ["EPERM", "EACCES", "EBUSY", "EEXIST"].includes(error.code ?? "");
}
//# sourceMappingURL=fs.js.map