#!/usr/bin/env node

import { C as normalizeAtsProfileId, M as sessionsDir, x as locksDir } from "./paths--89D6C7M.js";
import process$1 from "node:process";
import { createHash } from "node:crypto";
import { mkdir, open, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";

//#region src/config/session-profile.ts
const ATS_CLI_CONTEXT_ENV_KEY = "ATS_CLI_CONTEXT_ID";
const LEGACY_ATS_CLI_CONTEXT_ENV_KEY = "ATS_AGENT_SESSION_ID";
const CLI_CONTEXT_ENV_KEYS = [ATS_CLI_CONTEXT_ENV_KEY, LEGACY_ATS_CLI_CONTEXT_ENV_KEY];
const SESSION_FILE_MAX_LENGTH = 128;
const SESSION_FILE_HASH_LENGTH = 8;
function resolveCliContext(profileIdInput) {
	const explicit = normalizeSessionId(profileIdInput);
	if (explicit) return {
		cliContextId: explicit,
		source: "manual"
	};
	for (const key of CLI_CONTEXT_ENV_KEYS) {
		const sessionId = normalizeSessionId(process.env[key]);
		if (sessionId) return {
			cliContextId: sessionId,
			source: `environment:${key}`
		};
	}
	return resolveTerminalSessionId();
}
function resolveCliContextId(profileIdInput) {
	return resolveCliContext(profileIdInput)?.cliContextId ?? null;
}
function normalizeSessionSource(raw) {
	const value = String(raw ?? "").trim();
	if (!value) return null;
	return value;
}
async function readCliContextState(input) {
	const cliContextId = resolveSessionStateId(input?.sessionId);
	if (!cliContextId) return null;
	const raw = await readFile(cliContextStatePath(cliContextId), "utf8").catch(() => null);
	if (!raw) return null;
	return parseCliContextState(raw);
}
async function listCliContextStates() {
	const entryNames = await readdir(sessionsDir()).catch(() => []);
	const states = [];
	for (const entryName of entryNames) {
		if (!entryName.endsWith(".json")) continue;
		const raw = await readFile(join(sessionsDir(), entryName), "utf8").catch(() => null);
		if (!raw) continue;
		const parsed = parseCliContextState(raw);
		if (!parsed) continue;
		states.push(parsed);
	}
	return states.sort((left, right) => left.sessionId.localeCompare(right.sessionId));
}
async function writeCliContextState(input) {
	const cliContext = resolveCliContextStateInput(input) ?? resolveCliContext();
	if (!cliContext) return false;
	await mkdir(sessionsDir(), { recursive: true });
	const existing = await readCliContextState({ sessionId: cliContext.cliContextId });
	const nextAtsProfileId = resolveNextSessionAtsProfileId(existing, input);
	const nextSelectedAtsProfileId = resolveNextSelectedAtsProfileId(existing, input);
	const outputPath = cliContextStatePath(cliContext.cliContextId);
	if (!(nextAtsProfileId || nextSelectedAtsProfileId)) {
		await rm(outputPath, { force: true });
		return true;
	}
	await writeCliContextStateFile(outputPath, {
		v: 1,
		sessionId: cliContext.cliContextId,
		cliContextSource: cliContext.source ?? existing?.cliContextSource ?? "manual",
		...nextAtsProfileId ? { atsProfileId: nextAtsProfileId } : {},
		...nextSelectedAtsProfileId ? { selectedAtsProfileId: nextSelectedAtsProfileId } : {},
		updatedAt: (/* @__PURE__ */ new Date()).toISOString()
	});
	return true;
}
function cliContextStatePath(sessionId) {
	return join(sessionsDir(), `${toSafeSessionFile(sessionId)}.json`);
}
function toSafeSessionFile(value) {
	const hash = createHash("sha1").update(value, "utf8").digest("hex").slice(0, SESSION_FILE_HASH_LENGTH);
	const safeBaseRaw = value.replace(/[^a-zA-Z0-9._-]+/g, "_");
	const safeBase = safeBaseRaw.length > 0 ? safeBaseRaw : "session";
	const maxBaseLength = Math.max(1, SESSION_FILE_MAX_LENGTH - SESSION_FILE_HASH_LENGTH - 1);
	return `${safeBase.slice(0, maxBaseLength)}-${hash}`;
}
function resolveCliContextStateInput(input) {
	const sessionId = normalizeSessionId(input.sessionId);
	if (!sessionId) return null;
	return {
		cliContextId: sessionId,
		source: normalizeSessionSource(input.sessionSource) ?? "manual"
	};
}
function resolveSessionStateId(sessionId) {
	const explicit = normalizeSessionId(sessionId);
	if (explicit) return explicit;
	return resolveCliContext()?.cliContextId ?? null;
}
function parseCliContextState(raw) {
	let parsed;
	try {
		parsed = JSON.parse(raw);
	} catch {
		return null;
	}
	if (parsed.v !== 1) return null;
	const sessionId = normalizeSessionId(parsed.sessionId);
	if (!sessionId) return null;
	const atsProfileId = resolveStoredSessionAtsProfileId(parsed);
	const selectedAtsProfileId = resolveStoredSelectedAtsProfileId(parsed);
	if (!(atsProfileId || selectedAtsProfileId)) return null;
	const cliContextSource = normalizeSessionSource(parsed.cliContextSource);
	return {
		v: 1,
		sessionId,
		...cliContextSource ? { cliContextSource } : {},
		...atsProfileId ? { atsProfileId } : {},
		...selectedAtsProfileId ? { selectedAtsProfileId } : {},
		updatedAt: typeof parsed.updatedAt === "string" && parsed.updatedAt.trim().length > 0 ? parsed.updatedAt : (/* @__PURE__ */ new Date(0)).toISOString()
	};
}
function resolveNextSessionAtsProfileId(existing, input) {
	if (!("atsProfileId" in input)) return existing?.atsProfileId;
	return normalizeOptionalAtsProfileId(input.atsProfileId) ?? void 0;
}
function resolveNextSelectedAtsProfileId(existing, input) {
	if (!("selectedAtsProfileId" in input)) return existing?.selectedAtsProfileId;
	return normalizeOptionalAtsProfileId(input.selectedAtsProfileId) ?? void 0;
}
async function writeCliContextStateFile(outputPath, payload) {
	const tempPath = `${outputPath}.tmp-${process.pid}-${Date.now()}`;
	const file = await open(tempPath, "w");
	let writeFailure = null;
	try {
		await file.writeFile(`${JSON.stringify(payload, null, 2)}\n`, "utf8");
		await file.sync();
	} catch (error) {
		writeFailure = error;
	}
	try {
		await file.close();
	} catch (error) {
		if (!writeFailure) writeFailure = error;
	}
	if (writeFailure) {
		await rm(tempPath, { force: true }).catch(() => void 0);
		throw writeFailure;
	}
	try {
		await rename(tempPath, outputPath);
	} catch (error) {
		await rm(tempPath, { force: true }).catch(() => void 0);
		throw error;
	}
}
function normalizeSessionId(value) {
	const raw = String(value ?? "").trim();
	if (!raw) return null;
	return raw.slice(0, 256);
}
function resolveTerminalSessionId() {
	if (!(process.stdin.isTTY || process.stdout.isTTY)) return null;
	const parentPid = Number(process.ppid);
	if (!Number.isInteger(parentPid) || parentPid <= 1) return null;
	return {
		cliContextId: `terminal-ppid-${parentPid}`,
		source: `terminal:ppid-${parentPid}`
	};
}
function resolveStoredSessionAtsProfileId(input) {
	const atsProfileId = normalizeOptionalAtsProfileId(input.atsProfileId);
	const legacyProfile = normalizeOptionalAtsProfileId(input.profile);
	if (atsProfileId && legacyProfile && atsProfileId !== legacyProfile) throw new Error(`conflicting stored ATS profile identity: atsProfileId=${atsProfileId}, profile=${legacyProfile}. Remove or reconcile the legacy profile field so it matches atsProfileId.`);
	return atsProfileId ?? legacyProfile;
}
function resolveStoredSelectedAtsProfileId(input) {
	return normalizeOptionalAtsProfileId(input.selectedAtsProfileId);
}
function normalizeOptionalAtsProfileId(value) {
	if (typeof value !== "string") return null;
	try {
		return normalizeAtsProfileId(value);
	} catch {
		return null;
	}
}

//#endregion
//#region src/config/lock.ts
var AtsLockError = class extends Error {
	code = "ats.locked";
	lockPath;
	lockKey;
	metaRaw;
	meta;
	constructor(input) {
		const normalizedMetaRaw = normalizeMetaRaw(input.metaRaw);
		super(buildAtsLockErrorMessage(input.path, normalizedMetaRaw));
		this.name = "AtsLockError";
		this.lockPath = input.path;
		this.lockKey = input.key;
		this.metaRaw = normalizedMetaRaw;
		this.meta = parseAtsLockMeta(normalizedMetaRaw);
	}
};
function isAtsLockError(error) {
	return error instanceof AtsLockError;
}
async function acquireLock(input) {
	const atsProfileId = normalizeAtsProfileId(input.atsProfileId);
	const sessionId = resolveCliContextId();
	await mkdir(locksDir(atsProfileId), { recursive: true });
	const path = resolveLockPath({
		atsProfileId,
		key: input.key
	});
	for (let attempt = 0; attempt < 2; attempt++) try {
		await writeFile(path, `${JSON.stringify({
			pid: process$1.pid,
			atsProfileId,
			...sessionId ? { sessionId } : {},
			at: (/* @__PURE__ */ new Date()).toISOString(),
			...input.meta ? { ...input.meta } : {}
		})}\n`, {
			encoding: "utf8",
			flag: "wx"
		});
		return { path };
	} catch {
		const existingLock = await readLockSnapshot(path);
		if (await tryCleanupStaleLockPath(path, existingLock)) continue;
		throw new AtsLockError({
			path,
			key: input.key,
			metaRaw: existingLock.metaRaw ?? void 0
		});
	}
	throw new AtsLockError({
		path,
		key: input.key
	});
}
async function releaseLock(lock) {
	await rm(lock.path, { force: true });
}
async function runWithCleanupPreservingRunError(input) {
	let runResult;
	let runSucceeded = false;
	let runError;
	try {
		runResult = await input.run();
		runSucceeded = true;
	} catch (error) {
		runError = error;
	}
	try {
		await input.cleanup();
	} catch (cleanupError) {
		if (!runSucceeded) {
			input.onCleanupErrorAfterRunFailure?.(cleanupError);
			throw runError;
		}
		throw cleanupError;
	}
	if (!runSucceeded) throw runError;
	return runResult;
}
async function runWithHeldLock(input) {
	return await runWithCleanupPreservingRunError({
		run: input.run,
		cleanup: () => releaseLock(input.lock),
		onCleanupErrorAfterRunFailure: (releaseError) => {
			warnLockReleaseFailure({
				error: releaseError,
				lock: input.lock,
				releaseContext: input.releaseContext
			});
		}
	});
}
async function readLockMeta(input) {
	return (await readLockSnapshot(resolveLockPath({
		atsProfileId: input.atsProfileId,
		key: input.key
	}))).meta;
}
async function tryCleanupStaleLock(input) {
	return await tryCleanupStaleLockPath(resolveLockPath({
		atsProfileId: input.atsProfileId,
		key: input.key
	}));
}
function resolveLockPath(input) {
	return join(locksDir(normalizeAtsProfileId(input.atsProfileId)), `${sanitizeFilename(input.key)}.lock`);
}
function sanitizeFilename(value) {
	return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
}
function safeParse(value) {
	try {
		return JSON.parse(value);
	} catch {
		return null;
	}
}
function normalizeMetaRaw(metaRaw) {
	if (typeof metaRaw !== "string") return null;
	const normalized = metaRaw.trim();
	return normalized.length > 0 ? normalized : null;
}
function parseAtsLockMeta(metaRaw) {
	if (!metaRaw) return null;
	const parsed = safeParse(metaRaw);
	if (!parsed || typeof parsed !== "object") return null;
	return parsed;
}
function buildAtsLockErrorMessage(path, metaRaw) {
	if (!metaRaw) return `space is locked (${path})`;
	return `space is locked (${path}). Meta: ${metaRaw}`;
}
function getPidFromMeta(meta) {
	if (!meta || typeof meta !== "object") return null;
	const pid = meta.pid;
	return typeof pid === "number" ? pid : null;
}
async function readLockSnapshot(path) {
	const normalizedMetaRaw = normalizeMetaRaw(await readFile(path, "utf8").catch((error) => {
		if (getErrorCode(error) === "ENOENT") return null;
		throw error;
	}) ?? void 0);
	if (normalizedMetaRaw === null) return {
		metaRaw: null,
		meta: null,
		pid: null,
		hasStalePid: false
	};
	const meta = parseAtsLockMeta(normalizedMetaRaw);
	const pid = getPidFromMeta(meta);
	return {
		metaRaw: normalizedMetaRaw,
		meta,
		pid,
		hasStalePid: pid !== null && !isPidAlive(pid)
	};
}
async function tryCleanupStaleLockPath(path, initialSnapshot) {
	const firstSnapshot = initialSnapshot ?? await readLockSnapshot(path);
	if (firstSnapshot.metaRaw === null || firstSnapshot.pid === null || !firstSnapshot.hasStalePid) return false;
	const confirmedSnapshot = await readLockSnapshot(path);
	if (confirmedSnapshot.metaRaw !== firstSnapshot.metaRaw || confirmedSnapshot.pid !== firstSnapshot.pid || !confirmedSnapshot.hasStalePid) return false;
	await rm(path, { force: true });
	return true;
}
function isPidAlive(pid) {
	try {
		process$1.kill(pid, 0);
		return true;
	} catch (err) {
		if (getErrorCode(err) === "ESRCH") return false;
		return true;
	}
}
function getErrorCode(err) {
	if (!err || typeof err !== "object") return;
	const code = err.code;
	return typeof code === "string" ? code : void 0;
}
function warnLockReleaseFailure(input) {
	console.warn("[ats.lock.release.warning]", {
		lockPath: input.lock.path,
		releaseError: toErrorMessage(input.error),
		releaseContext: input.releaseContext ?? {}
	});
}
function toErrorMessage(error) {
	return error instanceof Error ? error.message : String(error);
}

//#endregion
export { runWithHeldLock as a, LEGACY_ATS_CLI_CONTEXT_ENV_KEY as c, resolveCliContext as d, resolveCliContextId as f, releaseLock as i, listCliContextStates as l, isAtsLockError as n, tryCleanupStaleLock as o, writeCliContextState as p, readLockMeta as r, ATS_CLI_CONTEXT_ENV_KEY as s, acquireLock as t, readCliContextState as u };
//# sourceMappingURL=lock-D6I2-W3A.js.map