// Generic exclusive file lock, same idiom as calendar.mjs but reusable for any
// critical section. Used for the global BUILD lock so cron builds (promoteDraft)
// and Developer Console builds (preview + publish) never run npm/astro
// concurrently — important on this memory-constrained box.
import { writeFileSync, openSync, closeSync, unlinkSync, statSync } from "node:fs";

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

/**
 * Acquire an exclusive lockfile, run fn, always release.
 * @param {string} lockPath absolute path to the lockfile
 * @param {() => Promise<T>|T} fn critical section
 * @param {{timeoutMs?: number, staleMs?: number}} [opts]
 * @returns {Promise<T>}
 */
export async function withLock(lockPath, fn, { timeoutMs = 300000, staleMs = 15 * 60 * 1000 } = {}) {
  const start = Date.now();
  for (;;) {
    try {
      const fd = openSync(lockPath, "wx"); // exclusive create — fails if it exists
      writeFileSync(lockPath, `${process.pid} ${new Date().toISOString()}`);
      closeSync(fd);
      break;
    } catch {
      try {
        if (Date.now() - statSync(lockPath).mtimeMs > staleMs) {
          unlinkSync(lockPath); // presumed orphaned
          continue;
        }
      } catch {
        /* lock vanished between failed create and stat — retry immediately */
      }
      if (Date.now() - start > timeoutMs) throw new Error(`lock timeout: ${lockPath}`);
      await sleep(200);
    }
  }
  try {
    return await fn();
  } finally {
    try {
      unlinkSync(lockPath);
    } catch {
      /* already gone */
    }
  }
}
