"use strict";
/* Socket Lib - Built with esbuild */
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var process_lock_exports = {};
__export(process_lock_exports, {
  processLock: () => processLock
});
module.exports = __toCommonJS(process_lock_exports);
var import_fs = require("fs");
var import_fs2 = require("./fs");
var import_logger = require("./logger");
var import_promises = require("./promises");
var import_signal_exit = require("./signal-exit");
const logger = (0, import_logger.getDefaultLogger)();
class ProcessLockManager {
  activeLocks = /* @__PURE__ */ new Set();
  touchTimers = /* @__PURE__ */ new Map();
  exitHandlerRegistered = false;
  /**
   * Ensure process exit handler is registered for cleanup.
   * Registers a handler that cleans up all active locks when the process exits.
   */
  ensureExitHandler() {
    if (this.exitHandlerRegistered) {
      return;
    }
    (0, import_signal_exit.onExit)(() => {
      for (const timer of this.touchTimers.values()) {
        clearInterval(timer);
      }
      this.touchTimers.clear();
      for (const lockPath of this.activeLocks) {
        try {
          if ((0, import_fs.existsSync)(lockPath)) {
            (0, import_fs2.safeDeleteSync)(lockPath, { recursive: true });
          }
        } catch {
        }
      }
    });
    this.exitHandlerRegistered = true;
  }
  /**
   * Touch a lock file to update its mtime.
   * This prevents the lock from being detected as stale during long operations.
   *
   * @param lockPath - Path to the lock directory
   */
  touchLock(lockPath) {
    try {
      if ((0, import_fs.existsSync)(lockPath)) {
        const now = /* @__PURE__ */ new Date();
        (0, import_fs.utimesSync)(lockPath, now, now);
      }
    } catch (error) {
      logger.warn(
        `Failed to touch lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`
      );
    }
  }
  /**
   * Start periodic touching of a lock file.
   * Aligned with npm npx strategy to prevent false stale detection.
   *
   * @param lockPath - Path to the lock directory
   * @param intervalMs - Touch interval in milliseconds
   */
  startTouchTimer(lockPath, intervalMs) {
    if (intervalMs <= 0 || this.touchTimers.has(lockPath)) {
      return;
    }
    const timer = setInterval(() => {
      this.touchLock(lockPath);
    }, intervalMs);
    timer.unref();
    this.touchTimers.set(lockPath, timer);
  }
  /**
   * Stop periodic touching of a lock file.
   *
   * @param lockPath - Path to the lock directory
   */
  stopTouchTimer(lockPath) {
    const timer = this.touchTimers.get(lockPath);
    if (timer) {
      clearInterval(timer);
      this.touchTimers.delete(lockPath);
    }
  }
  /**
   * Check if a lock is stale based on mtime.
   * Uses second-level granularity to avoid APFS floating-point precision issues.
   * Aligned with npm's npx locking strategy.
   *
   * @param lockPath - Path to the lock directory
   * @param staleMs - Stale timeout in milliseconds
   * @returns True if lock exists and is stale
   */
  isStale(lockPath, staleMs) {
    try {
      if (!(0, import_fs.existsSync)(lockPath)) {
        return false;
      }
      const stats = (0, import_fs.statSync)(lockPath);
      const ageSeconds = Math.floor((Date.now() - stats.mtime.getTime()) / 1e3);
      const staleSeconds = Math.floor(staleMs / 1e3);
      return ageSeconds > staleSeconds;
    } catch {
      return false;
    }
  }
  /**
   * Acquire a lock using mkdir for atomic operation.
   * Handles stale locks and includes exit cleanup.
   *
   * This method attempts to create a lock directory atomically. If the lock
   * already exists, it checks if it's stale and removes it before retrying.
   * Uses exponential backoff with jitter for retry attempts.
   *
   * @param lockPath - Path to the lock directory
   * @param options - Lock acquisition options
   * @returns Release function to unlock
   * @throws Error if lock cannot be acquired after all retries
   *
   * @example
   * ```typescript
   * const release = await processLock.acquire('/tmp/my-lock')
   * try {
   *   // Critical section
   * } finally {
   *   release()
   * }
   * ```
   */
  async acquire(lockPath, options = {}) {
    const {
      baseDelayMs = 100,
      maxDelayMs = 1e3,
      retries = 3,
      staleMs = 5e3,
      touchIntervalMs = 2e3
    } = options;
    this.ensureExitHandler();
    return await (0, import_promises.pRetry)(
      async () => {
        try {
          if ((0, import_fs.existsSync)(lockPath) && this.isStale(lockPath, staleMs)) {
            logger.log(`Removing stale lock: ${lockPath}`);
            try {
              (0, import_fs2.safeDeleteSync)(lockPath, { recursive: true });
            } catch {
            }
          }
          if ((0, import_fs.existsSync)(lockPath)) {
            throw new Error(`Lock already exists: ${lockPath}`);
          }
          (0, import_fs.mkdirSync)(lockPath, { recursive: true });
          this.activeLocks.add(lockPath);
          this.startTouchTimer(lockPath, touchIntervalMs);
          return () => this.release(lockPath);
        } catch (error) {
          const code = error.code;
          if (code === "EEXIST") {
            if (this.isStale(lockPath, staleMs)) {
              throw new Error(`Stale lock detected: ${lockPath}`);
            }
            throw new Error(`Lock already exists: ${lockPath}`);
          }
          if (code === "EACCES" || code === "EPERM") {
            throw new Error(
              `Permission denied creating lock: ${lockPath}. Check directory permissions or run with appropriate access.`,
              { cause: error }
            );
          }
          if (code === "EROFS") {
            throw new Error(
              `Cannot create lock on read-only filesystem: ${lockPath}`,
              { cause: error }
            );
          }
          if (code === "ENOTDIR") {
            const parentDir = lockPath.slice(0, lockPath.lastIndexOf("/"));
            throw new Error(
              `Cannot create lock directory: ${lockPath}
A path component is a file when it should be a directory.
Parent path: ${parentDir}
To resolve:
  1. Check if "${parentDir}" contains a file instead of a directory
  2. Remove any conflicting files in the path
  3. Ensure the full parent directory structure exists`,
              { cause: error }
            );
          }
          if (code === "ENOENT") {
            const parentDir = lockPath.slice(0, lockPath.lastIndexOf("/"));
            throw new Error(
              `Cannot create lock directory: ${lockPath}
Parent directory does not exist: ${parentDir}
To resolve:
  1. Ensure the parent directory "${parentDir}" exists
  2. Create the directory structure: mkdir -p "${parentDir}"
  3. Check filesystem permissions allow directory creation`,
              { cause: error }
            );
          }
          throw new Error(`Failed to acquire lock: ${lockPath}`, {
            cause: error
          });
        }
      },
      {
        retries,
        baseDelayMs,
        maxDelayMs,
        jitter: true
      }
    );
  }
  /**
   * Release a lock and remove from tracking.
   * Stops periodic touching and removes the lock directory.
   *
   * @param lockPath - Path to the lock directory
   *
   * @example
   * ```typescript
   * processLock.release('/tmp/my-lock')
   * ```
   */
  release(lockPath) {
    this.stopTouchTimer(lockPath);
    try {
      if ((0, import_fs.existsSync)(lockPath)) {
        (0, import_fs2.safeDeleteSync)(lockPath, { recursive: true });
      }
      this.activeLocks.delete(lockPath);
    } catch (error) {
      logger.warn(
        `Failed to release lock ${lockPath}: ${error instanceof Error ? error.message : String(error)}`
      );
    }
  }
  /**
   * Execute a function with exclusive lock protection.
   * Automatically handles lock acquisition, execution, and cleanup.
   *
   * This is the recommended way to use process locks, as it guarantees
   * cleanup even if the callback throws an error.
   *
   * @param lockPath - Path to the lock directory
   * @param fn - Function to execute while holding the lock
   * @param options - Lock acquisition options
   * @returns Result of the callback function
   * @throws Error from callback or lock acquisition failure
   *
   * @example
   * ```typescript
   * const result = await processLock.withLock('/tmp/my-lock', async () => {
   *   // Critical section
   *   return someValue
   * })
   * ```
   */
  async withLock(lockPath, fn, options) {
    const release = await this.acquire(lockPath, options);
    try {
      return await fn();
    } finally {
      release();
    }
  }
}
const processLock = new ProcessLockManager();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  processLock
});
