// SF Extension — Advisory Sync Lock
// Prevents concurrent worktree syncs from colliding via a simple file lock.
// Stale locks (mtime > 60s) are auto-overridden. Lock acquisition waits up
// to 5 seconds then skips non-fatally.
import { existsSync, statSync, unlinkSync } from "node:fs";
import { join } from "node:path";
import { atomicWriteSync } from "./atomic-write.js";

const STALE_THRESHOLD_MS = 60_000; // 60 seconds
const DEFAULT_TIMEOUT_MS = 5_000; // 5 seconds
const SPIN_INTERVAL_MS = 100; // 100ms polling interval
// SharedArrayBuffer for synchronous sleep via Atomics.wait
const SLEEP_BUFFER = new SharedArrayBuffer(4);
const SLEEP_VIEW = new Int32Array(SLEEP_BUFFER);
function lockFilePath(basePath) {
	return join(basePath, ".sf", "sync.lock");
}
function sleepSync(ms) {
	Atomics.wait(SLEEP_VIEW, 0, 0, ms);
}
/**
 * Acquire an advisory sync lock for the given basePath.
 * Returns { acquired: true } on success, { acquired: false } after timeout.
 *
 * - Creates lock file at {basePath}/.sf/sync.lock with JSON { pid, acquired_at }
 * - If lock exists and mtime > 60s (stale), overrides it
 * - If lock exists and not stale, spins up to timeoutMs before giving up
 */
export function acquireSyncLock(basePath, timeoutMs = DEFAULT_TIMEOUT_MS) {
	const lp = lockFilePath(basePath);
	const deadline = Date.now() + timeoutMs;
	while (true) {
		// Check if lock file exists
		if (existsSync(lp)) {
			// Check staleness
			try {
				const stat = statSync(lp);
				const age = Date.now() - stat.mtimeMs;
				if (age > STALE_THRESHOLD_MS) {
					// Stale lock — override it
					try {
						unlinkSync(lp);
					} catch {
						/* race: already removed */
					}
				} else {
					// Lock is held and not stale — wait or give up
					if (Date.now() >= deadline) {
						return { acquired: false };
					}
					sleepSync(SPIN_INTERVAL_MS);
					continue;
				}
			} catch {
				// stat failed (file removed between exists check and stat) — try to acquire
			}
		}
		// Lock file does not exist (or was just removed) — try to write it
		try {
			const lockData = {
				pid: process.pid,
				acquired_at: new Date().toISOString(),
			};
			atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
			return { acquired: true };
		} catch {
			// Write failed (race condition with another process) — retry or give up
			if (Date.now() >= deadline) {
				return { acquired: false };
			}
			sleepSync(SPIN_INTERVAL_MS);
		}
	}
}
/**
 * Release the advisory sync lock. No-op if lock file does not exist.
 */
export function releaseSyncLock(basePath) {
	const lp = lockFilePath(basePath);
	try {
		if (existsSync(lp)) {
			unlinkSync(lp);
		}
	} catch {
		// Non-fatal — lock may have been released by another process
	}
}
