"use strict";
/**
 * io-locks.ts
 *
 * Path constants, lockfile management, and config I/O helpers extracted from
 * unified-config-loader.ts (Phase 1 split — issue #1164).
 *
 * Forward-reference note: loadUnifiedConfigWithLockHeld and
 * writeUnifiedConfigWithLockHeld depend on mergeWithDefaults,
 * validateCompositeVariants, generateYamlHeader, and generateYamlWithComments
 * which still live in unified-config-loader.ts (extracted in later phases).
 * Those are passed as callbacks to avoid a circular import. Once Phases 2–3
 * land in separate modules with no dependency on this file, they can be
 * imported directly.
 */
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 });
exports.writeUnifiedConfigWithLockHeld = exports.loadUnifiedConfigWithLockHeld = exports.withConfigWriteLock = exports.sleepSync = exports.hasLegacyConfig = exports.hasUnifiedConfig = exports.releaseLock = exports.acquireLock = exports.getConfigJsonPath = exports.getConfigYamlPath = exports.GO_DURATION_PATTERN = exports.GO_DURATION_SEGMENT = exports.LOCK_STALE_MS = exports.CONFIG_LOCK = exports.CONFIG_JSON = exports.CONFIG_YAML = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const crypto = __importStar(require("crypto"));
const yaml = __importStar(require("js-yaml"));
const config_manager_1 = require("../../utils/config-manager");
const unified_config_types_1 = require("../unified-config-types");
// ---------------------------------------------------------------------------
// Path constants
// ---------------------------------------------------------------------------
exports.CONFIG_YAML = 'config.yaml';
exports.CONFIG_JSON = 'config.json';
exports.CONFIG_LOCK = 'config.yaml.lock';
/** Lock is stale after this many milliseconds */
exports.LOCK_STALE_MS = 5000;
exports.GO_DURATION_SEGMENT = String.raw `(?:\d+(?:\.\d+)?(?:ns|us|µs|μs|ms|s|m|h))`;
exports.GO_DURATION_PATTERN = new RegExp(`^${exports.GO_DURATION_SEGMENT}+$`);
// ---------------------------------------------------------------------------
// Path helpers
// ---------------------------------------------------------------------------
/**
 * Get path to unified config.yaml
 */
function getConfigYamlPath() {
    return path.join((0, config_manager_1.getCcsDir)(), exports.CONFIG_YAML);
}
exports.getConfigYamlPath = getConfigYamlPath;
/**
 * Get path to legacy config.json
 */
function getConfigJsonPath() {
    return path.join((0, config_manager_1.getCcsDir)(), exports.CONFIG_JSON);
}
exports.getConfigJsonPath = getConfigJsonPath;
/**
 * Get path to config lockfile (internal)
 */
function getLockFilePath() {
    return path.join((0, config_manager_1.getCcsDir)(), exports.CONFIG_LOCK);
}
// ---------------------------------------------------------------------------
// Process check
// ---------------------------------------------------------------------------
function processExists(pid) {
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
// ---------------------------------------------------------------------------
// Lockfile primitives
// ---------------------------------------------------------------------------
/**
 * Acquire lockfile for config write operations.
 * Returns a lock token if acquired, null if already locked by another process.
 * Cleans up stale locks (older than LOCK_STALE_MS).
 */
function acquireLock() {
    const lockPath = getLockFilePath();
    const lockDir = path.dirname(lockPath);
    const lockToken = crypto.randomUUID();
    const lockData = `${process.pid}\n${Date.now()}\n${lockToken}`;
    try {
        if (!fs.existsSync(lockDir)) {
            fs.mkdirSync(lockDir, { recursive: true, mode: 0o700 });
        }
        // Check if lock exists
        if (fs.existsSync(lockPath)) {
            const content = fs.readFileSync(lockPath, 'utf8');
            const [pidStr, timestampStr] = content.trim().split('\n');
            const pid = Number.parseInt(pidStr, 10);
            const timestamp = Number.parseInt(timestampStr, 10);
            const hasLiveOwner = Number.isInteger(pid) && pid > 0 && processExists(pid);
            const isStale = !Number.isFinite(timestamp) || Date.now() - timestamp > exports.LOCK_STALE_MS;
            if (hasLiveOwner) {
                return null;
            }
            if (isStale || !hasLiveOwner) {
                fs.unlinkSync(lockPath);
            }
        }
        // Acquire lock
        fs.writeFileSync(lockPath, lockData, { flag: 'wx', mode: 0o600 });
        return lockToken;
    }
    catch (error) {
        // EEXIST means another process acquired the lock between our check and write
        if (error.code === 'EEXIST') {
            return null;
        }
        return null;
    }
}
exports.acquireLock = acquireLock;
/**
 * Release lockfile after config write operation.
 */
function releaseLock(lockToken) {
    const lockPath = getLockFilePath();
    try {
        if (fs.existsSync(lockPath)) {
            const content = fs.readFileSync(lockPath, 'utf8');
            const fileToken = content.trim().split('\n')[2];
            if (fileToken === lockToken) {
                fs.unlinkSync(lockPath);
            }
        }
    }
    catch {
        // Ignore cleanup errors
    }
}
exports.releaseLock = releaseLock;
// ---------------------------------------------------------------------------
// Config format detection
// ---------------------------------------------------------------------------
/**
 * Check if unified config.yaml exists
 */
function hasUnifiedConfig() {
    return fs.existsSync(getConfigYamlPath());
}
exports.hasUnifiedConfig = hasUnifiedConfig;
/**
 * Check if legacy config.json exists
 */
function hasLegacyConfig() {
    return fs.existsSync(getConfigJsonPath());
}
exports.hasLegacyConfig = hasLegacyConfig;
// ---------------------------------------------------------------------------
// Sync sleep
// ---------------------------------------------------------------------------
/**
 * Sync sleep helper for lock retry loops.
 * Uses Atomics.wait when available to avoid CPU-intensive busy-wait.
 */
function sleepSync(ms) {
    try {
        Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
    }
    catch {
        const end = Date.now() + ms;
        while (Date.now() < end) {
            /* busy-wait */
        }
    }
}
exports.sleepSync = sleepSync;
// ---------------------------------------------------------------------------
// Lock-aware execution wrapper
// ---------------------------------------------------------------------------
/**
 * Execute a callback while holding the config lock.
 */
function withConfigWriteLock(callback) {
    // Acquire lock (retry for up to 1 second)
    const maxRetries = 10;
    const retryDelayMs = 100;
    let lockToken = null;
    for (let i = 0; i < maxRetries; i++) {
        const acquiredToken = acquireLock();
        if (acquiredToken) {
            lockToken = acquiredToken;
            break;
        }
        sleepSync(retryDelayMs);
    }
    if (!lockToken) {
        throw new Error('Config file is locked by another process. Wait a moment and try again.');
    }
    try {
        return callback();
    }
    finally {
        // Always release lock
        releaseLock(lockToken);
    }
}
exports.withConfigWriteLock = withConfigWriteLock;
// ---------------------------------------------------------------------------
// Lock-held read/write helpers
// ---------------------------------------------------------------------------
/**
 * Load unified config directly from disk while lock is already held.
 * Falls back to empty config when file doesn't exist.
 *
 * Forward-reference: mergeWithDefaults and validateCompositeVariants are
 * passed as callbacks to avoid a circular dependency. They will be imported
 * directly once all phases are complete.
 */
function loadUnifiedConfigWithLockHeld(mergeWithDefaults, validateCompositeVariants) {
    const yamlPath = getConfigYamlPath();
    if (!fs.existsSync(yamlPath)) {
        return (0, unified_config_types_1.createEmptyUnifiedConfig)();
    }
    const content = fs.readFileSync(yamlPath, 'utf8');
    const parsed = yaml.load(content);
    if (!(0, unified_config_types_1.isUnifiedConfig)(parsed)) {
        throw new Error(`Invalid config format in ${yamlPath}`);
    }
    const merged = mergeWithDefaults(parsed);
    validateCompositeVariants(merged);
    return merged;
}
exports.loadUnifiedConfigWithLockHeld = loadUnifiedConfigWithLockHeld;
/**
 * Write unified config to disk while lock is already held.
 * Uses atomic write (temp file + rename) to prevent corruption.
 *
 * Forward-reference: generateYamlHeader and generateYamlWithComments are
 * passed as callbacks to avoid a circular dependency. They will be imported
 * directly once all phases are complete.
 */
function writeUnifiedConfigWithLockHeld(config, generateYamlHeader, generateYamlWithComments) {
    const yamlPath = getConfigYamlPath();
    const dir = path.dirname(yamlPath);
    // Ensure directory exists
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
    }
    // Ensure version is set
    config.version = unified_config_types_1.UNIFIED_CONFIG_VERSION;
    // Generate YAML with section comments
    const yamlContent = generateYamlWithComments(config);
    const content = generateYamlHeader() + yamlContent;
    // Atomic write: write to temp file, then rename
    const tempPath = `${yamlPath}.tmp.${process.pid}`;
    try {
        fs.writeFileSync(tempPath, content, { mode: 0o600 });
        fs.renameSync(tempPath, yamlPath);
    }
    catch (error) {
        // Clean up temp file on error
        if (fs.existsSync(tempPath)) {
            try {
                fs.unlinkSync(tempPath);
            }
            catch {
                // Ignore cleanup errors
            }
        }
        // Classify filesystem errors
        const err = error;
        if (err.code === 'ENOSPC') {
            throw new Error('Disk full - cannot save config. Free up space and try again.');
        }
        else if (err.code === 'EROFS' || err.code === 'EACCES') {
            throw new Error(`Cannot write config - check file permissions: ${err.message}`);
        }
        throw error;
    }
}
exports.writeUnifiedConfigWithLockHeld = writeUnifiedConfigWithLockHeld;
//# sourceMappingURL=io-locks.js.map