import { getLogger } from "./utils/logger.js";
import { PROP_SESSION_REPLAY_ID } from "./core/constants.js";
import { SessionContext, _sessionStorage, getActiveContext } from "./context.js";
import { isServerless } from "./serverless.js";
import { randomUUID } from "node:crypto";

//#region src/session.ts
/**
* Session context manager using Node.js AsyncLocalStorage.
*
* Use `.run()` to execute code within session context. The session
* auto-ends when the callback completes, emitting `[Agent] Session End`.
*
* @example
* ```typescript
* const session = agent.session();
* await session.run(async (s) => {
*   s.trackUserMessage('What is retention?');
*   s.trackAiMessage('Retention is...', 'gpt-4', 'openai', 200);
* });
* ```
*/
let _warnedRunSyncAutoFlush = false;
function _warnRunSyncAutoFlush() {
	if (_warnedRunSyncAutoFlush) return;
	_warnedRunSyncAutoFlush = true;
	getLogger().warn("runSync() cannot flush asynchronously — autoFlush has no effect. Use session.run() (async) or call `await ai.flush()` after runSync().");
}
/** @internal Reset the one-time warning flag. For test isolation only. */
function _resetRunSyncWarning() {
	_warnedRunSyncAutoFlush = false;
}
var Session = class Session {
	sessionId;
	traceId = null;
	idleTimeoutMinutes;
	userId;
	deviceId;
	browserSessionId;
	tags;
	autoFlush;
	trackSessionEnd;
	/** @internal Set by `runAs()` to suppress auto user-message tracking in delegation contexts. */
	_skipAutoUserTracking = false;
	_agent;
	_enrichments = null;
	_sessionReplayId;
	constructor(agent, opts = {}) {
		this.sessionId = opts.sessionId ?? randomUUID();
		this.idleTimeoutMinutes = opts.idleTimeoutMinutes ?? null;
		this.userId = opts.userId ?? null;
		this.deviceId = opts.deviceId ?? agent._defaults.deviceId;
		this.browserSessionId = opts.browserSessionId ?? agent._defaults.browserSessionId;
		this.tags = opts.tags ?? null;
		this.autoFlush = opts.autoFlush ?? isServerless();
		this.trackSessionEnd = opts.trackSessionEnd ?? true;
		this._agent = agent;
		this._sessionReplayId = this.deviceId && this.browserSessionId ? `${this.deviceId}/${this.browserSessionId}` : null;
	}
	_buildSessionContext() {
		const defaults = this._agent._defaults;
		const ai = this._agent._ai;
		const sid = this.sessionId;
		return new SessionContext({
			sessionId: sid,
			traceId: this.traceId,
			userId: this.userId ?? defaults.userId,
			agentId: defaults.agentId,
			parentAgentId: defaults.parentAgentId,
			env: defaults.env,
			customerOrgId: defaults.customerOrgId,
			agentVersion: defaults.agentVersion,
			description: defaults.description,
			context: defaults.context,
			groups: defaults.groups,
			tags: this.tags,
			idleTimeoutMinutes: this.idleTimeoutMinutes,
			deviceId: this.deviceId ?? defaults.deviceId,
			browserSessionId: this.browserSessionId ?? defaults.browserSessionId,
			nextTurnIdFn: () => ai._nextTurnId(sid),
			amplitude: ai.amplitude,
			skipAutoUserTracking: this._skipAutoUserTracking
		});
	}
	newTrace() {
		this.traceId = randomUUID();
		const ctx = getActiveContext();
		if (ctx != null) ctx.traceId = this.traceId;
		return this.traceId;
	}
	setEnrichments(enrichments) {
		this._enrichments = enrichments;
	}
	/**
	* Run a callback within this session context.
	* This is the Node.js equivalent of Python's `with session as s:` block.
	*
	* When {@link autoFlush} is `true` (default in serverless environments),
	* all pending events are flushed before the promise resolves. This
	* prevents event loss when the serverless runtime freezes or terminates
	* after the handler returns.
	*/
	async run(fn) {
		if (this.traceId == null) this.traceId = randomUUID();
		const ctx = this._buildSessionContext();
		try {
			return await _sessionStorage.run(ctx, () => fn(this));
		} finally {
			if (this.trackSessionEnd) this._autoEnd();
			if (this.autoFlush) await this._flush();
		}
	}
	/**
	* Synchronous version of run() for non-async code.
	*
	* Note: `autoFlush` has no effect here because `runSync` cannot await
	* the asynchronous flush.  Use `session.run()` (async) or call
	* `await ai.flush()` manually after `runSync` returns.
	*/
	runSync(fn) {
		if (this.autoFlush) _warnRunSyncAutoFlush();
		if (this.traceId == null) this.traceId = randomUUID();
		const ctx = this._buildSessionContext();
		try {
			return _sessionStorage.run(ctx, () => fn(this));
		} finally {
			if (this.trackSessionEnd) this._autoEnd();
		}
	}
	/**
	* Run a callback as a child agent within this session.
	*
	* Provider wrappers automatically pick up the child agent's identity
	* (`agentId`, `parentAgentId`) while sharing this session's `sessionId`,
	* `traceId`, and turn counter. No `[Agent] Session End` is emitted.
	*
	* @example
	* ```typescript
	* const child = parentAgent.child('researcher');
	* await session.run(async (s) => {
	*   const result = await s.runAs(child, async (cs) => {
	*     // provider wrappers see agentId='researcher'
	*     return openai.chat.completions.create({ ... });
	*   });
	* });
	* ```
	*/
	async runAs(childAgent, fn) {
		const childSession = new Session(childAgent, {
			sessionId: this.sessionId,
			userId: this.userId,
			deviceId: this.deviceId,
			browserSessionId: this.browserSessionId,
			tags: this.tags,
			trackSessionEnd: false
		});
		childSession.traceId = this.traceId;
		childSession._skipAutoUserTracking = true;
		const ctx = childSession._buildSessionContext();
		return await _sessionStorage.run(ctx, () => fn(childSession));
	}
	/**
	* Synchronous version of {@link runAs}.
	*/
	runAsSync(childAgent, fn) {
		const childSession = new Session(childAgent, {
			sessionId: this.sessionId,
			userId: this.userId,
			deviceId: this.deviceId,
			browserSessionId: this.browserSessionId,
			tags: this.tags,
			trackSessionEnd: false
		});
		childSession.traceId = this.traceId;
		childSession._skipAutoUserTracking = true;
		const ctx = childSession._buildSessionContext();
		return _sessionStorage.run(ctx, () => fn(childSession));
	}
	_autoEnd() {
		try {
			const endOpts = {
				sessionId: this.sessionId,
				enrichments: this._enrichments,
				idleTimeoutMinutes: this.idleTimeoutMinutes
			};
			if (this.userId != null) endOpts.userId = this.userId;
			if (this.deviceId != null) endOpts.deviceId = this.deviceId;
			this._agent.trackSessionEnd(this._inject(endOpts));
		} catch (e) {
			getLogger().debug(`Failed to auto-end session ${this.sessionId}: ${e}`);
		}
	}
	async _flush() {
		try {
			const result = this._agent.flush();
			if (result != null) {
				const awaitable = typeof result.promise === "object" ? result.promise : typeof result.then === "function" ? result : null;
				if (awaitable != null) await awaitable;
			}
		} catch (e) {
			getLogger().warn(`Failed to flush after session ${this.sessionId}: ${e}`);
		}
	}
	_inject(kwargs) {
		const merged = { ...kwargs };
		if (merged.sessionId == null) merged.sessionId = this.sessionId;
		if (this.traceId != null && merged.traceId == null) merged.traceId = this.traceId;
		if (this.userId != null && merged.userId == null) merged.userId = this.userId;
		if (this.deviceId != null && merged.deviceId == null) merged.deviceId = this.deviceId;
		if (this.browserSessionId != null && merged.browserSessionId == null) merged.browserSessionId = this.browserSessionId;
		if (this._sessionReplayId != null || this.tags != null) {
			const existingEp = merged.eventProperties;
			const ep = existingEp != null ? { ...existingEp } : {};
			if (this._sessionReplayId != null && !(PROP_SESSION_REPLAY_ID in ep)) ep[PROP_SESSION_REPLAY_ID] = this._sessionReplayId;
			if (this.tags != null && !("[Agent] Tags" in ep)) ep["[Agent] Tags"] = JSON.stringify(this.tags);
			merged.eventProperties = ep;
		}
		return merged;
	}
	trackUserMessage(content, opts = {}) {
		const merged = this._inject(opts);
		if (this.idleTimeoutMinutes != null && merged.idleTimeoutMinutes == null) merged.idleTimeoutMinutes = this.idleTimeoutMinutes;
		return this._agent.trackUserMessage(content, merged);
	}
	trackAiMessage(content, model, provider, latencyMs, opts = {}) {
		return this._agent.trackAiMessage(content, model, provider, latencyMs, this._inject(opts));
	}
	trackToolCall(toolName, latencyMs, success, opts = {}) {
		return this._agent.trackToolCall(toolName, latencyMs, success, this._inject(opts));
	}
	trackEmbedding(model, provider, latencyMs, opts = {}) {
		return this._agent.trackEmbedding(model, provider, latencyMs, this._inject(opts));
	}
	trackSpan(spanName, latencyMs, opts = {}) {
		return this._agent.trackSpan(spanName, latencyMs, this._inject(opts));
	}
	score(name, value, targetId, opts = {}) {
		this._agent.score(name, value, targetId, this._inject(opts));
	}
};

//#endregion
export { Session, _resetRunSyncWarning };
//# sourceMappingURL=session.js.map