import { PtyTerminalSession } from "./pty-session.js";

export interface BackgroundSession {
	id: string;
	name: string;
	command: string;
	reason?: string;
	session: PtyTerminalSession;
	startedAt: Date;
}

export type ActiveSessionStatus = "running" | "monitoring" | "user-takeover" | "exited" | "killed" | "backgrounded";

export interface ActiveSessionResult {
	exitCode: number | null;
	signal?: number;
	backgrounded?: boolean;
	backgroundId?: string;
	cancelled?: boolean;
	timedOut?: boolean;
}

export interface OutputResult {
	output: string;
	truncated: boolean;
	totalBytes: number;
	// For incremental/offset modes
	totalLines?: number;
	hasMore?: boolean;
	// Rate limiting
	rateLimited?: boolean;
	waitSeconds?: number;
}

export interface OutputOptions {
	skipRateLimit?: boolean;
	lines?: number; // Override default 20 lines
	maxChars?: number; // Override default 5KB
	offset?: number; // Line offset for pagination (0-indexed)
	drain?: boolean; // If true, return only NEW output since last query (raw stream)
	incremental?: boolean; // If true, return next N lines not yet seen (server tracks position)
}

export interface ActiveSession {
	id: string;
	command: string;
	reason?: string;
	write: (data: string) => void;
	kill: () => void;
	background: () => void;
	getOutput: (options?: OutputOptions | boolean) => OutputResult;
	getStatus: () => ActiveSessionStatus;
	getRuntime: () => number;
	getResult: () => ActiveSessionResult | undefined;
	setUpdateInterval?: (intervalMs: number) => void;
	setQuietThreshold?: (thresholdMs: number) => void;
	onComplete: (callback: () => void) => void;
}

// Human-readable session slug generation
const SLUG_ADJECTIVES = [
	"amber", "brisk", "calm", "clear", "cool", "crisp", "dawn", "ember",
	"fast", "fresh", "gentle", "keen", "kind", "lucky", "mellow", "mild",
	"neat", "nimble", "nova", "quick", "quiet", "rapid", "sharp", "swift",
	"tender", "tidy", "vivid", "warm", "wild", "young",
];

const SLUG_NOUNS = [
	"atlas", "bloom", "breeze", "cedar", "cloud", "comet", "coral", "cove",
	"crest", "delta", "dune", "ember", "falcon", "fjord", "glade", "haven",
	"kelp", "lagoon", "meadow", "mist", "nexus", "orbit", "pine", "reef",
	"ridge", "river", "sage", "shell", "shore", "summit", "trail", "zephyr",
];

function randomChoice<T>(arr: T[]): T {
	return arr[Math.floor(Math.random() * arr.length)];
}

// Track used IDs to avoid collisions
const usedIds = new Set<string>();

export function generateSessionId(name?: string): string {
	// If a custom name is provided, use simple counter approach
	if (name) {
		let counter = 1;
		let id = name;
		while (usedIds.has(id)) {
			counter++;
			id = `${name}-${counter}`;
		}
		usedIds.add(id);
		return id;
	}

	// Generate human-readable slug
	for (let attempt = 0; attempt < 20; attempt++) {
		const adj = randomChoice(SLUG_ADJECTIVES);
		const noun = randomChoice(SLUG_NOUNS);
		const base = `${adj}-${noun}`;

		if (!usedIds.has(base)) {
			usedIds.add(base);
			return base;
		}

		// Try with suffix
		for (let i = 2; i <= 9; i++) {
			const candidate = `${base}-${i}`;
			if (!usedIds.has(candidate)) {
				usedIds.add(candidate);
				return candidate;
			}
		}
	}

	// Fallback: timestamp-based
	const fallback = `shell-${Date.now().toString(36)}`;
	usedIds.add(fallback);
	return fallback;
}

export function releaseSessionId(id: string): void {
	usedIds.delete(id);
}

// Derive a friendly display name from command (e.g., "pi Fix all bugs" -> "pi Fix all bugs")
function deriveSessionName(command: string): string {
	const trimmed = command.trim();
	if (trimmed.length <= 60) return trimmed;

	// Truncate with ellipsis
	return trimmed.slice(0, 57) + "...";
}

export class ShellSessionManager {
	private sessions = new Map<string, BackgroundSession>();
	private exitWatchers = new Map<string, NodeJS.Timeout>();
	private cleanupTimers = new Map<string, NodeJS.Timeout>();
	private activeSessions = new Map<string, ActiveSession>();
	private changeListeners = new Set<() => void>();

	onChange(listener: () => void): () => void {
		this.changeListeners.add(listener);
		return () => { this.changeListeners.delete(listener); };
	}

	private notifyChange(): void {
		for (const listener of this.changeListeners) {
			try {
				listener();
			} catch (error) {
				console.error("interactive-shell: change listener error:", error);
			}
		}
	}

	registerActive(session: ActiveSession): void {
		this.activeSessions.set(session.id, session);
	}

	unregisterActive(id: string, releaseId = false): void {
		this.activeSessions.delete(id);
		// Only release the ID if explicitly requested (when session fully terminates)
		// This prevents ID reuse while session is still running after takeover
		if (releaseId) {
			releaseSessionId(id);
		}
	}

	getActive(id: string): ActiveSession | undefined {
		return this.activeSessions.get(id);
	}

	writeToActive(id: string, data: string): boolean {
		const session = this.activeSessions.get(id);
		if (!session) return false;
		session.write(data);
		return true;
	}

	setActiveUpdateInterval(id: string, intervalMs: number): boolean {
		const session = this.activeSessions.get(id);
		if (!session?.setUpdateInterval) return false;
		session.setUpdateInterval(intervalMs);
		return true;
	}

	setActiveQuietThreshold(id: string, thresholdMs: number): boolean {
		const session = this.activeSessions.get(id);
		if (!session?.setQuietThreshold) return false;
		session.setQuietThreshold(thresholdMs);
		return true;
	}

	add(command: string, session: PtyTerminalSession, name?: string, reason?: string, options?: { id?: string; noAutoCleanup?: boolean; startedAt?: Date }): string {
		const id = options?.id ?? generateSessionId(name);
		if (options?.id) usedIds.add(id);
		const entry: BackgroundSession = {
			id,
			name: name || deriveSessionName(command),
			command,
			reason,
			session,
			startedAt: options?.startedAt ?? new Date(),
		};

		this.storeBackgroundEntry(entry, options?.noAutoCleanup === true);
		return id;
	}

	restore(entry: BackgroundSession, options?: { noAutoCleanup?: boolean }): void {
		usedIds.add(entry.id);
		this.storeBackgroundEntry(entry, options?.noAutoCleanup === true);
	}

	private storeBackgroundEntry(entry: BackgroundSession, noAutoCleanup: boolean): void {
		this.sessions.set(entry.id, entry);
		entry.session.setEventHandlers({});

		if (!noAutoCleanup) {
			const checkExit = setInterval(() => {
				if (entry.session.exited) {
					clearInterval(checkExit);
					this.exitWatchers.delete(entry.id);
					this.notifyChange();
					const cleanupTimer = setTimeout(() => {
						this.cleanupTimers.delete(entry.id);
						this.remove(entry.id);
					}, 30000);
					this.cleanupTimers.set(entry.id, cleanupTimer);
				}
			}, 1000);
			this.exitWatchers.set(entry.id, checkExit);
		}

		this.notifyChange();
	}

	take(id: string): BackgroundSession | undefined {
		const watcher = this.exitWatchers.get(id);
		if (watcher) {
			clearInterval(watcher);
			this.exitWatchers.delete(id);
		}
		const cleanupTimer = this.cleanupTimers.get(id);
		if (cleanupTimer) {
			clearTimeout(cleanupTimer);
			this.cleanupTimers.delete(id);
		}
		const session = this.sessions.get(id);
		if (session) {
			this.sessions.delete(id);
			this.notifyChange();
			return session;
		}
		return undefined;
	}

	get(id: string): BackgroundSession | undefined {
		// Suspend all auto-cleanup while session is being actively used
		const watcher = this.exitWatchers.get(id);
		if (watcher) {
			clearInterval(watcher);
			this.exitWatchers.delete(id);
		}
		const cleanupTimer = this.cleanupTimers.get(id);
		if (cleanupTimer) {
			clearTimeout(cleanupTimer);
			this.cleanupTimers.delete(id);
		}
		return this.sessions.get(id);
	}

	restartAutoCleanup(id: string): void {
		if (this.exitWatchers.has(id)) return;
		const entry = this.sessions.get(id);
		if (!entry) return;
		if (entry.session.exited) {
			this.scheduleCleanup(id);
			return;
		}
		const checkExit = setInterval(() => {
			if (entry.session.exited) {
				clearInterval(checkExit);
				this.exitWatchers.delete(id);
				this.notifyChange();
				this.scheduleCleanup(id);
			}
		}, 1000);
		this.exitWatchers.set(id, checkExit);
	}

	scheduleCleanup(id: string, delayMs = 30000): void {
		if (this.cleanupTimers.has(id)) return;
		const timer = setTimeout(() => {
			this.cleanupTimers.delete(id);
			this.remove(id);
		}, delayMs);
		this.cleanupTimers.set(id, timer);
	}

	remove(id: string): void {
		const watcher = this.exitWatchers.get(id);
		if (watcher) {
			clearInterval(watcher);
			this.exitWatchers.delete(id);
		}

		const cleanupTimer = this.cleanupTimers.get(id);
		if (cleanupTimer) {
			clearTimeout(cleanupTimer);
			this.cleanupTimers.delete(id);
		}

		const session = this.sessions.get(id);
		if (session) {
			session.session.dispose();
			this.sessions.delete(id);
			releaseSessionId(id);
			this.notifyChange();
		}
	}

	list(): BackgroundSession[] {
		return Array.from(this.sessions.values());
	}

	killAll(): void {
		// Kill all background sessions
		// Collect IDs first to avoid modifying map during iteration
		const bgIds = Array.from(this.sessions.keys());
		for (const id of bgIds) {
			this.remove(id);
		}

		// Kill all active hands-free sessions
		// Collect entries first since kill() may trigger unregisterActive()
		const activeEntries = Array.from(this.activeSessions.entries());
		for (const [id, session] of activeEntries) {
			try {
				session.kill();
				// Only release ID if kill succeeded - let natural cleanup handle failures
				// The session's exit handler will call unregisterActive() which releases the ID
			} catch (error) {
				console.error(`interactive-shell: failed to kill active session ${id} during shutdown`, error);
				// Keep the slug reservation when kill fails so a potentially still-running
				// session cannot collide with a newly generated ID.
			}
		}
		// Don't clear immediately - let unregisterActive() handle cleanup as sessions exit
		// This prevents ID reuse while processes are still terminating
	}
}

export const sessionManager = new ShellSessionManager();
