"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const crypto_1 = require("crypto");
class ProfileContextSyncLock {
    constructor(instancesDir) {
        this.locksDir = path.join(instancesDir, '.locks');
    }
    sanitizeName(name) {
        return name.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
    }
    getLockPath(profileName) {
        const safeName = this.sanitizeName(profileName);
        const profileHash = (0, crypto_1.createHash)('sha1').update(profileName).digest('hex').slice(0, 8);
        return path.join(this.locksDir, `${safeName}-${profileHash}.lock`);
    }
    isProcessAlive(pid) {
        if (!Number.isInteger(pid) || pid <= 0) {
            return false;
        }
        try {
            process.kill(pid, 0);
            return true;
        }
        catch (error) {
            if (error.code === 'EPERM') {
                return true;
            }
            return false;
        }
    }
    parseContextSyncLock(raw) {
        const trimmed = raw.trim();
        if (trimmed.length === 0) {
            return null;
        }
        try {
            const parsed = JSON.parse(trimmed);
            if (typeof parsed.pid === 'number' && Number.isInteger(parsed.pid) && parsed.pid > 0) {
                const nonce = typeof parsed.nonce === 'string' && parsed.nonce.length > 0 ? parsed.nonce : undefined;
                return { pid: parsed.pid, nonce };
            }
        }
        catch {
            const legacyPid = Number.parseInt(trimmed, 10);
            if (Number.isInteger(legacyPid) && legacyPid > 0) {
                return { pid: legacyPid };
            }
        }
        return null;
    }
    readContextSyncLockSnapshot(lockPath) {
        try {
            const raw = fs.readFileSync(lockPath, 'utf8');
            return {
                raw,
                owner: this.parseContextSyncLock(raw),
            };
        }
        catch (error) {
            if (error.code === 'ENOENT') {
                return null;
            }
            return null;
        }
    }
    tryRemoveLockIfUnchanged(lockPath, expectedRaw) {
        try {
            const currentRaw = fs.readFileSync(lockPath, 'utf8');
            if (currentRaw !== expectedRaw) {
                return false;
            }
            fs.unlinkSync(lockPath);
            return true;
        }
        catch {
            return false;
        }
    }
    tryRemoveDeadOwnerLock(lockPath, snapshot) {
        if (!snapshot.owner || this.isProcessAlive(snapshot.owner.pid)) {
            return false;
        }
        return this.tryRemoveLockIfUnchanged(lockPath, snapshot.raw);
    }
    async withNamedLock(lockName, callback) {
        const lockPath = this.getLockPath(lockName);
        const retryDelayMs = 50;
        const staleLockMs = 30000;
        const timeoutMs = staleLockMs + 5000;
        const start = Date.now();
        const ownerPayload = {
            version: 1,
            pid: process.pid,
            nonce: (0, crypto_1.createHash)('sha1')
                .update(`${process.pid}:${Date.now()}:${Math.random()}`)
                .digest('hex')
                .slice(0, 16),
            acquiredAtMs: Date.now(),
        };
        const ownerPayloadRaw = JSON.stringify(ownerPayload);
        fs.mkdirSync(this.locksDir, { recursive: true, mode: 0o700 });
        while (true) {
            try {
                const fd = fs.openSync(lockPath, 'wx', 0o600);
                fs.writeFileSync(fd, ownerPayloadRaw, 'utf8');
                fs.closeSync(fd);
                break;
            }
            catch (error) {
                const err = error;
                if (err.code !== 'EEXIST') {
                    throw error;
                }
                const lockSnapshot = this.readContextSyncLockSnapshot(lockPath);
                if (lockSnapshot) {
                    if (this.tryRemoveDeadOwnerLock(lockPath, lockSnapshot)) {
                        continue;
                    }
                    // For malformed lock payloads, fall back to age-based stale cleanup.
                    if (!lockSnapshot.owner) {
                        try {
                            const lockStats = fs.statSync(lockPath);
                            if (Date.now() - lockStats.mtimeMs > staleLockMs) {
                                if (this.tryRemoveLockIfUnchanged(lockPath, lockSnapshot.raw)) {
                                    continue;
                                }
                            }
                        }
                        catch {
                            // Best-effort stale lock cleanup.
                        }
                    }
                }
                if (Date.now() - start > timeoutMs) {
                    throw new Error(`Timed out waiting for profile context lock: ${lockName}`);
                }
                await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
            }
        }
        try {
            return await callback();
        }
        finally {
            this.tryRemoveLockIfUnchanged(lockPath, ownerPayloadRaw);
        }
    }
    async withLock(profileName, callback) {
        return this.withNamedLock(profileName, callback);
    }
    withNamedLockSync(lockName, callback) {
        const lockPath = this.getLockPath(lockName);
        const retryDelayMs = 50;
        const staleLockMs = 30000;
        const timeoutMs = staleLockMs + 5000;
        const start = Date.now();
        const ownerPayload = {
            version: 1,
            pid: process.pid,
            nonce: (0, crypto_1.createHash)('sha1')
                .update(`${process.pid}:${Date.now()}:${Math.random()}`)
                .digest('hex')
                .slice(0, 16),
            acquiredAtMs: Date.now(),
        };
        const ownerPayloadRaw = JSON.stringify(ownerPayload);
        fs.mkdirSync(this.locksDir, { recursive: true, mode: 0o700 });
        while (true) {
            try {
                const fd = fs.openSync(lockPath, 'wx', 0o600);
                fs.writeFileSync(fd, ownerPayloadRaw, 'utf8');
                fs.closeSync(fd);
                break;
            }
            catch (error) {
                const err = error;
                if (err.code !== 'EEXIST') {
                    throw error;
                }
                const lockSnapshot = this.readContextSyncLockSnapshot(lockPath);
                if (lockSnapshot) {
                    if (this.tryRemoveDeadOwnerLock(lockPath, lockSnapshot)) {
                        continue;
                    }
                    if (!lockSnapshot.owner) {
                        try {
                            const lockStats = fs.statSync(lockPath);
                            if (Date.now() - lockStats.mtimeMs > staleLockMs) {
                                if (this.tryRemoveLockIfUnchanged(lockPath, lockSnapshot.raw)) {
                                    continue;
                                }
                            }
                        }
                        catch {
                            // Best-effort stale lock cleanup.
                        }
                    }
                }
                if (Date.now() - start > timeoutMs) {
                    throw new Error(`Timed out waiting for profile context lock: ${lockName}`);
                }
                const until = Date.now() + retryDelayMs;
                while (Date.now() < until) {
                    // Sync callers need a synchronous retry path here.
                    // This lock only guards short local filesystem normalization work, so
                    // contention should be brief and limited to profile/bootstrap edges.
                }
            }
        }
        try {
            return callback();
        }
        finally {
            this.tryRemoveLockIfUnchanged(lockPath, ownerPayloadRaw);
        }
    }
    withLockSync(profileName, callback) {
        return this.withNamedLockSync(profileName, callback);
    }
}
exports.default = ProfileContextSyncLock;
//# sourceMappingURL=profile-context-sync-lock.js.map