/**
 * SF-TUI — Unified TUI enhancements for Singularity Forge
 *
 * Features:
 * - Powerline footer: git branch, diff stats, last commit, model, cost, context
 * - Header: project name + branch + model
 * - Prompt history: Ctrl+Alt+H overlay
 * - Mode cycling: Ctrl+Shift+M, Ctrl+Shift+R, Ctrl+Shift+A, Ctrl+Shift+S
 */

import { execSync, spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { platform } from "node:os";
import { Key } from "@singularity-forge/tui";
import { getAutoSession } from "../auto/session.js";
import { isAutoActive } from "../auto.js";
import { projectRoot } from "../commands/context.js";
import { getErrorMessage } from "../error-utils.js";
import { getExperimentalFlag, setExperimentalFlag } from "../experimental.js";
import { registerSessionColor } from "./color-band.js";
import { registerSessionEmoji } from "./emoji.js";
import { renderAutoFooter, renderFooter } from "./footer.js";
import { invalidateGitStatus } from "./git.js";
import { renderAutoHeader, renderHeader } from "./header.js";
import { openMarketplaceOverlay } from "./marketplace.js";
import {
	appendPromptHistory,
	openPromptHistoryOverlay,
	pushPromptHistory,
	readPromptHistory,
} from "./prompt-history.js";

const WORK_MODE_CYCLE = [
	"chat",
	"plan",
	"build",
	"review",
	"repair",
	"research",
];
const PERMISSION_PROFILE_CYCLE = [
	"restricted",
	"normal",
	"trusted",
	"unrestricted",
];

function cycleWorkMode(ctx) {
	const session = getAutoSession();
	const current = session.getMode().workMode;
	const idx = WORK_MODE_CYCLE.indexOf(current);
	const next = WORK_MODE_CYCLE[(idx + 1) % WORK_MODE_CYCLE.length];
	const transition = session.setMode({ workMode: next });
	ctx.ui.notify(
		`Mode: ${transition.from.workMode} → ${transition.to.workMode}`,
		"info",
	);
}

function setWorkMode(ctx, mode) {
	const session = getAutoSession();
	const transition = session.setMode({ workMode: mode });
	ctx.ui.notify(
		`Mode: ${transition.from.workMode} → ${transition.to.workMode}`,
		"info",
	);
}

function setRunControl(ctx, rc) {
	const session = getAutoSession();
	const transition = session.setMode({ runControl: rc });
	ctx.ui.notify(
		`Run control: ${transition.from.runControl} → ${transition.to.runControl}`,
		"info",
	);
}

function cyclePermissionProfile(ctx) {
	const session = getAutoSession();
	const current = session.getMode().permissionProfile;
	const idx = PERMISSION_PROFILE_CYCLE.indexOf(current);
	const next =
		PERMISSION_PROFILE_CYCLE[(idx + 1) % PERMISSION_PROFILE_CYCLE.length];
	const transition = session.setMode({ permissionProfile: next });
	ctx.ui.notify(
		`Trust: ${transition.from.permissionProfile} → ${transition.to.permissionProfile}`,
		"info",
	);
}

function installHeader(ctx) {
	if (!ctx.hasUI) return;
	ctx.ui.setHeader((_tui, theme) => {
		return {
			render: (width) => {
				if (isAutoActive()) {
					return renderAutoHeader(theme, ctx, width);
				}
				return renderHeader(theme, ctx, width);
			},
			invalidate: () => {},
			dispose: () => {},
		};
	});
}
function installFooter(ctx) {
	if (!ctx.hasUI) return;
	ctx.ui.setFooter((_tui, theme, footerData) => {
		return {
			render: (width) => {
				if (isAutoActive()) {
					return renderAutoFooter(theme, footerData, ctx, width);
				}
				return renderFooter(theme, footerData, ctx, width);
			},
			invalidate: () => {},
			dispose: () => {},
		};
	});
}
export default function sfTui(pi) {
	registerSessionEmoji(pi);
	registerSessionColor(pi);
	const promptHistory = readPromptHistory();
	let promptHistorySessionId = randomUUID();
	let projectBasePath = null;
	let wasAutoActive = false;
	pi.on("session_start", async (_event, ctx) => {
		if (!ctx.hasUI) return;
		promptHistorySessionId =
			ctx.sessionManager?.getSessionId?.() ?? promptHistorySessionId;
		try {
			projectBasePath = projectRoot();
			const projectPromptHistory = readPromptHistory(projectBasePath);
			promptHistory.splice(0, promptHistory.length, ...projectPromptHistory);
		} catch {
			projectBasePath = null;
		}
		installHeader(ctx);
		installFooter(ctx);
		const openProjectPromptHistory = (overlayCtx) =>
			openPromptHistoryOverlay(overlayCtx, projectBasePath ?? undefined);
		pi.registerShortcut(Key.ctrlAlt("h"), {
			description: "Open prompt history",
			handler: openProjectPromptHistory,
		});
		pi.registerShortcut(Key.ctrlShift("h"), {
			description: "Open prompt history (fallback)",
			handler: openProjectPromptHistory,
		});
		pi.registerShortcut(Key.ctrlAlt("m"), {
			description: "Open marketplace browser",
			handler: openMarketplaceOverlay,
		});
		// Mode cycling shortcuts
		pi.registerShortcut(Key.ctrlShift("m"), {
			description: "Cycle work mode (chat→plan→build→review→repair→research)",
			handler: () => cycleWorkMode(ctx),
		});
		pi.registerShortcut(Key.ctrlShift("r"), {
			description: "Set work mode to repair",
			handler: () => setWorkMode(ctx, "repair"),
		});
		pi.registerShortcut(Key.ctrlShift("a"), {
			description: "Set run control to autonomous",
			handler: () => setRunControl(ctx, "autonomous"),
		});
		pi.registerShortcut(Key.ctrlShift("s"), {
			description: "Set run control to assisted (step)",
			handler: () => setRunControl(ctx, "assisted"),
		});
		pi.registerShortcut(Key.ctrlShift("p"), {
			description:
				"Cycle permission profile (restricted→normal→trusted→unrestricted)",
			handler: () => cyclePermissionProfile(ctx),
		});
		// Ctrl+G — open current project in $EDITOR (or notify if none)
		pi.registerShortcut(Key.ctrl("g"), {
			description: "Open project root in $EDITOR",
			handler: () => {
				const editor = process.env.EDITOR || process.env.VISUAL;
				if (!editor) {
					ctx.ui.notify(
						"No $EDITOR set. Set EDITOR=code (or vim, nano, etc.) in your shell.",
						"warning",
					);
					return;
				}
				spawn(editor, [projectRoot() ?? "."], {
					stdio: "ignore",
					detached: true,
				}).unref();
				ctx.ui.notify(`Opened ${editor} ${projectRoot() ?? "."}`, "info");
			},
		});
		// Ctrl+T — toggle reasoning display
		pi.registerShortcut(Key.ctrl("t"), {
			description: "Toggle extended thinking / reasoning display",
			handler: () => {
				const current = getExperimentalFlag("show_reasoning");
				setExperimentalFlag("show_reasoning", !current);
				ctx.ui.notify(`Reasoning display ${current ? "OFF" : "ON"}`, "info");
			},
		});
		// Ctrl+X B — open background session switcher (BACKGROUND_SESSIONS flag)
		pi.registerShortcut(Key.ctrlAlt("b"), {
			description: "Open background session switcher",
			handler: async () => {
				if (!getExperimentalFlag("background_sessions")) {
					ctx.sendMessage?.("/tasks");
					return;
				}
				await openBgSessionSwitcher(ctx);
			},
		});
		// Ctrl+X O — open URL from last agent message
		pi.registerShortcut(Key.ctrlAlt("o"), {
			description: "Open last URL from agent output in browser",
			handler: () => {
				const entries = ctx.sessionManager?.getEntries?.() ?? [];
				const lastText =
					[...entries].reverse().find((e) => e.type === "assistant")?.content ??
					"";
				const urlMatch = String(lastText).match(/https?:\/\/[^\s"'<>)]+/);
				if (!urlMatch) {
					ctx.ui.notify("No URL found in last agent output.", "info");
					return;
				}
				const url = urlMatch[0];
				try {
					const openCmd =
						platform() === "darwin"
							? "open"
							: platform() === "win32"
								? "start"
								: "xdg-open";
					execSync(`${openCmd} "${url}"`, { stdio: "ignore" });
					ctx.ui.notify(`Opened: ${url}`, "info");
				} catch {
					ctx.ui.notify(`URL: ${url}`, "info");
				}
			},
		});
		// STATUS_LINE — spawn user script every 5s, pipe stdout to footer chip
		startStatusLineRunner(ctx);
		wasAutoActive = isAutoActive();
	});
	pi.on("before_agent_start", async (event) => {
		const prompt = event.prompt?.trim();
		if (prompt) {
			pushPromptHistory(promptHistory, prompt);
			appendPromptHistory(
				prompt,
				projectBasePath ?? undefined,
				promptHistorySessionId,
			);
			pi.appendEntry("sf-prompt-history", {
				prompt,
				projectRoot: projectBasePath,
				sessionId: promptHistorySessionId,
				timestamp: Date.now(),
			});
		}
	});
	pi.on("tool_result", async (_event, ctx) => {
		invalidateGitStatus();
		const autoNow = isAutoActive();
		if (!autoNow && wasAutoActive) {
			installHeader(ctx);
			installFooter(ctx);
		}
		wasAutoActive = autoNow;
	});
	pi.on("agent_end", async (_event, ctx) => {
		const autoNow = isAutoActive();
		if (!autoNow) {
			installHeader(ctx);
			installFooter(ctx);
		}
		wasAutoActive = autoNow;
	});
	// SHOW_FILE tool — renders a file path + optional line range as a code block
	// in the agent timeline when the experimental flag is enabled.
	pi.registerTool({
		name: "show_file",
		description:
			"Display a file (or a section of it) as a highlighted code block in the conversation timeline. Use when you want to show the user specific code without just dumping text.",
		parameters: {
			type: "object",
			properties: {
				path: {
					type: "string",
					description: "Absolute or relative path to the file",
				},
				start_line: {
					type: "number",
					description: "First line to display (1-indexed, optional)",
				},
				end_line: {
					type: "number",
					description: "Last line to display (inclusive, optional)",
				},
			},
			required: ["path"],
		},
		execute: async ({ path: filePath, start_line, end_line }) => {
			if (!getExperimentalFlag("show_file")) {
				return {
					output:
						"SHOW_FILE is not enabled. Run /experimental on show_file to enable.",
				};
			}
			const { readFileSync, existsSync } = await import("node:fs");
			const { resolve } = await import("node:path");
			const abs = resolve(projectRoot() ?? ".", filePath);
			if (!existsSync(abs)) {
				return { output: `File not found: ${filePath}` };
			}
			const raw = readFileSync(abs, "utf-8");
			const lines = raw.split("\n");
			const from = start_line != null ? Math.max(1, start_line) - 1 : 0;
			const to =
				end_line != null ? Math.min(lines.length, end_line) : lines.length;
			const slice = lines.slice(from, to).join("\n");
			const ext = abs.split(".").pop() ?? "";
			return { output: `\`\`\`${ext}\n${slice}\n\`\`\`` };
		},
		renderResult: ({ output }) => output,
	});

	// ask_user_elicitation — structured form-based ask_user replacement.
	// Shows a TUI select overlay when choices are provided, freeform input otherwise.
	pi.registerTool({
		name: "ask_user_elicitation",
		description:
			"Ask the user a question using a structured form with optional choices. Shows a TUI select overlay when choices are provided, or a freeform text prompt otherwise.",
		parameters: {
			type: "object",
			properties: {
				question: {
					type: "string",
					description: "The question to ask the user",
				},
				choices: {
					type: "array",
					items: { type: "string" },
					description: "Optional list of choices to show as a select menu",
				},
				allow_freeform: {
					type: "boolean",
					description:
						"Whether to allow freeform text input in addition to choices",
				},
			},
			required: ["question"],
		},
		execute: async ({ question, choices, allow_freeform }, ctx) => {
			if (!ctx?.hasUI) {
				return { output: "No UI available for elicitation." };
			}
			if (choices?.length) {
				const answer = await ctx.ui.select(question, choices);
				if (!answer && allow_freeform) {
					const freeform = await ctx.ui.input(question);
					return { output: freeform ?? "" };
				}
				return { output: answer ?? "" };
			}
			const answer = await ctx.ui.input(question);
			return { output: answer ?? "" };
		},
		renderResult: ({ output }) => (output ? `**Answer:** ${output}` : ""),
	});
}

/** Run the STATUS_LINE user script on a 5s interval, posting stdout to footer. */
let _statusLineInterval = null;
function startStatusLineRunner(ctx) {
	if (_statusLineInterval) {
		clearInterval(_statusLineInterval);
		_statusLineInterval = null;
	}
	const tick = async () => {
		if (!getExperimentalFlag("status_line")) return;
		const scriptPath = getExperimentalFlag("status_line_script");
		if (!scriptPath || typeof scriptPath !== "string") return;
		try {
			const { execFile } = await import("node:child_process");
			const { promisify } = await import("node:util");
			const execFileAsync = promisify(execFile);
			const { stdout } = await execFileAsync(scriptPath, [], {
				timeout: 4000,
				encoding: "utf-8",
			});
			const text = stdout.trim().slice(0, 120);
			if (text) {
				ctx.ui.setStatus?.("sf-status-line", text);
			}
		} catch {
			/* Non-fatal — script may be missing or timing out */
		}
	};
	_statusLineInterval = setInterval(tick, 5000);
	tick();
}

// ─── Background Session Switcher ─────────────────────────────────────────────

const BG_SESSIONS_FILE = ".sf/sessions-queue.json";

/**
 * Open a TUI overlay listing background sessions from .sf/sessions-queue.json.
 * Selecting one sends /resume <sessionId> to the chat.
 *
 * Purpose: BACKGROUND_SESSIONS parity — let users switch between concurrent
 * sessions without leaving the TUI.
 * Consumer: Ctrl+Alt+B keybinding (session_start hook).
 */
async function openBgSessionSwitcher(ctx) {
	if (!ctx?.hasUI) return;
	const { existsSync, readFileSync } = await import("node:fs");
	const { join: pathJoin } = await import("node:path");
	let root;
	try {
		root = projectRoot();
	} catch {
		root = process.cwd();
	}
	const queuePath = pathJoin(root ?? process.cwd(), BG_SESSIONS_FILE);
	let sessions = [];
	if (existsSync(queuePath)) {
		try {
			sessions = JSON.parse(readFileSync(queuePath, "utf-8"));
		} catch {
			/* malformed */
		}
	}
	if (!sessions.length) {
		ctx.ui.notify(
			"No background sessions queued.\nStart one with /resume <description> or via BACKGROUND_SESSIONS controls.",
			"info",
		);
		return;
	}
	const labels = sessions.map(
		(s, i) =>
			`${(i + 1).toString().padStart(2)}. ${s.id ?? "unknown"} — ${s.summary ?? "no summary"}`,
	);
	const chosen = await ctx.ui.select("Switch to session:", labels);
	if (!chosen) return;
	const idx = labels.indexOf(chosen);
	const session = sessions[idx];
	if (session?.id) {
		ctx.sendMessage?.(`/resume ${session.id}`);
	}
}
