import fs from "node:fs/promises";
import path from "node:path";
const writeLocks = new Map();
async function withFileLock(targetPath, writer) {
    const lockKey = path.resolve(targetPath);
    const prev = writeLocks.get(lockKey) ?? Promise.resolve();
    let release;
    const current = new Promise((resolve) => {
        release = resolve;
    });
    writeLocks.set(lockKey, prev.then(() => current));
    await prev;
    try {
        await writer();
    }
    finally {
        release();
        if (writeLocks.get(lockKey) === current) {
            writeLocks.delete(lockKey);
        }
    }
}
async function atomicWriteFile(targetPath, content) {
    await ensureParentDir(targetPath);
    const dir = path.dirname(targetPath);
    const base = path.basename(targetPath);
    const tmpPath = path.join(dir, `.${base}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`);
    await fs.writeFile(tmpPath, content, "utf-8");
    await fs.rename(tmpPath, targetPath);
}
export async function pathExists(targetPath) {
    try {
        await fs.access(targetPath);
        return true;
    }
    catch {
        return false;
    }
}
export async function ensureDir(targetDir) {
    await fs.mkdir(targetDir, { recursive: true });
}
export async function ensureParentDir(targetPath) {
    await ensureDir(path.dirname(targetPath));
}
export async function readJsonFile(targetPath, fallback) {
    if (!(await pathExists(targetPath))) {
        return fallback;
    }
    const raw = await fs.readFile(targetPath, "utf-8");
    return JSON.parse(raw);
}
export async function writeJsonFile(targetPath, data) {
    await withFileLock(targetPath, async () => {
        await atomicWriteFile(targetPath, `${JSON.stringify(data, null, 2)}\n`);
    });
}
export async function writeTextFile(targetPath, content) {
    await withFileLock(targetPath, async () => {
        await atomicWriteFile(targetPath, content);
    });
}
export async function copyFile(sourcePath, targetPath) {
    await withFileLock(targetPath, async () => {
        await ensureParentDir(targetPath);
        await fs.copyFile(sourcePath, targetPath);
    });
}
export async function removeIfExists(targetPath) {
    if (await pathExists(targetPath)) {
        await fs.rm(targetPath, { recursive: true, force: true });
    }
}
export async function copyDir(sourceDir, targetDir) {
    await ensureDir(targetDir);
    const entries = await fs.readdir(sourceDir, { withFileTypes: true });
    await Promise.all(entries.map(async (entry) => {
        const sourcePath = path.join(sourceDir, entry.name);
        const targetPath = path.join(targetDir, entry.name);
        if (entry.isDirectory()) {
            await copyDir(sourcePath, targetPath);
            return;
        }
        if (entry.isSymbolicLink()) {
            const linkTarget = await fs.readlink(sourcePath);
            await fs.symlink(linkTarget, targetPath);
            return;
        }
        await fs.copyFile(sourcePath, targetPath);
    }));
}
function isMissingPathError(err) {
    if (typeof err !== "object" || err === null || !("code" in err)) {
        return false;
    }
    const { code } = err;
    return code === "ENOENT";
}
export async function copyDirIfExists(sourceDir, targetDir) {
    if (!(await pathExists(sourceDir))) {
        return false;
    }
    try {
        await copyDir(sourceDir, targetDir);
        return true;
    }
    catch (err) {
        if (isMissingPathError(err)) {
            await removeIfExists(targetDir);
            return false;
        }
        throw err;
    }
}
