/**
 * TUI: Background Process Manager Overlay.
 */
import {
	Key,
	matchesKey,
	truncateToWidth,
	visibleWidth,
} from "@singularity-forge/tui";
import {
	cleanupAll,
	killProcess,
	processes,
	restartProcess,
} from "./process-manager.js";
import { ERROR_PATTERNS, WARNING_PATTERNS } from "./types.js";
import { formatTimeAgo, formatUptime } from "./utilities.js";
export class BgManagerOverlay {
	tui;
	theme;
	onClose;
	selected = 0;
	mode = "list";
	viewingProcess = null;
	scrollOffset = 0;
	cachedWidth;
	cachedLines;
	refreshTimer;
	constructor(tui, theme, onClose) {
		this.tui = tui;
		this.theme = theme;
		this.onClose = onClose;
		this.refreshTimer = setInterval(() => {
			this.invalidate();
			this.tui.requestRender();
		}, 1000);
	}
	getProcessList() {
		return Array.from(processes.values());
	}
	selectAndView(index) {
		const procs = this.getProcessList();
		if (index >= 0 && index < procs.length) {
			this.selected = index;
			this.viewingProcess = procs[index];
			this.mode = "output";
			this.scrollOffset = Math.max(0, procs[index].output.length - 20);
		}
	}
	handleInput(data) {
		if (this.mode === "output") {
			this.handleOutputInput(data);
			return;
		}
		if (this.mode === "events") {
			this.handleEventsInput(data);
			return;
		}
		this.handleListInput(data);
	}
	handleListInput(data) {
		const procs = this.getProcessList();
		if (
			matchesKey(data, Key.escape) ||
			matchesKey(data, Key.ctrl("c")) ||
			matchesKey(data, Key.ctrlAlt("b"))
		) {
			clearInterval(this.refreshTimer);
			this.onClose();
			return;
		}
		if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
			if (this.selected > 0) {
				this.selected--;
				this.invalidate();
				this.tui.requestRender();
			}
			return;
		}
		if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
			if (this.selected < procs.length - 1) {
				this.selected++;
				this.invalidate();
				this.tui.requestRender();
			}
			return;
		}
		if (matchesKey(data, Key.enter)) {
			const proc = procs[this.selected];
			if (proc) {
				this.viewingProcess = proc;
				this.mode = "output";
				this.scrollOffset = Math.max(0, proc.output.length - 20);
				this.invalidate();
				this.tui.requestRender();
			}
			return;
		}
		// e = view events
		if (data === "e") {
			const proc = procs[this.selected];
			if (proc) {
				this.viewingProcess = proc;
				this.mode = "events";
				this.scrollOffset = Math.max(0, proc.events.length - 15);
				this.invalidate();
				this.tui.requestRender();
			}
			return;
		}
		// r = restart
		if (data === "r") {
			const proc = procs[this.selected];
			if (proc) {
				restartProcess(proc.id)
					.then(() => {
						this.invalidate();
						this.tui.requestRender();
					})
					.catch((err) => {
						if (process.env.SF_DEBUG)
							console.error("[bg-shell] restart failed:", err);
						this.invalidate();
						this.tui.requestRender();
					});
			}
			return;
		}
		// x or d = kill selected
		if (data === "x" || data === "d") {
			const proc = procs[this.selected];
			if (proc && proc.alive) {
				killProcess(proc.id, "SIGTERM");
				setTimeout(() => {
					if (proc.alive) killProcess(proc.id, "SIGKILL");
					this.invalidate();
					this.tui.requestRender();
				}, 300);
			}
			return;
		}
		// X or D = kill all
		if (data === "X" || data === "D") {
			cleanupAll();
			this.selected = 0;
			this.invalidate();
			this.tui.requestRender();
			return;
		}
	}
	handleOutputInput(data) {
		if (matchesKey(data, Key.escape) || matchesKey(data, "q")) {
			this.mode = "list";
			this.viewingProcess = null;
			this.scrollOffset = 0;
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		// Tab to switch to events view
		if (matchesKey(data, Key.tab)) {
			this.mode = "events";
			if (this.viewingProcess) {
				this.scrollOffset = Math.max(0, this.viewingProcess.events.length - 15);
			}
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
			if (this.viewingProcess) {
				const total = this.viewingProcess.output.length;
				this.scrollOffset = Math.min(
					this.scrollOffset + 5,
					Math.max(0, total - 20),
				);
			}
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
			this.scrollOffset = Math.max(0, this.scrollOffset - 5);
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (data === "G") {
			if (this.viewingProcess) {
				const total = this.viewingProcess.output.length;
				this.scrollOffset = Math.max(0, total - 20);
			}
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (data === "g") {
			this.scrollOffset = 0;
			this.invalidate();
			this.tui.requestRender();
			return;
		}
	}
	handleEventsInput(data) {
		if (matchesKey(data, Key.escape) || matchesKey(data, "q")) {
			this.mode = "list";
			this.viewingProcess = null;
			this.scrollOffset = 0;
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		// Tab to switch back to output view
		if (matchesKey(data, Key.tab)) {
			this.mode = "output";
			if (this.viewingProcess) {
				this.scrollOffset = Math.max(0, this.viewingProcess.output.length - 20);
			}
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
			if (this.viewingProcess) {
				this.scrollOffset = Math.min(
					this.scrollOffset + 3,
					Math.max(0, this.viewingProcess.events.length - 10),
				);
			}
			this.invalidate();
			this.tui.requestRender();
			return;
		}
		if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
			this.scrollOffset = Math.max(0, this.scrollOffset - 3);
			this.invalidate();
			this.tui.requestRender();
			return;
		}
	}
	render(width) {
		if (this.cachedLines && this.cachedWidth === width) {
			return this.cachedLines;
		}
		let lines;
		if (this.mode === "events") {
			lines = this.renderEvents(width);
		} else if (this.mode === "output") {
			lines = this.renderOutput(width);
		} else {
			lines = this.renderList(width);
		}
		this.cachedWidth = width;
		this.cachedLines = lines;
		return lines;
	}
	box(inner, width) {
		const th = this.theme;
		const bdr = (s) => th.fg("borderMuted", s);
		const iw = width - 4;
		const lines = [];
		lines.push(bdr("╭" + "─".repeat(width - 2) + "╮"));
		for (const line of inner) {
			const truncated = truncateToWidth(line, iw);
			const pad = Math.max(0, iw - visibleWidth(truncated));
			lines.push(bdr("│") + " " + truncated + " ".repeat(pad) + " " + bdr("│"));
		}
		lines.push(bdr("╰" + "─".repeat(width - 2) + "╯"));
		return lines;
	}
	renderList(width) {
		const th = this.theme;
		const procs = this.getProcessList();
		const inner = [];
		if (procs.length === 0) {
			inner.push(th.fg("dim", "No background processes."));
			inner.push("");
			inner.push(th.fg("dim", "esc close"));
			return this.box(inner, width);
		}
		inner.push(th.fg("dim", "Background Processes"));
		inner.push("");
		for (let i = 0; i < procs.length; i++) {
			const p = procs[i];
			const sel = i === this.selected;
			const pointer = sel ? th.fg("accent", "▸ ") : "  ";
			const statusIcon = p.alive
				? p.status === "ready"
					? th.fg("success", "●")
					: p.status === "error"
						? th.fg("error", "●")
						: th.fg("warning", "●")
				: th.fg("dim", "○");
			const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt));
			const name = sel ? th.fg("text", p.label) : th.fg("muted", p.label);
			const typeTag = th.fg("dim", `[${p.processType}]`);
			const portInfo =
				p.ports.length > 0 ? th.fg("dim", ` :${p.ports.join(",")}`) : "";
			const errBadge =
				p.recentErrors.length > 0
					? th.fg("error", ` ⚠${p.recentErrors.length}`)
					: "";
			const groupTag = p.group ? th.fg("dim", ` {${p.group}}`) : "";
			const restartBadge =
				p.restartCount > 0 ? th.fg("warning", ` ↻${p.restartCount}`) : "";
			const status = p.alive ? "" : "  " + th.fg("dim", `exit ${p.exitCode}`);
			inner.push(
				`${pointer}${statusIcon} ${name} ${typeTag} ${uptime}${portInfo}${errBadge}${groupTag}${restartBadge}${status}`,
			);
		}
		inner.push("");
		inner.push(
			th.fg(
				"dim",
				"↑↓ select · enter output · e events · r restart · x kill · esc close",
			),
		);
		return this.box(inner, width);
	}
	processStatusHeader(p, activeTab) {
		const th = this.theme;
		if (!p) return { statusIcon: "", headerLine: "" };
		const statusIcon = p.alive
			? p.status === "ready"
				? th.fg("success", "●")
				: p.status === "error"
					? th.fg("error", "●")
					: th.fg("warning", "●")
			: th.fg("dim", "○");
		const name = th.fg("muted", p.label);
		const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt));
		const typeTag = th.fg("dim", `[${p.processType}]`);
		const portInfo =
			p.ports.length > 0 ? th.fg("dim", ` :${p.ports.join(",")}`) : "";
		const tabIndicator =
			activeTab === "output"
				? th.fg("accent", "[Output]") + " " + th.fg("dim", "Events")
				: th.fg("dim", "Output") + " " + th.fg("accent", "[Events]");
		const headerLine = `${statusIcon} ${name} ${typeTag} ${uptime}${portInfo}  ${tabIndicator}`;
		return { statusIcon, headerLine };
	}
	renderOutput(width) {
		const th = this.theme;
		const p = this.viewingProcess;
		if (!p) return [""];
		const inner = [];
		const { headerLine } = this.processStatusHeader(p, "output");
		inner.push(headerLine);
		inner.push("");
		// Unified buffer is already chronologically interleaved
		const allOutput = p.output;
		const maxVisible = 18;
		const visible = allOutput.slice(
			this.scrollOffset,
			this.scrollOffset + maxVisible,
		);
		if (allOutput.length === 0) {
			inner.push(th.fg("dim", "(no output)"));
		} else {
			for (const entry of visible) {
				const isError = ERROR_PATTERNS.some((pat) => pat.test(entry.line));
				const isWarning =
					!isError && WARNING_PATTERNS.some((pat) => pat.test(entry.line));
				const prefix = entry.stream === "stderr" ? th.fg("error", "⚠ ") : "";
				const color = isError ? "error" : isWarning ? "warning" : "dim";
				inner.push(prefix + th.fg(color, entry.line));
			}
			if (allOutput.length > maxVisible) {
				inner.push("");
				const pos = `${this.scrollOffset + 1}–${Math.min(this.scrollOffset + maxVisible, allOutput.length)} of ${allOutput.length}`;
				inner.push(th.fg("dim", pos));
			}
		}
		inner.push("");
		inner.push(th.fg("dim", "↑↓ scroll · g/G top/end · tab events · q back"));
		return this.box(inner, width);
	}
	renderEvents(width) {
		const th = this.theme;
		const p = this.viewingProcess;
		if (!p) return [""];
		const inner = [];
		const { headerLine } = this.processStatusHeader(p, "events");
		inner.push(headerLine);
		inner.push("");
		if (p.events.length === 0) {
			inner.push(th.fg("dim", "(no events)"));
		} else {
			const maxVisible = 15;
			const visible = p.events.slice(
				this.scrollOffset,
				this.scrollOffset + maxVisible,
			);
			for (const ev of visible) {
				const time = th.fg("dim", formatTimeAgo(ev.timestamp));
				const typeColor =
					ev.type === "crashed" || ev.type === "error_detected"
						? "error"
						: ev.type === "ready" || ev.type === "recovered"
							? "success"
							: ev.type === "port_open"
								? "accent"
								: "dim";
				const typeLabel = th.fg(typeColor, ev.type);
				inner.push(`${time}  ${typeLabel}`);
				inner.push(`  ${th.fg("dim", ev.detail.slice(0, 80))}`);
			}
			if (p.events.length > maxVisible) {
				inner.push("");
				inner.push(
					th.fg(
						"dim",
						`${this.scrollOffset + 1}–${Math.min(this.scrollOffset + maxVisible, p.events.length)} of ${p.events.length} events`,
					),
				);
			}
		}
		inner.push("");
		inner.push(th.fg("dim", "↑↓ scroll · tab output · q back"));
		return this.box(inner, width);
	}
	dispose() {
		clearInterval(this.refreshTimer);
	}
	invalidate() {
		this.cachedWidth = undefined;
		this.cachedLines = undefined;
	}
}
