// ABOUTME: Cycles operational modes (NORMAL/PLAN/SPEC/PIPELINE/TEAM/CHAIN) via Shift+Tab.
// ABOUTME: Gates which extension's before_agent_start fires and injects PLAN/SPEC prompts.

import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Text } from "@mariozechner/pi-tui";
import { outputLine } from "./lib/output-box.ts";
import { applyExtensionDefaults } from "./lib/themeMap.ts";
import { MODES, nextMode, modeLabel, modeBgAnsi, modeTextAnsi, type Mode } from "./lib/mode-cycler-logic.ts";
import { PLAN_PROMPT, SPEC_PROMPT, buildNormalPrompt } from "./lib/mode-prompts.ts";
import { writeFileSync } from "fs";
import { showBanner, isBannerVisible } from "./agent-banner.ts";

const MODE_FILE = "/tmp/pi-current-mode.txt";


export default function (pi: ExtensionAPI) {
	let currentMode: Mode = "NORMAL";

	function updateWidgets(mode: Mode, ctx: ExtensionContext) {
		if (!ctx.hasUI) return;

		if (mode === "NORMAL") {
			ctx.ui.setWidget("mode-block", undefined);
			// Re-set agent-banner after clearing mode-block to ensure correct rendering order
			// Only re-set if banner was previously visible (not hidden by user input)
			if (isBannerVisible()) {
				showBanner(ctx);
			}
			return;
		}

		// Mode block — full-width colored banner with mode name
		// Uses theme accent color (same as model name in footer)
		ctx.ui.setWidget(
			"mode-block",
			(_tui, _theme) => ({
				invalidate() {},
				render(width: number): string[] {
					const bg = modeBgAnsi(mode);
					const text = modeTextAnsi(mode);
					const reset = "\x1b[0m";
					const label = ` ${mode} `;
					const pad = " ".repeat(Math.max(0, width - label.length));
					return [bg + text + label + pad + reset];
				},
			}),
			{ placement: "aboveEditor" },
		);

		// Re-set agent-banner after setting mode-block to ensure it renders above the bar
		// This maintains the visual hierarchy: agent-banner (logo) → mode-block (bar) → editor
		// Only re-set if banner was previously visible (not hidden by user input)
		if (isBannerVisible()) {
			showBanner(ctx);
		}
	}

	// Expose refresh function so other extensions (e.g. agent-team) can re-pin
	// the mode-block as the last aboveEditor widget (closest to the editor input).
	function refreshModeBlock(ctx: ExtensionContext) {
		updateWidgets(currentMode, ctx);
	}

	function setMode(mode: Mode, ctx: ExtensionContext) {
		currentMode = mode;
		(globalThis as any).__piCurrentMode = mode;

		// Write to temp file for statusline
		try { writeFileSync(MODE_FILE, mode, "utf-8"); } catch {}

		if (ctx.hasUI) {
			ctx.ui.setStatus("mode", modeLabel(mode));
		}

		// Publish refresh callback so other aboveEditor widgets can re-pin the mode bar
		(globalThis as any).__piRefreshModeBlock = () => refreshModeBlock(ctx);

		updateWidgets(mode, ctx);
	}

	// ── Shift+Tab: cycle forward ──────────────────

	pi.registerShortcut("shift+tab", {
		description: "Cycle operational mode",
		handler: async (ctx) => {
			setMode(nextMode(currentMode), ctx);
		},
	});

	// ── /thinking command ─────────────────────────

	const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];

	pi.registerCommand("thinking", {
		description: "Set thinking level: /thinking or /thinking <LEVEL>",
		handler: async (args, ctx) => {
			if (!ctx.hasUI) return;

			const arg = args.trim().toLowerCase();
			if (arg && THINKING_LEVELS.includes(arg)) {
				pi.setThinkingLevel(arg);
				ctx.ui.notify(`Thinking: ${arg}`);
				return;
			}

			if (arg) {
				ctx.ui.notify(`Unknown level: ${arg}. Valid: ${THINKING_LEVELS.join(", ")}`, "error");
				return;
			}

			// Picker
			const current = pi.getThinkingLevel();
			const items = THINKING_LEVELS.map(l => {
				const active = l === current ? " (active)" : "";
				return `${l}${active}`;
			});
			const selected = await ctx.ui.select("Select Thinking Level", items);
			if (!selected) return;

			const level = selected.split(/\s/)[0];
			pi.setThinkingLevel(level);
			ctx.ui.notify(`Thinking: ${level}`);
		},
		autocomplete: (partial) => {
			return THINKING_LEVELS.filter(l => l.startsWith(partial.toLowerCase()));
		},
	});

	// ── /mode command ─────────────────────────────

	pi.registerCommand("mode", {
		description: "Set mode: /mode or /mode <MODE>",
		handler: async (args, ctx) => {
			if (!ctx.hasUI) return;

			const arg = args.trim().toUpperCase();
			if (arg && MODES.includes(arg as Mode)) {
				setMode(arg as Mode, ctx);
				return;
			}

			if (arg) {
				ctx.ui.notify(`Unknown mode: ${arg}. Valid: ${MODES.join(", ")}`, "error");
				return;
			}

			// Picker
			const items = MODES.map(m => {
				const active = m === currentMode ? " (active)" : "";
				return `${m}${active}`;
			});
			const selected = await ctx.ui.select("Select Mode", items);
			if (!selected) return;

			const name = selected.split(/\s/)[0] as Mode;
			setMode(name, ctx);
		},
	});

	// ── set_mode tool (autonomous mode switching) ──

	pi.registerTool({
		name: "set_mode",
		label: "Set Mode",
		description: "Switch the operational mode. Call this from NORMAL mode to activate PLAN, SPEC, TEAM, CHAIN, or PIPELINE based on task classification.",
		parameters: Type.Object({
			mode: Type.String({ description: "Target mode: NORMAL, PLAN, SPEC, PIPELINE, TEAM, or CHAIN" }),
			reason: Type.Optional(Type.String({ description: "Why this mode was chosen" })),
		}),

		async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
			const { mode: target, reason } = params as { mode: string; reason?: string };
			const upper = target.toUpperCase();

			if (!MODES.includes(upper as Mode)) {
				return {
					content: [{ type: "text", text: `Unknown mode: ${target}. Valid: ${MODES.join(", ")}` }],
					details: { error: true },
				};
			}

			setMode(upper as Mode, ctx);
			const msg = reason
				? `Mode set to ${upper}. Reason: ${reason}`
				: `Mode set to ${upper}.`;

			return {
				content: [{ type: "text", text: msg }],
				details: { mode: upper, reason },
			};
		},

		renderCall(args, theme) {
			const target = (args as any).mode || "?";
			const reason = (args as any).reason || "";
			const preview = reason.length > 50 ? reason.slice(0, 47) + "..." : reason;
			const text =
				theme.fg("toolTitle", theme.bold("set_mode ")) +
				theme.fg("accent", target.toUpperCase()) +
				(preview ? theme.fg("dim", " — ") + theme.fg("muted", preview) : "");
			return new Text(outputLine(theme, "accent", text), 0, 0);
		},

		renderResult(result, _options, theme) {
			const text = result.content[0];
			const msg = text?.type === "text" ? text.text : "";
			return new Text(outputLine(theme, "success", msg), 0, 0);
		},
	});

	// ── System prompt injection per mode ─────────

	pi.on("before_agent_start", async (_event, _ctx) => {
		if (currentMode === "NORMAL") {
			const g = globalThis as any;
			const scoutId = typeof g.__piScoutId === "number" ? g.__piScoutId : null;
			return { systemPrompt: buildNormalPrompt({
				commanderAvailable: !!g.__piCommanderAvailable,
				activeChain: g.__piActiveChain || null,
				activePipeline: g.__piActivePipeline || null,
				scoutId,
			})};
		}
		if (currentMode === "PLAN") return { systemPrompt: PLAN_PROMPT };
		if (currentMode === "SPEC") return { systemPrompt: SPEC_PROMPT };
		return {};
	});

	// ── Session init ──────────────────────────────

	pi.on("session_start", async (_event, ctx) => {
		applyExtensionDefaults(import.meta.url, ctx);
		currentMode = "NORMAL";
		(globalThis as any).__piCurrentMode = "NORMAL";
		(globalThis as any).__piRefreshModeBlock = () => refreshModeBlock(ctx);
		try { writeFileSync(MODE_FILE, "NORMAL", "utf-8"); } catch {}
		if (ctx.hasUI) {
			ctx.ui.setStatus("mode", "");
		}
		updateWidgets("NORMAL", ctx);
	});

	// ── Session switch (/new) ──────────────────────

	pi.on("session_switch", async (_event, ctx) => {
		// Re-apply current mode widgets after banner is shown to ensure correct rendering order
		// The banner is shown in agent-banner.ts's session_switch handler, so we need to
		// re-set widgets here to ensure mode-block (if any) renders before banner is re-set
		// Use process.nextTick to ensure banner's session_switch handler runs first
		process.nextTick(() => {
			updateWidgets(currentMode, ctx);
		});
	});
}
