import type { OverlayHandle } from "@mariozechner/pi-tui";
import type { HeadlessDispatchMonitor } from "./headless-monitor.js";
import type { MonitorConfig, MonitorEventPayload, MonitorSessionState, MonitorTerminalReason } from "./types.js";

const MONITOR_HISTORY_LIMIT = 200;

/** Centralizes overlay, monitor, widget, and completion-suppression state for the extension runtime. */
export class InteractiveShellCoordinator {
	private overlayOpen = false;
	private overlayHandle: OverlayHandle | null = null;
	private headlessMonitors = new Map<string, HeadlessDispatchMonitor>();
	private monitorEventHistory = new Map<string, MonitorEventPayload[]>();
	private monitorEventCounters = new Map<string, number>();
	private monitorSessionState = new Map<string, MonitorSessionState>();
	private pendingMonitorReason = new Map<string, MonitorTerminalReason>();
	private bgWidgetCleanup: (() => void) | null = null;
	private agentHandledCompletion = new Set<string>();

	isOverlayOpen(): boolean {
		return this.overlayOpen;
	}

	beginOverlay(): boolean {
		if (this.overlayOpen) return false;
		this.overlayOpen = true;
		return true;
	}

	endOverlay(): void {
		this.overlayOpen = false;
		this.clearOverlayHandle();
	}

	focusOverlay(): void {
		this.overlayHandle?.focus();
	}

	unfocusOverlay(): void {
		this.overlayHandle?.unfocus();
	}

	isOverlayFocused(): boolean {
		return this.overlayHandle?.isFocused() === true;
	}

	setOverlayHandle(handle: OverlayHandle): void {
		this.overlayHandle = handle;
	}

	clearOverlayHandle(): void {
		this.overlayHandle = null;
	}

	markAgentHandledCompletion(sessionId: string): void {
		this.agentHandledCompletion.add(sessionId);
	}

	consumeAgentHandledCompletion(sessionId: string): boolean {
		const had = this.agentHandledCompletion.has(sessionId);
		this.agentHandledCompletion.delete(sessionId);
		return had;
	}

	setMonitor(id: string, monitor: HeadlessDispatchMonitor): void {
		this.headlessMonitors.set(id, monitor);
	}

	getMonitor(id: string): HeadlessDispatchMonitor | undefined {
		return this.headlessMonitors.get(id);
	}

	deleteMonitor(id: string): void {
		this.headlessMonitors.delete(id);
	}

	registerMonitorSession(sessionId: string, monitor: MonitorConfig, startedAt: Date): MonitorSessionState {
		const state: MonitorSessionState = {
			sessionId,
			strategy: monitor.strategy ?? "stream",
			triggerIds: monitor.triggers.map((trigger) => trigger.id),
			status: "running",
			eventCount: 0,
			startedAt: startedAt.toISOString(),
		};
		this.monitorSessionState.set(sessionId, state);
		return state;
	}

	markMonitorStopping(sessionId: string, reason: MonitorTerminalReason = "stopped"): void {
		this.pendingMonitorReason.set(sessionId, reason);
	}

	consumePendingMonitorReason(sessionId: string): MonitorTerminalReason | undefined {
		const reason = this.pendingMonitorReason.get(sessionId);
		this.pendingMonitorReason.delete(sessionId);
		return reason;
	}

	finalizeMonitorSession(
		sessionId: string,
		result: { exitCode: number | null; signal?: number },
		reason: MonitorTerminalReason,
	): MonitorSessionState | undefined {
		const current = this.monitorSessionState.get(sessionId);
		if (!current) return undefined;
		const finalized: MonitorSessionState = {
			...current,
			status: "stopped",
			endedAt: new Date().toISOString(),
			terminalReason: reason,
			exitCode: result.exitCode,
			signal: result.signal,
		};
		this.monitorSessionState.set(sessionId, finalized);
		this.pendingMonitorReason.delete(sessionId);
		return finalized;
	}

	getMonitorSessionState(sessionId: string): MonitorSessionState | undefined {
		return this.monitorSessionState.get(sessionId);
	}

	recordMonitorEvent(event: Omit<MonitorEventPayload, "eventId" | "timestamp">): MonitorEventPayload {
		const nextId = (this.monitorEventCounters.get(event.sessionId) ?? 0) + 1;
		this.monitorEventCounters.set(event.sessionId, nextId);

		const recorded: MonitorEventPayload = {
			...event,
			eventId: nextId,
			timestamp: new Date().toISOString(),
		};

		const existing = this.monitorEventHistory.get(event.sessionId) ?? [];
		existing.push(recorded);
		if (existing.length > MONITOR_HISTORY_LIMIT) {
			existing.splice(0, existing.length - MONITOR_HISTORY_LIMIT);
		}
		this.monitorEventHistory.set(event.sessionId, existing);

		const currentState = this.monitorSessionState.get(event.sessionId);
		if (currentState) {
			this.monitorSessionState.set(event.sessionId, {
				...currentState,
				eventCount: nextId,
				lastEventId: recorded.eventId,
				lastEventAt: recorded.timestamp,
				lastTriggerId: recorded.triggerId,
			});
		}

		return recorded;
	}

	getMonitorEvents(sessionId: string, options?: { limit?: number; offset?: number; sinceEventId?: number; triggerId?: string }): {
		events: MonitorEventPayload[];
		total: number;
		limit: number;
		offset: number;
		sinceEventId?: number;
		triggerId?: string;
	} {
		let events = this.monitorEventHistory.get(sessionId) ?? [];
		const sinceEventId = options?.sinceEventId !== undefined ? Math.max(0, Math.trunc(options.sinceEventId)) : undefined;
		if (sinceEventId !== undefined) {
			events = events.filter((event) => event.eventId > sinceEventId);
		}
		const triggerId = options?.triggerId?.trim();
		if (triggerId) {
			events = events.filter((event) => event.triggerId === triggerId);
		}
		const total = events.length;
		const limit = Math.max(1, Math.trunc(options?.limit ?? 20));
		const offset = Math.max(0, Math.trunc(options?.offset ?? 0));
		const end = Math.max(0, total - offset);
		const start = Math.max(0, end - limit);
		return {
			events: events.slice(start, end),
			total,
			limit,
			offset,
			sinceEventId,
			triggerId,
		};
	}

	clearMonitorEvents(sessionId: string): void {
		this.monitorEventHistory.delete(sessionId);
		this.monitorEventCounters.delete(sessionId);
		this.monitorSessionState.delete(sessionId);
		this.pendingMonitorReason.delete(sessionId);
	}

	disposeMonitor(id: string): void {
		const monitor = this.headlessMonitors.get(id);
		if (!monitor) return;
		monitor.dispose();
		this.headlessMonitors.delete(id);
	}

	disposeAllMonitors(): void {
		for (const monitor of this.headlessMonitors.values()) {
			monitor.dispose();
		}
		this.headlessMonitors.clear();
	}

	replaceBackgroundWidgetCleanup(cleanup: (() => void) | null): void {
		this.bgWidgetCleanup?.();
		this.bgWidgetCleanup = cleanup;
	}

	clearBackgroundWidget(): void {
		this.bgWidgetCleanup?.();
		this.bgWidgetCleanup = null;
	}
}
