/*
 * Lightweight wrapper around the `blessed` terminal UI library.
 *
 * With Bun's `--compile` the dependency is bundled, so we import it
 * directly and only fall back to plain text when not running in a TTY.
 */

import { stdin as input, stdout as output } from "node:process";
import type { ProgramInterface, ScreenInterface, ScreenOptions } from "neo-neo-bblessed";
import { screen as blessedScreen, box, program as createProgram } from "neo-neo-bblessed";

type ErrorConstructor = new () => unknown;

function constructError(value: unknown): Error | undefined {
	if (typeof value !== "function") {
		return undefined;
	}

	try {
		const candidate = new (value as ErrorConstructor)();
		return candidate instanceof Error ? candidate : undefined;
	} catch {
		return undefined;
	}
}

function normalizeToError(value: unknown): Error {
	if (value instanceof Error) {
		return value;
	}

	const constructed = constructError(value);
	if (constructed) {
		return constructed;
	}

	return new Error(String(value ?? "Unknown screen error"));
}

/**
 * Add pageup/pagedown/home/end scroll keybindings to a scrollable widget.
 * Blessed's built-in `keys: true` / `vi: true` only handle arrow keys and
 * Ctrl+D/U — this fills in the standard terminal navigation keys.
 */
export function addScrollKeys(
	widget: { height?: number | string; key: (keys: string[], fn: () => boolean | undefined) => void },
	screen: ScreenInterface,
): void {
	const scrollable = widget as unknown as {
		scroll?: (offset: number) => void;
		setScroll?: (offset: number) => void;
		setScrollPerc?: (perc: number) => void;
	};

	const pageAmount = () => {
		const height = typeof widget.height === "number" ? widget.height : 0;
		return height > 0 ? Math.max(1, height - 3) : 0;
	};

	widget.key(["pageup"], () => {
		const delta = pageAmount();
		if (delta > 0) {
			scrollable.scroll?.(-delta);
			screen.render();
		}
		return false;
	});
	widget.key(["pagedown"], () => {
		const delta = pageAmount();
		if (delta > 0) {
			scrollable.scroll?.(delta);
			screen.render();
		}
		return false;
	});
	widget.key(["home"], () => {
		scrollable.setScroll?.(0);
		screen.render();
		return false;
	});
	widget.key(["end"], () => {
		scrollable.setScrollPerc?.(100);
		screen.render();
		return false;
	});
}

export function createScreen(options: Partial<ScreenOptions> = {}): ScreenInterface {
	const program: ProgramInterface = createProgram({ tput: false });
	const screen = blessedScreen({ smartCSR: true, program, fullUnicode: true, ...options });

	if (process.env.DEBUG) {
		const tputColors = (screen as unknown as { tput?: { colors?: number } }).tput?.colors;
		console.error(`[TUI debug] tput.colors=${tputColors}, TERM=${process.env.TERM}`);
	}

	// Windows runners occasionally surface file system watcher errors as plain objects
	// (rather than Error instances). Blessed rethrows unhandled "error" events by
	// constructing the first argument, which explodes when it is a string. Attach a
	// defensive handler so these platform-specific events don't crash tests.
	screen.on("error", (err) => {
		const normalizedError = normalizeToError(err);
		if (process.env.DEBUG) {
			console.warn("TUI screen error", normalizedError);
		}
		throw normalizedError;
	});

	return screen;
}

// Ask the user for a single line of input.  Falls back to readline.
export async function promptText(message: string, defaultValue = ""): Promise<string> {
	// Always use readline for simple text input to avoid blessed rendering quirks
	const { createInterface } = await import("node:readline/promises");
	const rl = createInterface({ input, output });
	const answer = (await rl.question(`${message} `)).trim();
	rl.close();
	return answer || defaultValue;
}

// Display long content in a scrollable viewer.
export async function scrollableViewer(content: string): Promise<void> {
	if (output.isTTY === false) {
		console.log(content);
		return;
	}

	return new Promise<void>((resolve) => {
		const screen = createScreen({
			style: {},
		});

		const viewer = box({
			parent: screen,
			content,
			scrollable: true,
			alwaysScroll: true,
			keys: true,
			vi: true,
			mouse: true,
			width: "100%",
			height: "100%",
			padding: { left: 1, right: 1 },
			wrap: true,
			scrollbar: { ch: " ", inverse: true },
			style: { scrollbar: { bg: "gray" } },
		});

		addScrollKeys(viewer, screen);

		screen.key(["escape", "q", "C-c"], () => {
			screen.destroy();
			resolve();
		});

		viewer.focus();
		screen.render();
	});
}
