import { open, readFile, unlink, mkdir } from "node:fs/promises";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { hostname } from "node:os";
export class PidLockError extends Error {
    constructor(message, existingLock) {
        super(message);
        this.existingLock = existingLock;
        this.name = "PidLockError";
    }
}
function isPidRunning(pid) {
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
function killAndWait(pid, timeoutMs = 5000) {
    return new Promise((resolve) => {
        try {
            process.kill(pid, "SIGTERM");
        }
        catch {
            resolve(true);
            return;
        }
        const deadline = Date.now() + timeoutMs;
        const poll = setInterval(() => {
            if (!isPidRunning(pid)) {
                clearInterval(poll);
                resolve(true);
                return;
            }
            if (Date.now() >= deadline) {
                clearInterval(poll);
                try {
                    process.kill(pid, "SIGKILL");
                }
                catch {
                    // already gone
                }
                setTimeout(() => resolve(!isPidRunning(pid)), 200);
            }
        }, 100);
    });
}
function getPidFilePath(seaworkHome) {
    return join(seaworkHome, "seawork.pid");
}
function resolveOwnerPid(ownerPid) {
    if (typeof ownerPid === "number" && Number.isInteger(ownerPid) && ownerPid > 0) {
        return ownerPid;
    }
    return process.pid;
}
export async function acquirePidLock(seaworkHome, listen, options) {
    const pidPath = getPidFilePath(seaworkHome);
    // Ensure seaworkHome directory exists
    if (!existsSync(seaworkHome)) {
        await mkdir(seaworkHome, { recursive: true });
    }
    // Try to read existing lock
    let existingLock = null;
    try {
        const content = await readFile(pidPath, "utf-8");
        existingLock = JSON.parse(content);
    }
    catch {
        // No existing lock or invalid JSON - that's fine
    }
    // Check if existing lock is stale
    const lockOwnerPid = resolveOwnerPid(options?.ownerPid);
    if (existingLock) {
        if (isPidRunning(existingLock.pid)) {
            if (existingLock.pid === lockOwnerPid) {
                return;
            }
            // Auto-takeover: kill the old daemon and proceed
            const killed = await killAndWait(existingLock.pid);
            if (!killed) {
                throw new PidLockError(`Another Seawork daemon is already running (PID ${existingLock.pid}, started ${existingLock.startedAt}) and could not be stopped`, existingLock);
            }
        }
        // Stale lock or just-killed process - remove it
        await unlink(pidPath).catch(() => { });
    }
    // Create new lock with exclusive flag
    const lockInfo = {
        pid: lockOwnerPid,
        startedAt: new Date().toISOString(),
        hostname: hostname(),
        uid: process.getuid?.() ?? 0,
        listen,
        ...(process.env.SEAWORK_DESKTOP_MANAGED === "1" ? { desktopManaged: true } : {}),
    };
    let fd;
    try {
        fd = await open(pidPath, "wx");
        await fd.write(JSON.stringify(lockInfo));
    }
    catch (err) {
        if (err.code === "EEXIST") {
            // Race condition - another process created the file
            // Re-read and check
            try {
                const content = await readFile(pidPath, "utf-8");
                const raceLock = JSON.parse(content);
                throw new PidLockError(`Another Seawork daemon is already running (PID ${raceLock.pid})`, raceLock);
            }
            catch (innerErr) {
                if (innerErr instanceof PidLockError)
                    throw innerErr;
                throw new PidLockError("Failed to acquire PID lock due to race condition");
            }
        }
        throw err;
    }
    finally {
        await fd?.close();
    }
}
export async function updatePidLock(seaworkHome, patch, options) {
    const pidPath = getPidFilePath(seaworkHome);
    const lockOwnerPid = resolveOwnerPid(options?.ownerPid);
    const content = await readFile(pidPath, "utf-8");
    const existingLock = JSON.parse(content);
    if (existingLock.pid !== lockOwnerPid) {
        throw new PidLockError(`Cannot update PID lock owned by PID ${existingLock.pid}`, existingLock);
    }
    const updatedLock = {
        ...existingLock,
        ...patch,
    };
    const fd = await open(pidPath, "r+");
    try {
        await fd.truncate(0);
        await fd.writeFile(JSON.stringify(updatedLock));
    }
    finally {
        await fd.close();
    }
}
export async function releasePidLock(seaworkHome, options) {
    const pidPath = getPidFilePath(seaworkHome);
    const lockOwnerPid = resolveOwnerPid(options?.ownerPid);
    try {
        // Only remove if it's our lock
        const content = await readFile(pidPath, "utf-8");
        const lock = JSON.parse(content);
        if (lock.pid === lockOwnerPid) {
            await unlink(pidPath);
        }
    }
    catch {
        // Ignore errors - lock may already be gone
    }
}
export async function getPidLockInfo(seaworkHome) {
    const pidPath = getPidFilePath(seaworkHome);
    try {
        const content = await readFile(pidPath, "utf-8");
        return JSON.parse(content);
    }
    catch {
        return null;
    }
}
export async function isLocked(seaworkHome) {
    const info = await getPidLockInfo(seaworkHome);
    if (!info) {
        return { locked: false };
    }
    if (!isPidRunning(info.pid)) {
        return { locked: false, info };
    }
    return { locked: true, info };
}
//# sourceMappingURL=pid-lock.js.map