import { version } from "../../package.js";
import { loadConfig } from "../config/loader.js";
import { parseAgentSessionKey } from "../routing/session-key-utils.js";
import { init_agent_session_key } from "../routing/agent-session-key.js";
import "../config/index.js";
import { resolveTuiSessionKey, resolveTuiStartupSessionKey } from "../routing/resolve-tui-session-key.js";
import { saveClipboardImageToTempFile } from "./clipboard-image.js";
import { createXopcTuiKeybindingsManager } from "./tui-keybindings-file.js";
import { StreamAssembler } from "./stream-assembler.js";
import { applyThemeById, getBashExcludeBorderColor, getBashModeBorderColor, getThinkingBorderColor, initTuiTheme } from "./theme-manager.js";
import { editorTheme, theme } from "./theme.js";
import { clearPendingToolCallIds, dispatchAgentSSE } from "./tui-agent-events.js";
import { createTuiCommandHandler, getSlashCommands } from "./tui-commands.js";
import { resolveFdPath } from "./tui-fd-path.js";
import { createBackspaceDeduper, drainAndStopTuiSafely, isIgnorableTuiStopError, resolveCtrlCAction, stopTuiSafely } from "./tui-lifecycle.js";
import { createLocalShellRunner } from "./tui-local-shell.js";
import { createOverlayHandlers } from "./tui-overlays.js";
import { filterModelsForCycle, loadScopedModelRefs, saveScopedModelRefs } from "./tui-scoped-models.js";
import { openModelPickerOverlay, openScopedModelsOverlay, openSessionPickerOverlay, openSettingsOverlay } from "./tui-picker-overlay.js";
import { createSessionActions } from "./tui-session-actions.js";
import { loadTuiSettings, saveTuiSettings } from "./tui-settings.js";
import { installTuiStdioFilter } from "./tui-stdio-filter.js";
import { createEditorSubmitHandler, createSubmitBurstCoalescer, shouldEnableWindowsGitBashPasteFallback } from "./tui-submit.js";
import { withTuiSuspended } from "./tui-suspend.js";
import { createInitialState } from "./tui-types.js";
import { EmbeddedBackend } from "./backends/embedded-backend.js";
import { GatewaySseBackend } from "./backends/gateway-sse-backend.js";
import { ChatLog } from "./components/chat-log.js";
import { CustomEditor } from "./components/custom-editor.js";
import { TuiBottomBar } from "./components/tui-bottom-bar.js";
import { TuiHeader } from "./components/tui-header.js";
import { loadExtensionsForTuiLocalMode } from "./extension-host/load-extensions.js";
import { createTuiExtensionRuntime } from "./extension-host/runtime.js";
import { mkdtempSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { spawnSync } from "node:child_process";
import { Container, Loader, ProcessTerminal, TUI, setKeybindings } from "@earendil-works/pi-tui";
//#region src/tui/tui.ts
init_agent_session_key();
const THINK_LEVEL_CYCLE = [
	"off",
	"minimal",
	"low",
	"medium",
	"high",
	"xhigh",
	"adaptive"
];
function nextThinkLevel(current) {
	const c = (current ?? "medium").toLowerCase();
	const idx = THINK_LEVEL_CYCLE.indexOf(c);
	const mediumIdx = THINK_LEVEL_CYCLE.indexOf("medium");
	return THINK_LEVEL_CYCLE[((idx >= 0 ? idx : mediumIdx >= 0 ? mediumIdx : 0) + 1) % THINK_LEVEL_CYCLE.length];
}
const DOUBLE_ESCAPE_WINDOW_MS = 500;
async function runTui(opts) {
	const stdioFilter = installTuiStdioFilter();
	const restoreStdio = () => stdioFilter.restore();
	const isLocalMode = opts.local === true;
	const startup = resolveTuiStartupSessionKey({
		cfg: loadConfig(),
		sessionOption: opts.session,
		cwd: process.cwd()
	});
	let currentAgentId = startup.agentId;
	const sessionScope = startup.sessionScope;
	const sessionMainKey = startup.sessionMainKey;
	const resolveSessionKey = (raw) => resolveTuiSessionKey({
		raw,
		sessionScope,
		currentAgentId,
		sessionMainKey
	});
	let tuiSettings = loadTuiSettings();
	initTuiTheme({ themeId: opts.theme ?? tuiSettings.theme });
	const state = createInitialState(startup.sessionKey);
	state.scopedModelRefs = loadScopedModelRefs();
	state.showThinking = tuiSettings.showThinking;
	state.toolsExpanded = tuiSettings.toolsExpanded;
	const assembler = new StreamAssembler();
	let extensionRegistry;
	if (isLocalMode) extensionRegistry = await loadExtensionsForTuiLocalMode();
	const client = isLocalMode ? new EmbeddedBackend({ extensionRegistry }) : new GatewaySseBackend({
		url: opts.url ?? "http://localhost:3120",
		token: opts.token
	});
	const keybindings = createXopcTuiKeybindingsManager();
	setKeybindings(keybindings);
	let modelChoices = [];
	const cycleModelChoices = () => filterModelsForCycle(modelChoices, state.scopedModelRefs);
	const refreshCycleModels = () => {
		bottomBar.invalidate();
		tui.requestRender();
	};
	const tui = new TUI(new ProcessTerminal());
	const dedupeBackspace = createBackspaceDeduper();
	tui.addInputListener((data) => {
		const next = dedupeBackspace(data);
		if (next.length === 0) return { consume: true };
		return { data: next };
	});
	const header = new TuiHeader(() => ({
		version: version ?? "dev",
		connectionLabel: client.connectionLabel,
		sessionKey: state.currentSessionKey,
		showHints: tuiSettings.showStartupHints
	}));
	const statusContainer = new Container();
	const bottomBar = new TuiBottomBar(() => state, () => opts.thinking);
	const chatLog = new ChatLog();
	chatLog.setToolsExpanded(state.toolsExpanded);
	const editor = new CustomEditor(tui, editorTheme, keybindings);
	const root = new Container();
	root.addChild(header);
	root.addChild(chatLog);
	root.addChild(statusContainer);
	root.addChild(editor);
	root.addChild(bottomBar);
	tui.addChild(root);
	tui.setFocus(editor);
	let isBashMode = false;
	let isBashExcludeContext = false;
	const updateEditorBorderColor = () => {
		if (isBashMode) editor.borderColor = isBashExcludeContext ? getBashExcludeBorderColor() : getBashModeBorderColor();
		else editor.borderColor = getThinkingBorderColor(state.sessionInfo.thinkingLevel ?? opts.thinking ?? "off");
		tui.requestRender();
	};
	editor.onChange = (text) => {
		const trimmed = text.trimStart();
		const nextExclude = trimmed.startsWith("!!");
		const nextBash = trimmed.startsWith("!");
		if (nextBash !== isBashMode || nextExclude !== isBashExcludeContext) {
			isBashMode = nextBash;
			isBashExcludeContext = nextExclude;
			updateEditorBorderColor();
		}
	};
	const { openOverlay, closeOverlay } = createOverlayHandlers(tui, editor);
	const slashCommands = getSlashCommands(isLocalMode);
	const extensionRuntime = createTuiExtensionRuntime({
		registry: extensionRegistry,
		tui,
		chatLog,
		header,
		bottomBar,
		getState: () => state,
		baseSlashCommands: slashCommands,
		cwd: process.cwd(),
		fdPath: resolveFdPath(),
		openOverlay,
		closeOverlay,
		onInvalidate: () => {
			updateHeader();
			updateFooter();
			tui.requestRender();
		}
	});
	editor.setAutocompleteProvider(extensionRuntime.autocompleteProvider);
	let statusLoader = null;
	let statusStartedAt = null;
	let lastActivityStatus = "";
	let elapsedTimerId = null;
	const busyStates = new Set([
		"sending",
		"waiting",
		"streaming",
		"running",
		"compacting"
	]);
	const syncTerminalProgress = () => {
		if (!tuiSettings.showTerminalProgress) {
			tui.terminal.setProgress(false);
			return;
		}
		tui.terminal.setProgress(busyStates.has(state.activityStatus));
	};
	let lastStreamActivityAt = Date.now();
	let streamWatchdogId = null;
	const touchStreamingActivity = () => {
		lastStreamActivityAt = Date.now();
	};
	const formatElapsed = (startMs) => {
		const totalSeconds = Math.max(0, Math.floor((Date.now() - startMs) / 1e3));
		if (totalSeconds < 60) return `${totalSeconds}s`;
		return `${Math.floor(totalSeconds / 60)}m ${totalSeconds % 60}s`;
	};
	const renderStatus = () => {
		if (busyStates.has(state.activityStatus)) {
			if (!statusStartedAt || lastActivityStatus !== state.activityStatus) statusStartedAt = Date.now();
			if (!statusLoader) {
				statusContainer.clear();
				statusLoader = new Loader(tui, (spinner) => theme.accent(spinner), (text) => theme.bold(theme.accentSoft(text)), "");
				statusContainer.addChild(statusLoader);
			}
			const elapsed = formatElapsed(statusStartedAt);
			statusLoader.setMessage(`${state.progressMessage ?? state.activityStatus} • ${elapsed} | ${state.connectionStatus}`);
			if (!elapsedTimerId) elapsedTimerId = setInterval(() => {
				if (statusStartedAt && statusLoader) {
					const el = formatElapsed(statusStartedAt);
					statusLoader.setMessage(`${state.progressMessage ?? state.activityStatus} • ${el} | ${state.connectionStatus}`);
				}
			}, 1e3);
		} else {
			state.progressMessage = null;
			statusStartedAt = null;
			if (elapsedTimerId) {
				clearInterval(elapsedTimerId);
				elapsedTimerId = null;
			}
			statusLoader?.stop();
			statusLoader = null;
			statusContainer.clear();
		}
		lastActivityStatus = state.activityStatus;
		bottomBar.invalidate();
		syncTerminalProgress();
		tui.requestRender();
	};
	const setActivityStatus = (status) => {
		state.activityStatus = status;
		renderStatus();
	};
	const setConnectionStatus = (text) => {
		state.connectionStatus = text;
		renderStatus();
	};
	const updateHeader = () => {
		header.invalidate();
		syncTerminalTitle();
	};
	const syncTerminalTitle = () => {
		const shortKey = state.currentSessionKey.length > 48 ? `${state.currentSessionKey.slice(0, 45)}…` : state.currentSessionKey;
		tui.terminal.setTitle(`xopc · ${shortKey}`);
	};
	const updateFooter = () => {
		bottomBar.invalidate();
	};
	const refreshModelChoices = async () => {
		try {
			modelChoices = await client.listModels();
		} catch {
			modelChoices = [];
		}
		bottomBar.invalidate();
		tui.requestRender();
	};
	let finishTui = null;
	let exitResult = { exitReason: "exit" };
	const requestExit = () => {
		if (state.exitRequested) return;
		state.exitRequested = true;
		if (elapsedTimerId) {
			clearInterval(elapsedTimerId);
			elapsedTimerId = null;
		}
		if (streamWatchdogId) {
			clearInterval(streamWatchdogId);
			streamWatchdogId = null;
		}
		client.stop();
		tui.terminal.setProgress(false);
		drainAndStopTuiSafely(tui).then(() => {
			restoreStdio();
			finishTui?.();
			process.exit(0);
		});
	};
	const { refreshSessionInfo, loadHistory: loadSessionHistory, setSession, abortActive, resetCurrentSession, clearChatForSessionSwitch } = createSessionActions({
		client,
		chatLog,
		tui,
		state,
		assembler,
		resolveSessionKey,
		updateHeader,
		updateFooter,
		setActivityStatus,
		onAgentIdChange: (agentId) => {
			currentAgentId = agentId;
		}
	});
	const refreshSessionInfoWithBorder = async () => {
		await refreshSessionInfo();
		updateEditorBorderColor();
	};
	const resolveModelChoiceIndex = () => {
		const choices = cycleModelChoices();
		if (choices.length === 0) return -1;
		const p = state.sessionInfo.modelProvider;
		const m = state.sessionInfo.model;
		if (p && m) {
			const byParts = choices.findIndex((x) => x.provider === p && x.id === m);
			if (byParts >= 0) return byParts;
		}
		if (m?.includes("/")) {
			const [a, b] = m.split("/", 2);
			const bySlash = choices.findIndex((x) => x.provider === a && x.id === b);
			if (bySlash >= 0) return bySlash;
		}
		return 0;
	};
	const cycleModel = (dir) => {
		const choices = cycleModelChoices();
		if (choices.length === 0) {
			refreshModelChoices().then(() => {
				if (cycleModelChoices().length === 0) {
					chatLog.addSystem("No models available to cycle.");
					tui.requestRender();
					return;
				}
				cycleModel(dir);
			});
			return;
		}
		const idx = resolveModelChoiceIndex();
		const next = choices[((idx >= 0 ? idx : 0) + (dir === "forward" ? 1 : -1) + choices.length) % choices.length];
		sendMessage(`/switch ${next.provider}/${next.id}`);
	};
	const handleCtrlZ = () => {
		if (process.platform === "win32") {
			chatLog.addSystem("Suspend (Ctrl+Z) is not supported on Windows.");
			tui.requestRender();
			return;
		}
		const suspendKeepAlive = setInterval(() => {}, 2 ** 30);
		const ignoreSigint = () => {};
		process.on("SIGINT", ignoreSigint);
		process.once("SIGCONT", () => {
			clearInterval(suspendKeepAlive);
			process.removeListener("SIGINT", ignoreSigint);
			tui.start();
			tui.setFocus(editor);
			tui.requestRender(true);
		});
		try {
			tui.stop();
			process.kill(0, "SIGTSTP");
		} catch {
			clearInterval(suspendKeepAlive);
			process.removeListener("SIGINT", ignoreSigint);
		}
	};
	const openExternalEditor = () => {
		const filePath = join(mkdtempSync(join(tmpdir(), "xopc-tui-edit-")), "message.md");
		writeFileSync(filePath, editor.getText(), "utf8");
		const editorBin = process.env.EDITOR || process.env.VISUAL || "vi";
		(async () => {
			await withTuiSuspended(tui, async () => {
				spawnSync(editorBin, [filePath], { stdio: "inherit" });
			});
			try {
				const next = readFileSync(filePath, "utf8");
				editor.setText(next.replace(/\r\n/g, "\n"));
			} catch {}
			try {
				unlinkSync(filePath);
			} catch {}
			tui.setFocus(editor);
			tui.requestRender(true);
		})();
	};
	const isAgentBusy = () => state.activeRunId != null || state.isCompacting || busyStates.has(state.activityStatus);
	const steerMessage = (text) => {
		chatLog.addUser(text);
		touchStreamingActivity();
		tui.requestRender();
		client.steerChat({
			sessionKey: state.currentSessionKey,
			message: text
		}).then(({ ok }) => {
			if (!ok) chatLog.addSystem(theme.dim("Could not steer — no active run or steer failed. Press Escape to abort, or Alt+Enter to queue a follow-up."));
			else chatLog.addSystem(theme.dim("Steered — message injects at the next tool boundary (pi-style)."));
			tui.requestRender();
		}).catch((error) => {
			const errorMessage = error instanceof Error ? error.message : String(error);
			chatLog.addSystem(theme.dim(`Steer failed: ${errorMessage}`));
			tui.requestRender();
		});
	};
	const sendMessage = (text) => {
		if (state.isCompacting) {
			state.compactionQueue.push(text);
			chatLog.addSystem(theme.dim(`Queued during compaction (${state.compactionQueue.length}). Sends when compact finishes.`));
			bottomBar.invalidate();
			tui.requestRender();
			return;
		}
		if (state.activeRunId) {
			chatLog.addSystem("A response is still in progress. Press Enter to steer, Alt+Enter to queue, or Escape to abort.");
			tui.requestRender();
			return;
		}
		chatLog.addUser(text);
		setActivityStatus("sending");
		touchStreamingActivity();
		tui.requestRender();
		client.sendChat({
			sessionKey: state.currentSessionKey,
			message: text,
			thinking: opts.thinking
		}).catch((error) => {
			const errorMessage = error instanceof Error ? error.message : String(error);
			chatLog.addSystem(`❌ Failed to send: ${errorMessage}`);
			setActivityStatus("idle");
			tui.requestRender();
		});
	};
	const runCompaction = async () => {
		if (state.isCompacting) return;
		state.isCompacting = true;
		setActivityStatus("compacting");
		chatLog.addSystem(theme.dim("Compacting session…"));
		tui.requestRender();
		try {
			const result = await client.compactSession(state.currentSessionKey, { force: true });
			chatLog.addSystem(result.summary ?? (result.compacted ? "Session compacted" : "Nothing to compact"));
			if (result.compacted) {
				assembler.clear();
				clearPendingToolCallIds();
				state.historyLoaded = false;
				await loadSessionHistory();
			}
		} catch (error) {
			const errorMessage = error instanceof Error ? error.message : String(error);
			chatLog.addSystem(`❌ Compaction failed: ${errorMessage}`);
		} finally {
			state.isCompacting = false;
			setActivityStatus("idle");
			await refreshSessionInfoWithBorder();
			const queued = state.compactionQueue.shift();
			if (queued && !state.activeRunId) sendMessage(queued);
			updateFooter();
			tui.requestRender();
		}
	};
	const applyTuiSettings = (settings) => {
		tuiSettings = { ...settings };
		saveTuiSettings(tuiSettings);
		state.showThinking = tuiSettings.showThinking;
		state.toolsExpanded = tuiSettings.toolsExpanded;
		chatLog.setToolsExpanded(tuiSettings.toolsExpanded);
		applyThemeById(tuiSettings.theme);
		header.invalidate();
		updateEditorBorderColor();
		syncTerminalProgress();
		bottomBar.invalidate();
		tui.requestRender();
	};
	const previewTheme = (themeId) => {
		applyThemeById(themeId);
		updateEditorBorderColor();
		tui.requestRender();
	};
	const reloadKeybindings = () => {
		keybindings.reload();
		setKeybindings(keybindings);
		tui.requestRender();
	};
	const uiOverlays = {
		openSessionPicker: () => {},
		openScopedModels: () => {},
		openSettings: () => {},
		reloadKeybindings: () => reloadKeybindings()
	};
	const handleCommand = createTuiCommandHandler({
		state,
		chatLog,
		tui,
		assembler,
		isLocalMode,
		abortActive,
		sendMessage,
		requestExit,
		updateFooter,
		keybindings,
		uiOverlays,
		runCompaction,
		extensionSlashCommands: extensionRuntime.slashCommands,
		currentAgentId,
		setSession,
		resetSession: resetCurrentSession
	});
	const { runLocalShellLine } = createLocalShellRunner({
		chatLog,
		tui,
		editor,
		openOverlay,
		closeOverlay,
		pauseStdioFilter: () => stdioFilter.pause(),
		resumeStdioFilter: () => stdioFilter.resume(),
		runWithInheritedStdio: async (work) => {
			await withTuiSuspended(tui, work);
		}
	});
	const submitBurst = createSubmitBurstCoalescer({
		submit: createEditorSubmitHandler({
			editor,
			handleCommand,
			sendMessage,
			handleBangLine: runLocalShellLine,
			isAgentBusy,
			steerWhileBusy: steerMessage
		}),
		enabled: shouldEnableWindowsGitBashPasteFallback()
	});
	editor.onSubmit = submitBurst;
	const flushFollowUpQueue = () => {
		if (state.exitRequested) return;
		if (state.activeRunId) return;
		const next = state.messageFollowUpQueue.shift();
		if (next === void 0) return;
		sendMessage(next);
	};
	const handleFollowUp = () => {
		const text = editor.getText().trim();
		if (!text) return;
		if (isAgentBusy()) {
			editor.addToHistory(text);
			state.messageFollowUpQueue.push(text);
			editor.setText("");
			chatLog.addSystem(theme.dim(`Queued follow-up (${state.messageFollowUpQueue.length} in queue). Next sends when this reply finishes.`));
			bottomBar.invalidate();
			tui.requestRender();
			return;
		}
		submitBurst(text);
	};
	const handleDequeue = () => {
		if (state.messageFollowUpQueue.length === 0) {
			chatLog.addSystem(theme.dim("No queued messages to restore."));
			tui.requestRender();
			return;
		}
		const queued = [...state.messageFollowUpQueue];
		state.messageFollowUpQueue.length = 0;
		const current = editor.getText().trim();
		const combined = [queued.join("\n\n"), current].filter(Boolean).join("\n\n");
		editor.setText(combined);
		chatLog.addSystem(theme.dim(`Restored ${queued.length} queued message${queued.length > 1 ? "s" : ""} to editor.`));
		bottomBar.invalidate();
		tui.requestRender();
	};
	const setSessionKey = (key) => {
		state.currentSessionKey = resolveSessionKey(key);
		updateAgentFromPicker(key);
	};
	const updateAgentFromPicker = (key) => {
		const parsed = parseAgentSessionKey(resolveSessionKey(key));
		if (parsed?.agentId) currentAgentId = parsed.agentId;
	};
	let ctrlCHandling = false;
	const handleCtrlC = () => {
		if (ctrlCHandling) return;
		ctrlCHandling = true;
		try {
			const now = Date.now();
			const decision = resolveCtrlCAction({
				hasInput: editor.getText().trim().length > 0,
				now,
				lastCtrlCAt: state.lastCtrlCAt
			});
			state.lastCtrlCAt = decision.nextLastCtrlCAt;
			if (decision.action === "clear") {
				editor.setText("");
				setActivityStatus("cleared input; press ctrl+c again to exit");
				tui.requestRender();
				return;
			}
			if (decision.action === "exit") {
				requestExit();
				return;
			}
			setActivityStatus("press ctrl+c again to exit");
			tui.requestRender();
		} finally {
			ctrlCHandling = false;
		}
	};
	const setModelChoices = (models) => {
		modelChoices = models;
	};
	const pickerSvc = {
		tui,
		editor,
		openOverlay,
		closeOverlay,
		chatLog,
		client,
		sendMessage,
		refreshSessionInfo,
		updateHeader,
		state,
		setSessionKey,
		clearChatForSessionSwitch,
		loadSessionHistory,
		setModelChoices,
		getScopedModelRefs: () => state.scopedModelRefs,
		setScopedModelRefs: (refs) => {
			state.scopedModelRefs = refs;
			saveScopedModelRefs(refs);
		},
		refreshCycleModels,
		getTuiSettings: () => ({ ...tuiSettings }),
		applyTuiSettings,
		previewTheme,
		reloadKeybindings
	};
	uiOverlays.openSessionPicker = () => void openSessionPickerOverlay(pickerSvc);
	uiOverlays.openScopedModels = () => void openScopedModelsOverlay(pickerSvc);
	uiOverlays.openSettings = () => openSettingsOverlay(pickerSvc);
	const handleDoubleEscape = () => {
		switch (tuiSettings.doubleEscapeAction) {
			case "tree":
				chatLog.addSystem(theme.dim("Session tree is not available in xopc TUI yet."));
				break;
			case "fork":
				chatLog.addSystem(theme.dim("Session fork is not available in xopc TUI yet."));
				break;
			default: break;
		}
		tui.requestRender();
	};
	editor.onEscape = () => {
		if (state.activeRunId) {
			abortActive();
			return;
		}
		if (editor.getText().trim().length > 0) return;
		const now = Date.now();
		if (now - state.lastEscapeAt <= DOUBLE_ESCAPE_WINDOW_MS) {
			state.lastEscapeAt = 0;
			handleDoubleEscape();
			return;
		}
		state.lastEscapeAt = now;
	};
	editor.onCtrlD = () => requestExit();
	editor.onAction("app.clear", handleCtrlC);
	editor.onAction("app.exit", () => requestExit());
	editor.onAction("app.suspend", handleCtrlZ);
	editor.onAction("app.thinking.cycle", () => {
		sendMessage(`/think ${nextThinkLevel(state.sessionInfo.thinkingLevel ?? opts.thinking ?? "medium")}`);
		updateEditorBorderColor();
	});
	editor.onAction("app.model.cycleForward", () => cycleModel("forward"));
	editor.onAction("app.model.cycleBackward", () => cycleModel("backward"));
	editor.onAction("app.model.select", () => void openModelPickerOverlay(pickerSvc));
	editor.onAction("app.session.resume", () => void openSessionPickerOverlay(pickerSvc));
	editor.onAction("app.tools.expand", () => {
		state.toolsExpanded = !state.toolsExpanded;
		tuiSettings = {
			...tuiSettings,
			toolsExpanded: state.toolsExpanded
		};
		saveTuiSettings(tuiSettings);
		chatLog.setToolsExpanded(state.toolsExpanded);
		setActivityStatus(state.toolsExpanded ? "tools expanded" : "tools collapsed");
		tui.requestRender();
	});
	editor.onAction("app.thinking.toggle", () => {
		state.showThinking = !state.showThinking;
		tuiSettings = {
			...tuiSettings,
			showThinking: state.showThinking
		};
		saveTuiSettings(tuiSettings);
		updateFooter();
		tui.requestRender();
	});
	editor.onAction("app.editor.external", openExternalEditor);
	editor.onPasteImage = () => {
		(async () => {
			const filePath = await saveClipboardImageToTempFile();
			if (!filePath) return;
			editor.insertTextAtCursor(filePath);
			chatLog.addSystem(theme.dim(`Pasted image path: ${filePath}`));
			tui.requestRender();
		})();
	};
	editor.onAction("app.message.followUp", handleFollowUp);
	editor.onAction("app.message.dequeue", handleDequeue);
	streamWatchdogId = setInterval(() => {
		if (!state.activeRunId) return;
		if (!busyStates.has(state.activityStatus)) return;
		if (Date.now() - lastStreamActivityAt < 3e4) return;
		const rid = state.activeRunId;
		const finalText = assembler.finalize(rid, state.showThinking);
		if (finalText) chatLog.finalizeAssistant(finalText, rid);
		chatLog.addSystem("⚠️ No stream activity for 30s; UI reset (connection may have stalled). Retry or check gateway.");
		state.activeRunId = null;
		setActivityStatus("idle");
		refreshSessionInfoWithBorder().finally(() => {
			updateFooter();
			tui.requestRender();
		});
		flushFollowUpQueue();
		tui.requestRender();
	}, 5e3);
	const onAgentRunEnded = () => {
		state.progressMessage = null;
		refreshSessionInfoWithBorder().finally(() => {
			updateFooter();
			tui.requestRender();
		});
		flushFollowUpQueue();
	};
	client.onEvent = (evt) => {
		const data = evt.data ?? {};
		dispatchAgentSSE(evt.event, data, state, chatLog, assembler, tui, setActivityStatus, touchStreamingActivity, onAgentRunEnded);
	};
	client.onConnected = () => {
		state.isConnected = true;
		setConnectionStatus(isLocalMode ? "local ready" : "gateway connected");
		touchStreamingActivity();
		(async () => {
			await refreshSessionInfoWithBorder();
			await refreshModelChoices();
			await loadSessionHistory();
			updateHeader();
			updateFooter();
			tui.requestRender();
			if (!state.autoMessageSent && opts.message) {
				state.autoMessageSent = true;
				sendMessage(opts.message);
			}
		})();
	};
	client.onDisconnected = (reason) => {
		const wasConnected = state.isConnected;
		state.isConnected = false;
		touchStreamingActivity();
		if (isLocalMode) setConnectionStatus(`local stopped: ${reason}`);
		else {
			setConnectionStatus(`disconnected${wasConnected || state.historyLoaded ? ` (${reason}). Reconnecting broadcast stream…` : `. Ensure gateway is running (xopc gateway) or use --local.`}`);
			if (!wasConnected && !state.historyLoaded) {
				const gatewayUrl = opts.url ?? "http://localhost:3120";
				chatLog.addSystem(`Cannot reach gateway at ${gatewayUrl}.\nStart the gateway (\`xopc gateway\`) or run \`xopc tui --local\` for embedded mode.`);
			}
		}
		tui.requestRender();
	};
	client.onGap = (info) => {
		chatLog.addSystem(`⚠️ Event gap: expected ${info.expected}, received ${info.received}. Some updates may be missing.`);
		setConnectionStatus(`event gap: expected ${info.expected}, got ${info.received}`);
		tui.requestRender();
	};
	const sigintHandler = () => handleCtrlC();
	const sigtermHandler = () => requestExit();
	process.on("SIGINT", sigintHandler);
	process.on("SIGTERM", sigtermHandler);
	updateHeader();
	updateEditorBorderColor();
	setConnectionStatus(isLocalMode ? "starting local runtime" : "connecting");
	updateFooter();
	await extensionRuntime.activate();
	tui.start();
	client.start();
	await new Promise((resolve) => {
		finishTui = () => {
			process.removeListener("SIGINT", sigintHandler);
			process.removeListener("SIGTERM", sigtermHandler);
			if (streamWatchdogId) {
				clearInterval(streamWatchdogId);
				streamWatchdogId = null;
			}
			finishTui = null;
			resolve();
		};
	});
	return exitResult;
}
//#endregion
export { createBackspaceDeduper, drainAndStopTuiSafely, isIgnorableTuiStopError, resolveCtrlCAction, runTui, stopTuiSafely, withTuiSuspended };

//# sourceMappingURL=tui.js.map