import { i as __exportAll } from "./tool-display-B7qTeoCS.js";
import { t as emitEvent } from "./emitter-oHo8y9ow.js";
import { a as resolveUserUsagePath, t as provisionUserDirectory } from "./user-directory-CHkgmdWx.js";
import { t as mapRoleToScopes } from "./types-CQrfdFcp.js";
import path from "node:path";
import fs from "node:fs/promises";

//#region src/users/channel-identity-map.ts
const DEFAULT_REFRESH_INTERVAL_MS = 300 * 1e3;
function buildKey(channel, channelUserId) {
	return `${channel}:${channelUserId}`;
}
/**
* Create a new in-memory channel identity map.
*
* Keyed by `${channel}:${channelUserId}` for O(1) lookups.
*/
function createChannelIdentityMap() {
	const store = /* @__PURE__ */ new Map();
	let lastRefreshAt = null;
	let refreshTimer = null;
	function resolveUserId(channel, channelUserId) {
		return store.get(buildKey(channel, channelUserId)) ?? null;
	}
	function updateMapping(entry) {
		store.set(buildKey(entry.channel, entry.channelUserId), entry.userId);
	}
	function removeMapping(channel, channelUserId) {
		store.delete(buildKey(channel, channelUserId));
	}
	function bulkLoad(entries) {
		store.clear();
		for (const entry of entries) store.set(buildKey(entry.channel, entry.channelUserId), entry.userId);
		lastRefreshAt = Date.now();
	}
	function getStats() {
		return {
			count: store.size,
			lastRefreshAt
		};
	}
	function startPeriodicRefresh(fetchFn, intervalMs) {
		stopPeriodicRefresh();
		const interval = intervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
		refreshTimer = setInterval(async () => {
			try {
				bulkLoad(await fetchFn());
			} catch {}
		}, interval);
	}
	function stopPeriodicRefresh() {
		if (refreshTimer !== null) {
			clearInterval(refreshTimer);
			refreshTimer = null;
		}
	}
	return {
		resolveUserId,
		updateMapping,
		removeMapping,
		bulkLoad,
		getStats,
		startPeriodicRefresh,
		stopPeriodicRefresh
	};
}

//#endregion
//#region src/users/token-revocation.ts
const DEFAULT_TTL_MS = 36e5;
const CLEANUP_INTERVAL_MS = 6e4;
/**
* Creates a new token revocation store.
*
* Uses an in-memory Map keyed by token JTI, with values being
* expiry timestamps (milliseconds). Expired entries are pruned
* every 60 seconds via setInterval.
*/
function createTokenRevocationStore(opts) {
	const defaultTtlMs = opts?.defaultTtlMs ?? DEFAULT_TTL_MS;
	const revokedTokens = /* @__PURE__ */ new Map();
	function removeExpired() {
		const now = Date.now();
		for (const [jti, expiryMs] of revokedTokens) if (now >= expiryMs) revokedTokens.delete(jti);
	}
	const cleanupTimer = setInterval(removeExpired, CLEANUP_INTERVAL_MS);
	return {
		isRevoked(jti) {
			const expiryMs = revokedTokens.get(jti);
			if (expiryMs === void 0) return false;
			if (Date.now() >= expiryMs) {
				revokedTokens.delete(jti);
				return false;
			}
			return true;
		},
		revokeToken(jti, expiresAt) {
			const expiryMs = expiresAt !== void 0 ? expiresAt * 1e3 : Date.now() + defaultTtlMs;
			revokedTokens.set(jti, expiryMs);
		},
		handleRevocationEvent(event) {
			this.revokeToken(event.tokenId, event.expiresAt);
		},
		getRevocationCount() {
			removeExpired();
			return revokedTokens.size;
		},
		cleanup() {
			clearInterval(cleanupTimer);
			revokedTokens.clear();
		}
	};
}

//#endregion
//#region src/users/usage-tracker.ts
/**
* Per-user token usage tracking for the multi-tenant gateway.
*
* Tracks API call token consumption (input/output) per user and model.
* Data is stored as an append-only JSON file in each user's usage directory.
*
* This is the tracking foundation. The actual hook into the API call pipeline
* (recording tokens from Anthropic API responses) will be wired when the
* agent message pipeline passes through user context.
*/
const USAGE_FILE = "usage.json";
function emptyUsageSummary() {
	return {
		totalInputTokens: 0,
		totalOutputTokens: 0,
		byModel: {},
		lastUpdated: 0
	};
}
async function readUsageFile(filePath) {
	try {
		const raw = await fs.readFile(filePath, "utf-8");
		const parsed = JSON.parse(raw);
		return {
			totalInputTokens: parsed.totalInputTokens ?? 0,
			totalOutputTokens: parsed.totalOutputTokens ?? 0,
			byModel: parsed.byModel ?? {},
			lastUpdated: parsed.lastUpdated ?? 0
		};
	} catch {
		return emptyUsageSummary();
	}
}
async function writeUsageFile(filePath, summary) {
	const dir = path.dirname(filePath);
	await fs.mkdir(dir, { recursive: true });
	const tmpPath = `${filePath}.tmp`;
	await fs.writeFile(tmpPath, JSON.stringify(summary, null, 2), "utf-8");
	await fs.rename(tmpPath, filePath);
}
/**
* Creates a per-user token usage tracker.
*
* Usage data is stored at {stateDir}/users/{userId}/usage/usage.json.
* The tracker reads the current file, increments counters, and writes back
* using atomic file replacement to prevent corruption.
*/
function createUsageTracker(opts) {
	const { stateDir } = opts;
	return {
		async recordUsage(userId, tokens, model) {
			const usageDir = resolveUserUsagePath(stateDir, userId);
			const filePath = path.join(usageDir, USAGE_FILE);
			const summary = await readUsageFile(filePath);
			summary.totalInputTokens += tokens.input;
			summary.totalOutputTokens += tokens.output;
			if (!summary.byModel[model]) summary.byModel[model] = {
				input: 0,
				output: 0
			};
			summary.byModel[model].input += tokens.input;
			summary.byModel[model].output += tokens.output;
			summary.lastUpdated = Date.now();
			await writeUsageFile(filePath, summary);
		},
		async getUsage(userId) {
			const usageDir = resolveUserUsagePath(stateDir, userId);
			return readUsageFile(path.join(usageDir, USAGE_FILE));
		}
	};
}

//#endregion
//#region src/gateway/server-core/server-startup-users.ts
/**
* User subsystem startup for the multi-tenant gateway.
*
* Initializes and wires together:
* - Channel identity map (channel sender -> hub user ID resolution)
* - Token revocation store (revoked JWT rejection)
* - Usage tracker (per-user token consumption)
* - Hub push event handlers for real-time updates
*
* Called during gateway startup after event system init and before channels start.
*/
var server_startup_users_exports = /* @__PURE__ */ __exportAll({
	getActiveUserSubsystem: () => getActiveUserSubsystem,
	handleHubAssignmentChanged: () => handleHubAssignmentChanged,
	initUserSubsystem: () => initUserSubsystem,
	provisionOnConnect: () => provisionOnConnect
});
/** Module-level reference for the active user subsystem. */
let activeUserSubsystem = null;
/** Get the currently active user subsystem, or null if not initialized. */
function getActiveUserSubsystem() {
	return activeUserSubsystem;
}
/**
* Initialize the multi-tenant user subsystem.
*
* Creates channel identity map, token revocation store, and usage tracker.
* When OIDC issuers are configured, starts periodic identity map refresh
* and registers hub push event handlers.
*/
async function initUserSubsystem(params) {
	const { stateDir, log } = params;
	const channelIdentityMap = createChannelIdentityMap();
	const tokenRevocationStore = createTokenRevocationStore();
	const usageTracker = createUsageTracker({ stateDir });
	if (params.multiTenantConfig?.oidcIssuers && params.multiTenantConfig.oidcIssuers.length > 0 && params.fetchChannelIdentities) {
		try {
			const entries = await params.fetchChannelIdentities();
			channelIdentityMap.bulkLoad(entries);
			log.info(`channel identity map loaded: ${entries.length} mapping${entries.length !== 1 ? "s" : ""}`);
		} catch (err) {
			log.warn(`channel identity map initial fetch failed (will retry on periodic refresh): ${String(err)}`);
		}
		channelIdentityMap.startPeriodicRefresh(params.fetchChannelIdentities);
	}
	log.info("user subsystem initialized");
	const subsystem = {
		channelIdentityMap,
		tokenRevocationStore,
		usageTracker,
		shutdown: () => {
			channelIdentityMap.stopPeriodicRefresh();
			tokenRevocationStore.cleanup();
			activeUserSubsystem = null;
		}
	};
	activeUserSubsystem = subsystem;
	return subsystem;
}
/**
* Handle hub.assignment.changed push event.
*
* Updates all active connections for the affected user with new agent assignments.
* Per research pitfall #5: processes per-connection, not as global broadcast.
*/
function handleHubAssignmentChanged(params) {
	const { userId, agentIds, role, clients, broadcastToConnIds } = params;
	const affectedConnIds = [];
	for (const client of clients) if (client.userId === userId) {
		client.assignedAgentIds = agentIds;
		if (role) {
			client.userRole = role;
			client.connect.scopes = mapRoleToScopes(role);
		}
		affectedConnIds.push(client.connId);
	}
	if (affectedConnIds.length > 0) broadcastToConnIds("auth.assignment.changed", {
		userId,
		agentIds,
		role
	}, new Set(affectedConnIds));
	emitEvent({
		category: "auth",
		severity: "info",
		event: "auth.assignment.changed",
		message: `Assignment updated for user ${userId}: ${agentIds.length} agent(s)`,
		metadata: {
			userId,
			agentIds,
			affectedConnections: affectedConnIds.length
		},
		timestamp: Date.now()
	});
}
/**
* Provision user directory on first connect (idempotent).
*
* Called after successful JWT validation in the connect flow.
* Safe to call on every connect since provisionUserDirectory
* uses recursive mkdir.
*/
async function provisionOnConnect(params) {
	try {
		await provisionUserDirectory(params.stateDir, params.userId);
	} catch (err) {
		params.log.warn(`user directory provisioning failed for ${params.userId}: ${String(err)}`);
	}
}

//#endregion
export { server_startup_users_exports as i, handleHubAssignmentChanged as n, initUserSubsystem as r, getActiveUserSubsystem as t };