import { getLogger } from "../utils/logger.js";
import { PROP_IDLE_TIMEOUT_MINUTES, PROP_SESSION_REPLAY_ID } from "../core/constants.js";
import { trackAiMessage, trackUserMessage } from "../core/tracking.js";
import { getActiveContext, isTrackerManaged } from "../context.js";
import { StreamingAccumulator } from "../utils/streaming.js";
import { recordToolUsesFromResponse } from "../utils/tool-latency.js";
import { GENAI_ERROR_TYPE, GENAI_FINISH_REASONS, GENAI_INPUT_MESSAGES, GENAI_INPUT_TOKENS, GENAI_OPERATION_NAME, GENAI_OUTPUT_MESSAGES, GENAI_OUTPUT_TOKENS, GENAI_PROVIDER_NAME, GENAI_REQUEST_MAX_TOKENS, GENAI_REQUEST_MODEL, GENAI_REQUEST_TEMPERATURE, GENAI_REQUEST_TOP_P, GENAI_RESPONSE_MODEL, OP_CHAT } from "../otel/conventions.js";
import { resolveAmplitude } from "../types.js";
import { createRequire } from "node:module";

//#region src/providers/base.ts
/**
* Base class for AI provider wrappers.
*
* Provides shared tracking logic and session context integration.
*/
const _require = createRequire(import.meta.url);
/**
* Apply session context fields from AsyncLocalStorage to tracking options.
* Returns a merged set of fields with explicit values taking precedence.
* Also injects idle_timeout_minutes and session_replay_id from the context.
*/
function applySessionContext(overrides = {}) {
	const ctx = getActiveContext();
	const result = { ...overrides };
	if (ctx != null) {
		if (!result.userId) result.userId = ctx.userId;
		if (!result.deviceId) result.deviceId = ctx.deviceId;
		if (!result.sessionId) result.sessionId = ctx.sessionId;
		if (!result.traceId) result.traceId = ctx.traceId;
		if (!result.agentId) result.agentId = ctx.agentId;
		if (!result.parentAgentId) result.parentAgentId = ctx.parentAgentId;
		if (!result.customerOrgId) result.customerOrgId = ctx.customerOrgId;
		if (!result.agentVersion) result.agentVersion = ctx.agentVersion;
		if (!result.description) result.description = ctx.description;
		if (!result.context) result.context = ctx.context;
		if (!result.env) result.env = ctx.env;
		if (!result.groups) result.groups = ctx.groups;
		if (result.browserSessionId == null && ctx.browserSessionId != null) result.browserSessionId = ctx.browserSessionId;
		if (result.turnId == null) {
			const turnId = ctx.nextTurnId();
			if (turnId != null) result.turnId = turnId;
		}
		const existingEp = result.eventProperties;
		const ep = existingEp != null ? { ...existingEp } : {};
		if (ctx.idleTimeoutMinutes != null && !(PROP_IDLE_TIMEOUT_MINUTES in ep)) ep[PROP_IDLE_TIMEOUT_MINUTES] = ctx.idleTimeoutMinutes;
		if (ctx.deviceId && ctx.browserSessionId && !(PROP_SESSION_REPLAY_ID in ep)) ep[PROP_SESSION_REPLAY_ID] = `${ctx.deviceId}/${ctx.browserSessionId}`;
		if (Object.keys(ep).length > 0) result.eventProperties = ep;
	}
	return result;
}
function contextFields(ctx) {
	return {
		userId: ctx.userId ?? void 0,
		deviceId: ctx.deviceId ?? void 0,
		sessionId: ctx.sessionId,
		traceId: ctx.traceId,
		turnId: ctx.turnId ?? void 0,
		agentId: ctx.agentId,
		parentAgentId: ctx.parentAgentId,
		customerOrgId: ctx.customerOrgId,
		agentVersion: ctx.agentVersion,
		description: ctx.description,
		context: ctx.context,
		env: ctx.env,
		groups: ctx.groups,
		eventProperties: ctx.eventProperties,
		browserSessionId: ctx.browserSessionId
	};
}
function _getOtelTracer() {
	try {
		const provider = _require("@opentelemetry/api").trace.getTracerProvider();
		const delegateName = provider._delegate?.constructor?.name;
		const providerName = provider.constructor?.name;
		if (delegateName === "BasicTracerProvider" || delegateName === "NodeTracerProvider" || providerName === "BasicTracerProvider" || providerName === "NodeTracerProvider") return provider.getTracer?.("@amplitude/ai") ?? null;
		return null;
	} catch {
		return null;
	}
}
var BaseAIProvider = class {
	_amplitude;
	_privacyConfig;
	_providerName;
	constructor(options) {
		this._amplitude = resolveAmplitude(options.amplitude);
		this._privacyConfig = options.privacyConfig ?? null;
		this._providerName = options.providerName;
	}
	_track(opts) {
		if (isTrackerManaged()) return "";
		const merged = applySessionContext({
			userId: opts.userId,
			deviceId: opts.deviceId,
			sessionId: opts.sessionId,
			traceId: opts.traceId,
			turnId: opts.turnId,
			agentId: opts.agentId,
			parentAgentId: opts.parentAgentId,
			customerOrgId: opts.customerOrgId,
			agentVersion: opts.agentVersion,
			description: opts.description,
			context: opts.context,
			env: opts.env,
			groups: opts.groups,
			eventProperties: opts.eventProperties,
			browserSessionId: opts.browserSessionId
		});
		const tracer = _getOtelTracer();
		if (tracer != null) {
			try {
				const spanAttrs = {
					[GENAI_OPERATION_NAME]: OP_CHAT,
					[GENAI_REQUEST_MODEL]: opts.modelName,
					[GENAI_RESPONSE_MODEL]: opts.modelName,
					[GENAI_PROVIDER_NAME]: opts.provider
				};
				if (opts.inputTokens != null) spanAttrs[GENAI_INPUT_TOKENS] = opts.inputTokens;
				if (opts.outputTokens != null) spanAttrs[GENAI_OUTPUT_TOKENS] = opts.outputTokens;
				if (opts.temperature != null) spanAttrs[GENAI_REQUEST_TEMPERATURE] = opts.temperature;
				if (opts.maxOutputTokens != null) spanAttrs[GENAI_REQUEST_MAX_TOKENS] = opts.maxOutputTokens;
				if (opts.topP != null) spanAttrs[GENAI_REQUEST_TOP_P] = opts.topP;
				const inputMsgs = opts.inputMessages;
				if (inputMsgs != null) spanAttrs[GENAI_INPUT_MESSAGES] = JSON.stringify(inputMsgs);
				if (opts.responseContent != null) spanAttrs[GENAI_OUTPUT_MESSAGES] = JSON.stringify([{
					role: "assistant",
					content: opts.responseContent
				}]);
				if (opts.finishReason != null) spanAttrs[GENAI_FINISH_REASONS] = [opts.finishReason];
				if (opts.latencyMs != null) spanAttrs["amplitude.latency_ms"] = opts.latencyMs;
				if (opts.isError && opts.errorType != null) spanAttrs[GENAI_ERROR_TYPE] = opts.errorType;
				else if (opts.isError && opts.errorMessage != null) spanAttrs[GENAI_ERROR_TYPE] = opts.errorMessage;
				const span = tracer.startSpan(`${opts.provider}.${OP_CHAT}`, { attributes: spanAttrs });
				if (opts.isError) span.setStatus({
					code: 2,
					message: opts.errorMessage ?? "error"
				});
				span.end();
			} catch (e) {
				getLogger().debug(`Failed to create OTEL span for provider wrapper: ${e}`);
			}
			return "";
		}
		return trackAiMessage({
			...opts,
			amplitude: this._amplitude,
			userId: merged.userId ?? opts.userId,
			deviceId: merged.deviceId ?? opts.deviceId,
			sessionId: merged.sessionId ?? opts.sessionId,
			traceId: merged.traceId ?? opts.traceId,
			turnId: merged.turnId ?? opts.turnId,
			agentId: merged.agentId ?? opts.agentId,
			parentAgentId: merged.parentAgentId ?? opts.parentAgentId,
			customerOrgId: merged.customerOrgId ?? opts.customerOrgId,
			agentVersion: merged.agentVersion ?? opts.agentVersion,
			description: merged.description ?? opts.description,
			context: merged.context ?? opts.context,
			env: merged.env ?? opts.env,
			groups: merged.groups ?? opts.groups,
			eventProperties: merged.eventProperties ?? opts.eventProperties,
			browserSessionId: merged.browserSessionId ?? opts.browserSessionId,
			privacyConfig: this._privacyConfig
		});
	}
	trackFn() {
		return (opts) => this._track(opts);
	}
	createStreamingTracker() {
		return new SimpleStreamingTracker(this);
	}
	/** @internal Accessor for SimpleStreamingTracker. */
	_amplitudeClient() {
		return this._amplitude;
	}
	/** @internal Accessor for SimpleStreamingTracker. */
	_privacyConfigRef() {
		return this._privacyConfig;
	}
};
var SimpleStreamingTracker = class {
	_trackFn;
	_amplitude;
	_privacyConfig;
	accumulator;
	_modelName = "unknown";
	_providerName;
	_inputMessages = [];
	_autoUserTracked = false;
	_skipAutoUserTracking = false;
	constructor(provider) {
		this._trackFn = provider.trackFn();
		this._amplitude = provider._amplitudeClient();
		this._privacyConfig = provider._privacyConfigRef();
		this._providerName = provider._providerName;
		this.accumulator = new StreamingAccumulator();
	}
	/**
	* Hand the tracker the request's input conversation so that
	* {@link SimpleStreamingTracker.finalize} emits
	* `trackUserMessage` events for any new user-role messages
	* (those appearing after the last assistant reply). Matches the
	* behavior of the provider wrappers' `_trackInputMessages()`.
	*
	* Pass `{ skipAuto: true }` when the caller is already emitting
	* user-message events themselves.
	*/
	setInputMessages(messages, options = {}) {
		this._inputMessages = Array.isArray(messages) ? messages : [];
		if (options.skipAuto) this._skipAutoUserTracking = true;
	}
	setModel(model) {
		this._modelName = model;
		this.accumulator.model = model;
	}
	addContent(chunk) {
		this.accumulator.addContent(chunk);
	}
	setUsage(usage) {
		this.accumulator.setUsage(usage);
	}
	setFinishReason(reason) {
		this.accumulator.finishReason = reason;
	}
	addToolCall(toolCall) {
		this.accumulator.addToolCall(toolCall);
	}
	finalize(overrides = {}) {
		if (isTrackerManaged()) return "";
		const state = this.accumulator.getState();
		const ctx = applySessionContext(overrides);
		const tracer = _getOtelTracer();
		if (tracer != null) {
			try {
				const spanAttrs = {
					[GENAI_OPERATION_NAME]: OP_CHAT,
					[GENAI_REQUEST_MODEL]: this._modelName,
					[GENAI_RESPONSE_MODEL]: this._modelName,
					[GENAI_PROVIDER_NAME]: this._providerName
				};
				if (state.inputTokens != null) spanAttrs[GENAI_INPUT_TOKENS] = state.inputTokens;
				if (state.outputTokens != null) spanAttrs[GENAI_OUTPUT_TOKENS] = state.outputTokens;
				if (state.finishReason != null) spanAttrs[GENAI_FINISH_REASONS] = [state.finishReason];
				if (this._inputMessages.length > 0) spanAttrs[GENAI_INPUT_MESSAGES] = JSON.stringify(this._inputMessages);
				if (state.content) spanAttrs[GENAI_OUTPUT_MESSAGES] = JSON.stringify([{
					role: "assistant",
					content: state.content
				}]);
				if (this.accumulator.elapsedMs > 0) spanAttrs["amplitude.latency_ms"] = this.accumulator.elapsedMs;
				tracer.startSpan(`${this._providerName}.${OP_CHAT}`, { attributes: spanAttrs }).end();
			} catch (e) {
				getLogger().debug(`Failed to create OTEL span for streaming: ${e}`);
			}
			recordToolUsesFromResponse(state.toolCalls, {
				sessionId: ctx.sessionId,
				agentId: ctx.agentId
			});
			return "";
		}
		const eventId = this._trackFn({
			...contextFields(ctx),
			modelName: this._modelName,
			provider: this._providerName,
			responseContent: state.content,
			latencyMs: this.accumulator.elapsedMs,
			inputTokens: state.inputTokens,
			outputTokens: state.outputTokens,
			totalTokens: state.totalTokens,
			reasoningTokens: state.reasoningTokens,
			cacheReadInputTokens: state.cacheReadTokens,
			cacheCreationInputTokens: state.cacheCreationTokens,
			finishReason: state.finishReason,
			toolCalls: state.toolCalls.length > 0 ? state.toolCalls : null,
			providerTtfbMs: state.ttfbMs,
			isStreaming: true
		});
		recordToolUsesFromResponse(state.toolCalls, {
			sessionId: ctx.sessionId,
			agentId: ctx.agentId
		});
		const activeCtx = getActiveContext();
		if (!this._skipAutoUserTracking && !activeCtx?.skipAutoUserTracking && !this._autoUserTracked && (ctx.userId != null || ctx.deviceId != null) && ctx.sessionId != null && this._inputMessages.length > 0) {
			this._autoUserTracked = true;
			this._emitAutoUserMessages(ctx);
		}
		return eventId;
	}
	/**
	* Track each new user-role message in the input conversation.
	* Mirrors the provider wrappers' `_trackInputMessages()` logic —
	* only messages appearing after the last assistant / tool reply
	* are emitted, so repeat turns don't double-track.
	*/
	_emitAutoUserMessages(ctx) {
		const msgs = this._inputMessages;
		const lastReplyIdx = msgs.findLastIndex((m) => {
			const role = m?.role;
			return role === "assistant" || role === "tool";
		});
		const fresh = msgs.slice(lastReplyIdx + 1);
		for (const msg of fresh) {
			if (msg?.role !== "user") continue;
			const raw = msg.content;
			let content = "";
			if (typeof raw === "string") content = raw;
			else if (Array.isArray(raw)) {
				const hasToolResult = raw.some((p) => p != null && typeof p === "object" && (p.type === "tool_result" || p.type === "function_call_output"));
				const hasText = raw.some((p) => p != null && typeof p === "object" && typeof p.text === "string");
				if (hasToolResult && !hasText) continue;
				content = raw.map((p) => {
					if (typeof p === "string") return p;
					const text = p?.text;
					return typeof text === "string" ? text : "";
				}).join("");
			}
			if (!content) continue;
			trackUserMessage({
				amplitude: this._amplitude,
				userId: ctx.userId ?? void 0,
				deviceId: ctx.deviceId ?? void 0,
				messageContent: content,
				sessionId: ctx.sessionId,
				traceId: ctx.traceId,
				turnId: ctx.turnId ?? void 0,
				messageSource: ctx.parentAgentId ? "agent" : "user",
				agentId: ctx.agentId,
				parentAgentId: ctx.parentAgentId,
				customerOrgId: ctx.customerOrgId,
				agentVersion: ctx.agentVersion,
				context: ctx.context,
				env: ctx.env,
				groups: ctx.groups,
				eventProperties: ctx.eventProperties,
				browserSessionId: ctx.browserSessionId,
				privacyConfig: this._privacyConfig
			});
		}
	}
};

//#endregion
export { BaseAIProvider, SimpleStreamingTracker, applySessionContext, contextFields };
//# sourceMappingURL=base.js.map