/**
 * SF-Notify — Background task completion notifications
 *
 * Detects long-running tasks and notifies you when they complete
 * while the terminal is backgrounded.
 */
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import {
	BEEP_SOUNDS,
	bringTerminalToFront,
	checkSayAvailable,
	checkTerminalNotifierAvailable,
	detectTerminalInfo,
	displayOSXNotification,
	isTerminalInBackground,
	isTerminalNotifierAvailable,
	playBeep,
	replaceMessageTemplates,
	SAY_MESSAGES,
	speakMessage,
} from "../../shared/notify.js";
import { getSfAgentSettingsPath } from "../preferences.js";

const DEFAULT_CONFIG = {
	thresholdMs: 2000,
	beep: true,
	beepSound: "Funk",
	bringToFront: false,
	say: false,
	sayMessage: "Done in {dirname}",
};
const NotificationAction = {
	Beeped: "beeped",
	Spoke: "spoke",
	BroughtToFront: "brought to front",
};
// ─────────────────────────────────────────────────────────────────────────────
// Settings Loader
// ─────────────────────────────────────────────────────────────────────────────
async function readSettingsFile() {
	const sfPath = getSfAgentSettingsPath();
	const piPath = path.join(os.homedir(), ".pi", "agent", "settings.json");
	for (const p of [sfPath, piPath]) {
		try {
			const content = await fs.readFile(p, "utf8");
			return JSON.parse(content);
		} catch {} // settings not found or corrupt -> try next path
	}
	return {};
}
async function getBackgroundNotifyConfig(ctx, overrides) {
	const settings = ctx.settingsManager?.getSettings() ?? {};
	let config;
	if (settings.backgroundNotify) {
		config = { ...DEFAULT_CONFIG, ...settings.backgroundNotify };
	} else {
		const fileSettings = await readSettingsFile();
		config = { ...DEFAULT_CONFIG, ...fileSettings.backgroundNotify };
	}
	if (overrides) config = { ...config, ...overrides };
	return config;
}
// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────
function resetSessionState(state) {
	state.beepOverride = null;
	state.beepSoundOverride = null;
	state.focusOverride = null;
	state.sayOverride = null;
	state.sayMessageOverride = null;
	state.lastToolTime = undefined;
	state.totalActiveTime = 0;
}
function getEffective(state, config) {
	return {
		beep: state.beepOverride ?? config.beep,
		focus: state.focusOverride ?? config.bringToFront,
		say: state.sayOverride ?? config.say,
		sound: state.beepSoundOverride ?? config.beepSound,
		sayMessage: state.sayMessageOverride ?? config.sayMessage,
	};
}
function extractOptionText(action, iconPrefix) {
	if (!action || action === "❌ Cancel" || action === "───") return null;
	if (action.startsWith(iconPrefix)) {
		return action
			.replace(iconPrefix, "")
			.replace(" ✓", "")
			.replace(/^"|"$/g, "");
	}
	return null;
}
async function saveGlobalSettings(_ctx, updates) {
	try {
		const sfPath = getSfAgentSettingsPath();
		let fileSettings = {};
		try {
			const content = await fs.readFile(sfPath, "utf8");
			fileSettings = JSON.parse(content);
		} catch {
			// no file yet
		}
		fileSettings.backgroundNotify = {
			...(fileSettings.backgroundNotify ?? {}),
			...updates,
		};
		await fs.mkdir(path.dirname(sfPath), { recursive: true });
		await fs.writeFile(sfPath, JSON.stringify(fileSettings, null, 2), "utf8");
	} catch (err) {
		console.error("Failed to save settings:", err);
		throw err;
	}
}
// ─────────────────────────────────────────────────────────────────────────────
// Main Extension
// ─────────────────────────────────────────────────────────────────────────────
export default function sfNotify(pi) {
	const state = {
		beepOverride: null,
		beepSoundOverride: null,
		focusOverride: null,
		sayOverride: null,
		sayMessageOverride: null,
		terminalInfo: {},
		lastToolTime: undefined,
		totalActiveTime: 0,
	};
	registerCommands(pi, state);
	pi.on("session_start", async (_, ctx) => {
		resetSessionState(state);
		state.terminalInfo = await detectTerminalInfo();
		await checkSayAvailable();
		await checkTerminalNotifierAvailable();
		if (ctx.hasUI && (await isTerminalNotifierAvailable())) {
			ctx.ui.notify(
				"📢 Using terminal-notifier for notifications (clicking will activate Terminal)",
				"info",
			);
		}
	});
	pi.on("agent_start", () => {
		state.lastToolTime = Date.now();
		state.totalActiveTime = 0;
	});
	pi.on("tool_result", () => {
		if (state.lastToolTime) {
			state.totalActiveTime += Date.now() - state.lastToolTime;
		}
		state.lastToolTime = Date.now();
	});
	pi.on("agent_end", async (_, ctx) => {
		if (!state.lastToolTime) return;
		state.totalActiveTime += Date.now() - state.lastToolTime;
		const duration = state.totalActiveTime;
		state.lastToolTime = undefined;
		state.totalActiveTime = 0;
		const config = await getBackgroundNotifyConfig(ctx);
		const eff = getEffective(state, config);
		if (!eff.beep && !eff.focus && !eff.say) return;
		if (duration < config.thresholdMs) return;
		const isBackground = await isTerminalInBackground(state.terminalInfo);
		if (!isBackground) return;
		const tasks = [];
		const actions = [];
		if (eff.beep) {
			const notificationMessage = replaceMessageTemplates(eff.sayMessage);
			displayOSXNotification(
				notificationMessage,
				eff.sound,
				state.terminalInfo,
			);
			actions.push(NotificationAction.Beeped);
		}
		if (eff.focus) {
			tasks.push(bringTerminalToFront(state.terminalInfo));
			actions.push(NotificationAction.BroughtToFront);
		}
		if (eff.say) {
			speakMessage(eff.sayMessage);
			actions.push(NotificationAction.Spoke);
		}
		await Promise.all(tasks);
		if (ctx.hasUI) {
			ctx.ui.notify(
				`Task completed in ${(duration / 1000).toFixed(1)}s (${actions.join(", ")})`,
				"info",
			);
		}
	});
}
// ─────────────────────────────────────────────────────────────────────────────
// Commands
// ─────────────────────────────────────────────────────────────────────────────
function registerCommands(pi, state) {
	pi.registerCommand("notify-beep", {
		description: "Toggle beep notification",
		handler: async (_, ctx) => {
			const config = await getBackgroundNotifyConfig(ctx);
			const current = state.beepOverride ?? config.beep;
			if (current) {
				state.beepOverride = false;
				ctx.ui.notify("🔇 Beep OFF", "warning");
			} else {
				const currentSound = state.beepSoundOverride ?? config.beepSound;
				const options = [
					"🔊 Use current sound",
					"───",
					...BEEP_SOUNDS.map((s) => `🎵 ${s}${s === currentSound ? " ✓" : ""}`),
					"───",
					"❌ Cancel",
				];
				const action = await ctx.ui.select(
					`Turn beep ON - Select sound (current: ${currentSound})`,
					options,
				);
				const selectedAction = typeof action === "string" ? action : undefined;
				if (
					!selectedAction ||
					selectedAction === "❌ Cancel" ||
					selectedAction === "───"
				)
					return;
				if (selectedAction === "🔊 Use current sound") {
					state.beepOverride = true;
					ctx.ui.notify(`🔊 Beep ON (${currentSound})`, "info");
					playBeep(currentSound);
				} else {
					const sound = extractOptionText(selectedAction, "🎵 ");
					if (sound) {
						state.beepOverride = true;
						state.beepSoundOverride = sound;
						ctx.ui.notify(`🔊 Beep ON (${sound})`, "info");
						playBeep(sound);
					}
				}
			}
		},
	});
	pi.registerCommand("notify-focus", {
		description: "Toggle bring-to-front",
		handler: async (_, ctx) => {
			const config = await getBackgroundNotifyConfig(ctx);
			const current = state.focusOverride ?? config.bringToFront;
			state.focusOverride = !current;
			ctx.ui.notify(
				state.focusOverride ? "🪟 Focus ON" : "⬜ Focus OFF",
				state.focusOverride ? "info" : "warning",
			);
		},
	});
	pi.registerCommand("notify-say", {
		description: "Toggle speech notification",
		handler: async (_, ctx) => {
			const config = await getBackgroundNotifyConfig(ctx);
			const current = state.sayOverride ?? config.say;
			if (current) {
				state.sayOverride = false;
				ctx.ui.notify("🔇 Speech OFF", "warning");
			} else {
				const currentMessage = state.sayMessageOverride ?? config.sayMessage;
				const options = [
					"🗣️  Use current message",
					"───",
					...SAY_MESSAGES.map(
						(m) => `💬 "${m}"${m === currentMessage ? " ✓" : ""}`,
					),
					"───",
					"✏️  Enter custom message...",
					"───",
					"❌ Cancel",
				];
				const action = await ctx.ui.select(
					`Turn speech ON - Select message (current: "${currentMessage}")`,
					options,
				);
				const selectedAction = typeof action === "string" ? action : undefined;
				if (
					!selectedAction ||
					selectedAction === "❌ Cancel" ||
					selectedAction === "───"
				)
					return;
				if (selectedAction === "🗣️  Use current message") {
					state.sayOverride = true;
					ctx.ui.notify(`🗣️  Speech ON ("${currentMessage}")`, "info");
					speakMessage(currentMessage);
				} else if (selectedAction.startsWith("💬 ")) {
					const message = selectedAction
						.replace('💬 "', "")
						.replace('"', "")
						.replace(" ✓", "");
					state.sayOverride = true;
					state.sayMessageOverride = message;
					ctx.ui.notify(`🗣️  Speech ON ("${message}")`, "info");
					speakMessage(message);
				} else if (selectedAction === "✏️  Enter custom message...") {
					const customMessage = await ctx.ui.input("Enter message to speak");
					if (customMessage && customMessage.trim()) {
						state.sayOverride = true;
						state.sayMessageOverride = customMessage.trim();
						ctx.ui.notify(`🗣️  Speech ON ("${customMessage.trim()}")`, "info");
						speakMessage(customMessage.trim());
					}
				}
			}
		},
	});
	pi.registerCommand("notify-threshold", {
		description: "Set notification threshold (minimum task duration)",
		handler: async (_, ctx) => {
			const config = await getBackgroundNotifyConfig(ctx);
			const options = [
				`1000ms (1s)${config.thresholdMs === 1000 ? " ✓" : ""}`,
				`2000ms (2s)${config.thresholdMs === 2000 ? " ✓" : ""}`,
				`3000ms (3s)${config.thresholdMs === 3000 ? " ✓" : ""}`,
				`5000ms (5s)${config.thresholdMs === 5000 ? " ✓" : ""}`,
				`10000ms (10s)${config.thresholdMs === 10000 ? " ✓" : ""}`,
				"───",
				"❌ Cancel",
			];
			const action = await ctx.ui.select(
				`Threshold (current: ${config.thresholdMs}ms)`,
				options,
			);
			const selectedAction = typeof action === "string" ? action : undefined;
			if (
				!selectedAction ||
				selectedAction === "❌ Cancel" ||
				selectedAction === "───"
			)
				return;
			const match = selectedAction.match(/^(\d+)ms/);
			if (match) {
				const newThreshold = parseInt(match[1], 10);
				await saveGlobalSettings(ctx, { thresholdMs: newThreshold });
				ctx.ui.notify(`⏱️  Threshold set to ${newThreshold}ms`, "info");
			}
		},
	});
	pi.registerCommand("notify-status", {
		description: "Show notification settings",
		handler: async (_, ctx) => {
			const config = await getBackgroundNotifyConfig(ctx);
			const eff = getEffective(state, config);
			const beepIcon = eff.beep ? "🔊" : "🔇";
			const focusIcon = eff.focus ? "🪟" : "⬜";
			const sayIcon = eff.say ? "🗣️" : "🔇";
			const globalBeepIcon = config.beep ? "🔊" : "🔇";
			const globalFocusIcon = config.bringToFront ? "🪟" : "⬜";
			const globalSayIcon = config.say ? "🗣️" : "🔇";
			const hasOverrides =
				state.beepOverride !== null ||
				state.focusOverride !== null ||
				state.beepSoundOverride !== null ||
				state.sayOverride !== null ||
				state.sayMessageOverride !== null;
			const lines = [
				"╭─ Background Notify Status ─╮",
				"",
				"Current (Effective):",
				`  ${beepIcon} Beep: ${eff.beep ? "ON" : "OFF"}`,
				`  ${focusIcon} Focus: ${eff.focus ? "ON" : "OFF"}`,
				`  ${sayIcon} Speech: ${eff.say ? "ON" : "OFF"}`,
				`  💬 Message: "${eff.sayMessage}"`,
				eff.sayMessage.includes("{dirname}")
					? `  → Spoken: "${replaceMessageTemplates(eff.sayMessage)}"`
					: "",
				`  🎵 Sound: ${eff.sound}`,
				`  ⏱️  Threshold: ${config.thresholdMs}ms`,
				"",
				"Global Defaults:",
				`  ${globalBeepIcon} Beep: ${config.beep ? "ON" : "OFF"}`,
				`  ${globalFocusIcon} Focus: ${config.bringToFront ? "ON" : "OFF"}`,
				`  ${globalSayIcon} Speech: ${config.say ? "ON" : "OFF"}`,
				`  💬 Message: "${config.sayMessage}"`,
				config.sayMessage.includes("{dirname}")
					? `  → Spoken: "${replaceMessageTemplates(config.sayMessage)}"`
					: "",
				`  🎵 Sound: ${config.beepSound}`,
				`  ⏱️  Threshold: ${config.thresholdMs}ms`,
			];
			if (hasOverrides) {
				lines.push("", "Session Overrides:");
				if (state.beepOverride !== null)
					lines.push(
						`  ${state.beepOverride ? "🔊" : "🔇"} Beep: ${state.beepOverride ? "ON" : "OFF"}`,
					);
				if (state.focusOverride !== null)
					lines.push(
						`  ${state.focusOverride ? "🪟" : "⬜"} Focus: ${state.focusOverride ? "ON" : "OFF"}`,
					);
				if (state.beepSoundOverride !== null)
					lines.push(`  🎵 Sound: ${state.beepSoundOverride}`);
				if (state.sayOverride !== null)
					lines.push(
						`  ${state.sayOverride ? "🗣️" : "🔇"} Speech: ${state.sayOverride ? "ON" : "OFF"}`,
					);
				if (state.sayMessageOverride !== null) {
					lines.push(`  💬 Message: "${state.sayMessageOverride}"`);
					if (state.sayMessageOverride.includes("{dirname}")) {
						lines.push(
							`     → Spoken: "${replaceMessageTemplates(state.sayMessageOverride)}"`,
						);
					}
				}
			}
			lines.push(
				"",
				`💻 Terminal: ${state.terminalInfo.terminalApp ?? "(unknown)"}`,
				"╰────────────────────────────╯",
			);
			ctx.ui.notify(lines.filter(Boolean).join("\n"), "info");
		},
	});
	pi.registerCommand("notify-save-global", {
		description: "Save current settings as global defaults",
		handler: async (_, ctx) => {
			const config = await getBackgroundNotifyConfig(ctx);
			const eff = getEffective(state, config);
			await saveGlobalSettings(ctx, {
				beep: eff.beep,
				bringToFront: eff.focus,
				beepSound: eff.sound,
				say: eff.say,
				sayMessage: eff.sayMessage,
				thresholdMs: config.thresholdMs,
			});
			ctx.ui.notify("✅ Settings saved to ~/.sf/agent/settings.json", "info");
			const status = [
				`  ${eff.beep ? "🔊" : "🔇"} Beep: ${eff.beep ? "ON" : "OFF"}`,
				`  ${eff.focus ? "🪟" : "⬜"} Focus: ${eff.focus ? "ON" : "OFF"}`,
				`  ${eff.say ? "🗣️" : "🔇"} Speech: ${eff.say ? "ON" : "OFF"}`,
				`  💬 Message: "${eff.sayMessage}"`,
				`  🎵 Sound: ${eff.sound}`,
				`  ⏱️  Threshold: ${config.thresholdMs}ms`,
			]
				.filter(Boolean)
				.join("\n");
			ctx.ui.notify(status, "info");
		},
	});
}
