import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
import { parseModelRef } from "../../agent/models/selection.js";
import { consumeSSEStream, parseSSEData } from "../sse-consumer.js";
//#region src/tui/backends/gateway-sse-backend.ts
init_logger();
const log = createLogger("TUI:GatewaySSE");
/** Fetch wrapper that adds auth headers. */
async function gatewayFetch(baseUrl, path, token, init) {
	const headers = {
		"Content-Type": "application/json",
		...token ? { Authorization: `Bearer ${token}` } : {},
		...init?.headers
	};
	return fetch(`${baseUrl}${path}`, {
		...init,
		headers
	});
}
/**
* TUI backend that communicates with a running xopc gateway via HTTP + SSE.
*
* - Agent streaming: `POST /api/agent` with `Accept: text/event-stream`
* - Broadcast events: `GET /api/events` via long-lived SSE
* - REST calls for sessions, models, etc.
*/
var GatewaySseBackend = class {
	baseUrl;
	token;
	eventAbort = null;
	chatAbort = null;
	onEvent;
	onConnected;
	onDisconnected;
	onGap;
	constructor(opts) {
		this.baseUrl = opts.url.replace(/\/+$/, "");
		this.token = opts.token;
	}
	get connectionLabel() {
		return this.baseUrl;
	}
	start() {
		this.startEventStream();
	}
	stop() {
		this.eventAbort?.abort();
		this.eventAbort = null;
		this.chatAbort?.abort();
		this.chatAbort = null;
	}
	async sendChat(opts) {
		this.chatAbort?.abort();
		this.chatAbort = new AbortController();
		const signal = this.chatAbort.signal;
		const runId = crypto.randomUUID();
		this.onEvent?.({
			event: "status",
			data: {
				status: "started",
				runId
			}
		});
		(async () => {
			try {
				const res = await gatewayFetch(this.baseUrl, "/api/agent", this.token, {
					method: "POST",
					headers: { Accept: "text/event-stream" },
					body: JSON.stringify({
						message: opts.message.trimStart().startsWith("/") ? opts.message : prependEnvelopeTimestamp(opts.message),
						channel: "webchat",
						sessionKey: opts.sessionKey,
						thinking: opts.thinking
					}),
					signal
				});
				if (!res.ok) {
					const body = await res.json().catch(() => ({}));
					this.onEvent?.({
						event: "error",
						data: { content: body.error?.message ?? `Gateway error: ${res.status}` }
					});
					return;
				}
				if ((res.headers.get("Content-Type") ?? "").includes("text/event-stream") && res.body) await consumeSSEStream(res.body, (sseEvent) => {
					if (signal.aborted) return;
					const data = parseSSEData(sseEvent.data);
					if (!data) return;
					this.onEvent?.({
						event: sseEvent.event,
						data
					});
				}, signal);
				else {
					const json = await res.json();
					if (json.ok && json.payload?.content) {
						this.onEvent?.({
							event: "token",
							data: { content: json.payload.content }
						});
						this.onEvent?.({
							event: "result",
							data: { ok: true }
						});
					}
				}
			} catch (error) {
				if (signal.aborted) return;
				const errorMessage = error instanceof Error ? error.message : String(error);
				this.onEvent?.({
					event: "error",
					data: { content: errorMessage }
				});
			}
		})();
		return { runId };
	}
	async abortChat(opts) {
		this.chatAbort?.abort();
		this.chatAbort = null;
		try {
			return { ok: (await (await gatewayFetch(this.baseUrl, "/api/agent/abort", this.token, {
				method: "POST",
				body: JSON.stringify({ runId: opts.runId })
			})).json()).ok ?? false };
		} catch {
			return { ok: false };
		}
	}
	async steerChat(opts) {
		try {
			const res = await gatewayFetch(this.baseUrl, "/api/agent/steer", this.token, {
				method: "POST",
				body: JSON.stringify({
					chatId: opts.sessionKey,
					message: opts.message
				})
			});
			if (!res.ok) return { ok: false };
			return { ok: (await res.json()).ok === true };
		} catch {
			return { ok: false };
		}
	}
	async loadHistory(opts) {
		try {
			const params = new URLSearchParams();
			if (opts.limit) params.set("limit", String(opts.limit));
			const qs = params.toString();
			const res = await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(opts.sessionKey)}/messages${qs ? `?${qs}` : ""}`, this.token);
			if (!res.ok) return { messages: [] };
			return { messages: (await res.json()).payload?.messages ?? [] };
		} catch (error) {
			const errorMessage = error instanceof Error ? error.message : String(error);
			log.warn({
				err: error,
				errorMessage
			}, `Failed to load history: ${errorMessage}`);
			return { messages: [] };
		}
	}
	async listSessions() {
		try {
			const res = await gatewayFetch(this.baseUrl, "/api/sessions", this.token);
			if (!res.ok) return [];
			return ((await res.json()).items ?? []).map((s) => ({
				key: s.key,
				displayName: s.name,
				updatedAt: s.updatedAt ? Date.parse(s.updatedAt) : void 0,
				totalTokens: s.estimatedTokens ?? null,
				messageCount: typeof s.messageCount === "number" ? s.messageCount : void 0,
				model: typeof s.customData?.model === "string" ? s.customData.model : typeof s.customData?.modelRef === "string" ? s.customData.modelRef : null
			}));
		} catch {
			return [];
		}
	}
	async getSessionInfo(sessionKey) {
		const out = {};
		try {
			const sessionPath = `/api/sessions/${encodeURIComponent(sessionKey)}`;
			const [sessionRes, agentCfgRes] = await Promise.all([gatewayFetch(this.baseUrl, sessionPath, this.token), gatewayFetch(this.baseUrl, `${sessionPath}/agent-config`, this.token)]);
			let session;
			if (sessionRes.ok) {
				session = (await sessionRes.json()).session;
				if (session) {
					if (session.name) out.displayName = session.name;
					if (session.estimatedTokens != null) out.totalTokens = session.estimatedTokens;
				}
			}
			if (agentCfgRes.ok) {
				const p = (await agentCfgRes.json()).payload;
				if (p?.model && typeof p.model === "string") {
					const parsed = parseModelRef(p.model);
					if (parsed) {
						out.model = parsed.model;
						out.modelProvider = parsed.provider;
					} else out.model = p.model;
				}
				if (p?.thinkingLevel && typeof p.thinkingLevel === "string") out.thinkingLevel = p.thinkingLevel;
			}
			if (!out.model && session?.customData) {
				const cd = session.customData;
				const ref = typeof cd.model === "string" ? cd.model : typeof cd.modelRef === "string" ? cd.modelRef : void 0;
				if (ref) {
					const parsed = parseModelRef(ref);
					if (parsed) {
						out.model = parsed.model;
						out.modelProvider = parsed.provider;
					} else out.model = ref;
				}
				if (!out.modelProvider && typeof cd.modelProvider === "string") out.modelProvider = cd.modelProvider;
			}
			if (out.totalTokens != null) {
				const contextWindow = (await this.listModels()).find((m) => m.id === out.model && (!out.modelProvider || m.provider === out.modelProvider))?.contextWindow ?? 128e3;
				out.contextWindow = contextWindow;
				out.contextUsagePercent = contextWindow > 0 ? Math.min(100, Math.round(out.totalTokens / contextWindow * 100)) : null;
			}
			return out;
		} catch {
			return {};
		}
	}
	async listModels() {
		try {
			const res = await gatewayFetch(this.baseUrl, "/api/models", this.token);
			if (!res.ok) return [];
			return (await res.json()).payload?.models ?? [];
		} catch {
			return [];
		}
	}
	async resetSession(sessionKey) {
		await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}/reset`, this.token, { method: "POST" }).catch(() => {});
	}
	async compactSession(sessionKey, options) {
		try {
			const res = await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}/compaction/run`, this.token, {
				method: "POST",
				body: JSON.stringify({ force: options?.force ?? true })
			});
			if (!res.ok) return {
				compacted: false,
				summary: `Compaction failed (${res.status})`
			};
			const result = (await res.json()).payload?.result;
			if (!result?.compacted) return {
				compacted: false,
				summary: "Nothing to compact"
			};
			return {
				compacted: true,
				summary: `Compacted (${result.tokensBefore ?? "?"} → ${result.tokensAfter ?? "?"} tokens)`
			};
		} catch (err) {
			return {
				compacted: false,
				summary: err instanceof Error ? err.message : String(err)
			};
		}
	}
	async patchSession(sessionKey, patch) {
		await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}`, this.token, {
			method: "PATCH",
			body: JSON.stringify(patch)
		}).catch(() => {});
	}
	async renameSession(sessionKey, name) {
		try {
			return { ok: (await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}/rename`, this.token, {
				method: "POST",
				body: JSON.stringify({ name })
			})).ok };
		} catch {
			return { ok: false };
		}
	}
	async deleteSession(sessionKey) {
		try {
			const res = await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}`, this.token, { method: "DELETE" });
			if (!res.ok) return { ok: false };
			return { ok: (await res.json()).deleted !== false };
		} catch {
			return { ok: false };
		}
	}
	startEventStream() {
		this.eventAbort?.abort();
		this.eventAbort = new AbortController();
		const url = new URL(`${this.baseUrl}/api/events`);
		if (this.token) url.searchParams.set("token", this.token);
		const connect = async () => {
			try {
				const res = await fetch(url.toString(), {
					signal: this.eventAbort.signal,
					headers: { Accept: "text/event-stream" }
				});
				if (!res.ok || !res.body) {
					this.onDisconnected?.(`event stream error: ${res.status}`);
					this.scheduleReconnect();
					return;
				}
				this.onConnected?.();
				await consumeSSEStream(res.body, (sseEvent) => {
					if (sseEvent.event === "connected") return;
					if (sseEvent.event === "gap") {
						const gapData = parseSSEData(sseEvent.data);
						if (gapData && typeof gapData.expected === "number" && typeof gapData.received === "number") this.onGap?.({
							expected: gapData.expected,
							received: gapData.received
						});
						return;
					}
					const data = parseSSEData(sseEvent.data);
					if (data !== null) this.onEvent?.({
						event: sseEvent.event,
						data
					});
				}, this.eventAbort.signal);
				if (!this.eventAbort?.signal.aborted) {
					this.onDisconnected?.("stream closed");
					this.scheduleReconnect();
				}
			} catch (error) {
				if (this.eventAbort?.signal.aborted) return;
				const errorMessage = error instanceof Error ? error.message : String(error);
				log.warn({
					err: error,
					errorMessage
				}, `Event stream failed: ${errorMessage}`);
				this.onDisconnected?.(errorMessage);
				this.scheduleReconnect();
			}
		};
		connect();
	}
	scheduleReconnect() {
		if (this.eventAbort?.signal.aborted) return;
		setTimeout(() => {
			if (!this.eventAbort?.signal.aborted) this.startEventStream();
		}, 3e3);
	}
};
//#endregion
export { GatewaySseBackend };

//# sourceMappingURL=gateway-sse-backend.js.map