import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
import { Type } from "typebox";

type QuestionOption = {
	value: string;
	label: string;
	description?: string;
};

type Question = {
	id: string;
	label: string;
	prompt: string;
	options: QuestionOption[];
};

type Answer = {
	id: string;
	kind: "option" | "custom";
	value: string;
	label: string;
	optionIndex?: number;
	extension?: string;
};

type QuestionnaireResult = {
	questions: Question[];
	answers: Answer[];
	cancelled: boolean;
	submittedText?: string;
};

type RenderOption = QuestionOption & { isCustom?: boolean };

const QuestionOptionSchema = Type.Object({
	value: Type.String({ description: "Stable value returned when this option is selected." }),
	label: Type.String({ description: "Human-readable option text shown to the user." }),
	description: Type.Optional(Type.String({ description: "Optional secondary help text for this option." })),
});

const QuestionSchema = Type.Object({
	id: Type.String({ description: "Stable unique ID for this question, such as 'database' or 'scope'." }),
	label: Type.Optional(Type.String({ description: "Short label for navigation, such as 'Database' or 'Scope'." })),
	prompt: Type.String({ description: "The full question to ask the user." }),
	options: Type.Array(QuestionOptionSchema, { description: "Selectable answers to offer the user." }),
});

const QuestionnaireParams = Type.Object({
	title: Type.Optional(Type.String({ description: "Short title for the questionnaire." })),
	questions: Type.Array(QuestionSchema, { description: "Questions to ask the user." }),
});

function normalizeQuestions(params: { questions: Array<{ id: string; label?: string; prompt: string; options: QuestionOption[] }> }): Question[] {
	return params.questions.map((question, index) => ({
		id: question.id,
		label: question.label || `Q${index + 1}`,
		prompt: question.prompt,
		options: question.options,
	}));
}

function renderSubmittedText(title: string, questions: Question[], answers: Answer[]): string {
	const answerById = new Map(answers.map((answer) => [answer.id, answer]));
	const lines: string[] = [`${title}`, ""];
	for (let i = 0; i < questions.length; i++) {
		const question = questions[i];
		const answer = answerById.get(question.id);
		lines.push(`${i + 1}. ${question.prompt}`);
		if (!answer) {
			lines.push("   Answer: (missing)", "");
			continue;
		}
		if (answer.kind === "custom") {
			lines.push(`   Answer: user wrote: ${answer.label}`);
		} else {
			const optionNumber = answer.optionIndex ? `${answer.optionIndex}. ` : "";
			lines.push(`   Answer: user selected: ${optionNumber}${answer.label}`);
		}
		if (answer.extension?.trim()) {
			lines.push(`   Note/extension: ${answer.extension.trim()}`);
		}
		lines.push("");
	}
	return lines.join("\n").trimEnd();
}

function errorResult(message: string, questions: Question[] = []): { content: { type: "text"; text: string }[]; details: QuestionnaireResult } {
	return {
		content: [{ type: "text", text: message }],
		details: { questions, answers: [], cancelled: true, submittedText: message },
	};
}

export default function questionnaire(pi: ExtensionAPI) {
	async function runQuestionnaireUi(
		ctx: { hasUI: boolean; ui: { custom: <T>(factory: (tui: any, theme: any, keybindings: any, done: (value: T) => void) => any) => Promise<T>; notify?: (message: string, level: "info" | "warning" | "error") => void } },
		title: string,
		questions: Question[],
	): Promise<QuestionnaireResult> {
		return ctx.ui.custom<QuestionnaireResult>((tui, theme, _keybindings, done) => {
			let currentQuestionIndex = 0;
			let selectedOptionIndex = 0;
			let inputMode: "none" | "custom" | "extend" = "none";
			let inputQuestionId: string | undefined;
			let inputOption: { option: QuestionOption; optionIndex: number } | undefined;
			let cachedWidth: number | undefined;
			let cachedLines: string[] | undefined;
			const answers = new Map<string, Answer>();

			const editorTheme: EditorTheme = {
				borderColor: (s: string) => theme.fg("accent", s),
				selectList: {
					selectedPrefix: (s: string) => theme.fg("accent", s),
					selectedText: (s: string) => theme.fg("accent", s),
					description: (s: string) => theme.fg("muted", s),
					scrollInfo: (s: string) => theme.fg("dim", s),
					noMatch: (s: string) => theme.fg("warning", s),
				},
			};
			const editor = new Editor(tui, editorTheme);

			function refresh() {
				cachedWidth = undefined;
				cachedLines = undefined;
				tui.requestRender();
			}

			function currentQuestion(): Question | undefined {
				return questions[currentQuestionIndex];
			}

			function isReviewScreen(): boolean {
				return currentQuestionIndex === questions.length;
			}

			function currentOptions(): RenderOption[] {
				const question = currentQuestion();
				if (!question) return [];
				return [...question.options, { value: "__custom__", label: "Write my own response", isCustom: true }];
			}

			function allAnswered(): boolean {
				return questions.every((question) => answers.has(question.id));
			}

			function saveAnswer(answer: Answer) {
				answers.set(answer.id, answer);
			}

			function moveToNext() {
				currentQuestionIndex = Math.min(questions.length, currentQuestionIndex + 1);
				selectedOptionIndex = 0;
				refresh();
			}

			function moveToPrevious() {
				currentQuestionIndex = Math.max(0, currentQuestionIndex - 1);
				selectedOptionIndex = 0;
				refresh();
			}

			function startCustomInput(question: Question) {
				inputMode = "custom";
				inputQuestionId = question.id;
				inputOption = undefined;
				editor.setText(answers.get(question.id)?.kind === "custom" ? answers.get(question.id)?.label || "" : "");
				refresh();
			}

			function startExtendInput(question: Question, option: QuestionOption, optionIndex: number) {
				inputMode = "extend";
				inputQuestionId = question.id;
				inputOption = { option, optionIndex };
				const existing = answers.get(question.id);
				editor.setText(existing?.kind === "option" && existing.value === option.value ? existing.extension || "" : "");
				refresh();
			}

			function closeInputMode() {
				inputMode = "none";
				inputQuestionId = undefined;
				inputOption = undefined;
				editor.setText("");
				refresh();
			}

			editor.onSubmit = (value: string) => {
				const questionId = inputQuestionId;
				if (!questionId) return;
				const trimmed = value.trim();
				if (inputMode === "custom") {
					saveAnswer({
						id: questionId,
						kind: "custom",
						value: trimmed || "(no response)",
						label: trimmed || "(no response)",
					});
				} else if (inputMode === "extend" && inputOption) {
					saveAnswer({
						id: questionId,
						kind: "option",
						value: inputOption.option.value,
						label: inputOption.option.label,
						optionIndex: inputOption.optionIndex + 1,
						extension: trimmed,
					});
				}
				closeInputMode();
				moveToNext();
			};

			function submit(cancelled: boolean) {
				const finalAnswers = questions
					.map((question) => answers.get(question.id))
					.filter((answer): answer is Answer => answer !== undefined);
				const submittedText = cancelled
					? "User cancelled the questionnaire."
					: renderSubmittedText(title, questions, finalAnswers);
				done({ questions, answers: finalAnswers, cancelled, submittedText });
			}

			function handleInput(data: string) {
				if (inputMode !== "none") {
					if (matchesKey(data, Key.escape)) {
						closeInputMode();
						return;
					}
					editor.handleInput(data);
					refresh();
					return;
				}

				if (matchesKey(data, Key.escape)) {
					submit(true);
					return;
				}

				if (matchesKey(data, Key.left) || matchesKey(data, Key.shift("tab"))) {
					moveToPrevious();
					return;
				}
				if (matchesKey(data, Key.right) || matchesKey(data, Key.tab)) {
					moveToNext();
					return;
				}

				if (isReviewScreen()) {
					if (matchesKey(data, Key.enter) && allAnswered()) submit(false);
					return;
				}

				const question = currentQuestion();
				const options = currentOptions();
				if (!question) return;

				if (matchesKey(data, Key.up)) {
					selectedOptionIndex = Math.max(0, selectedOptionIndex - 1);
					refresh();
					return;
				}
				if (matchesKey(data, Key.down)) {
					selectedOptionIndex = Math.min(options.length - 1, selectedOptionIndex + 1);
					refresh();
					return;
				}

				const option = options[selectedOptionIndex];
				if ((data === "e" || data === "E") && option && !option.isCustom) {
					startExtendInput(question, option, selectedOptionIndex);
					return;
				}

				if (matchesKey(data, Key.enter) && option) {
					if (option.isCustom) {
						startCustomInput(question);
						return;
					}
					saveAnswer({
						id: question.id,
						kind: "option",
						value: option.value,
						label: option.label,
						optionIndex: selectedOptionIndex + 1,
					});
					moveToNext();
				}
			}

			function addWrapped(lines: string[], text: string, width: number, indent = "") {
				const wrapped = wrapTextWithAnsi(text, Math.max(1, width - indent.length));
				for (const line of wrapped) lines.push(truncateToWidth(indent + line, width));
			}

			function renderProgress(width: number): string[] {
				const chunks: string[] = [];
				for (let i = 0; i < questions.length; i++) {
					const question = questions[i];
					const answered = answers.has(question.id);
					const active = i === currentQuestionIndex;
					const mark = answered ? "■" : "□";
					const raw = ` ${mark} ${i + 1}:${question.label} `;
					chunks.push(active ? theme.bg("selectedBg", theme.fg("text", raw)) : theme.fg(answered ? "success" : "muted", raw));
				}
				const submitRaw = " ✓ Submit ";
				chunks.push(isReviewScreen() ? theme.bg("selectedBg", theme.fg("text", submitRaw)) : theme.fg(allAnswered() ? "success" : "dim", submitRaw));
				return wrapTextWithAnsi(chunks.join(" "), width).map((line) => truncateToWidth(line, width));
			}

			function render(width: number): string[] {
				if (cachedLines && cachedWidth === width) return cachedLines;
				const lines: string[] = [];
				const border = theme.fg("accent", "─".repeat(Math.max(0, width)));
				const add = (text = "") => lines.push(truncateToWidth(text, width));

				add(border);
				add(theme.fg("accent", theme.bold(` ${title}`)));
				for (const line of renderProgress(width)) add(line);
				add("");

				if (inputMode !== "none") {
					const question = questions.find((q) => q.id === inputQuestionId);
					if (question) addWrapped(lines, theme.fg("text", question.prompt), width, " ");
					add("");
					if (inputMode === "custom") {
						add(theme.fg("muted", " Write your custom response:"));
					} else if (inputOption) {
						add(theme.fg("muted", ` Extend selected option: ${inputOption.option.label}`));
						add(theme.fg("dim", " Leave blank and press Enter to select without extra notes."));
					}
					for (const line of editor.render(Math.max(1, width - 2))) add(` ${line}`);
					add("");
					add(theme.fg("dim", " Enter submit text • Esc return to options"));
					add(border);
					cachedWidth = width;
					cachedLines = lines;
					return lines;
				}

				if (isReviewScreen()) {
					add(theme.fg("accent", theme.bold(" Review before submission")));
					add("");
					for (let i = 0; i < questions.length; i++) {
						const question = questions[i];
						const answer = answers.get(question.id);
						addWrapped(lines, theme.fg("text", `${i + 1}. ${question.prompt}`), width, " ");
						if (answer) {
							const answerText = answer.kind === "custom"
								? `Answer: user wrote: ${answer.label}`
								: `Answer: user selected: ${answer.optionIndex}. ${answer.label}`;
							addWrapped(lines, theme.fg("success", answerText), width, "   ");
							if (answer.extension?.trim()) addWrapped(lines, theme.fg("muted", `Note/extension: ${answer.extension.trim()}`), width, "   ");
						} else {
							add(theme.fg("warning", "   Answer: missing"));
						}
						add("");
					}
					if (allAnswered()) add(theme.fg("success", " Enter submit • ←/Shift+Tab go back and change answers • Esc cancel"));
					else add(theme.fg("warning", " Some questions are unanswered. Use ←/Shift+Tab to go back."));
					add(border);
					cachedWidth = width;
					cachedLines = lines;
					return lines;
				}

				const question = currentQuestion();
				const options = currentOptions();
				if (question) {
					addWrapped(lines, theme.fg("text", `${currentQuestionIndex + 1}. ${question.prompt}`), width, " ");
					const existing = answers.get(question.id);
					if (existing) {
						const summary = existing.kind === "custom" ? `(current: custom — ${existing.label})` : `(current: ${existing.optionIndex}. ${existing.label}${existing.extension ? " + note" : ""})`;
						addWrapped(lines, theme.fg("muted", summary), width, " ");
					}
					add("");
					for (let i = 0; i < options.length; i++) {
						const option = options[i];
						const selected = i === selectedOptionIndex;
						const prefix = selected ? theme.fg("accent", "> ") : "  ";
						const label = option.isCustom ? `${i + 1}. ${option.label} ✎` : `${i + 1}. ${option.label}`;
						addWrapped(lines, `${prefix}${theme.fg(selected ? "accent" : "text", label)}`, width, "");
						if (option.description) addWrapped(lines, theme.fg("muted", option.description), width, "     ");
					}
				}
				add("");
				add(theme.fg("dim", " ↑↓ select • Enter choose • e extend option • Tab/→ next • Shift+Tab/← previous • Esc cancel"));
				add(border);

				cachedWidth = width;
				cachedLines = lines;
				return lines;
			}

			return { render, invalidate: refresh, handleInput };
		});
	}

	pi.registerCommand("questionnaire-demo", {
		description: "Open a demo interactive questionnaire to test the TUI flow",
		handler: async (_args, ctx) => {
			if (!ctx.hasUI) {
				ctx.ui.notify("questionnaire-demo requires interactive mode", "error");
				return;
			}
			const questions: Question[] = [
				{
					id: "style",
					label: "Style",
					prompt: "How should Nimrod ask follow-up questions by default?",
					options: [
						{ value: "interactive", label: "Interactive questionnaire", description: "Use the new selectable TUI for multi-question clarification." },
						{ value: "numbered", label: "Plain numbered list", description: "Keep the existing text-only numbered format." },
					],
				},
				{
					id: "pace",
					label: "Pace",
					prompt: "How much detail should the assistant include while asking?",
					options: [
						{ value: "brief", label: "Brief" },
						{ value: "balanced", label: "Balanced" },
						{ value: "detailed", label: "Detailed" },
					],
				},
			];
			const result = await runQuestionnaireUi(ctx, "Questionnaire Demo", questions);
			if (result.cancelled) {
				ctx.ui.notify("Questionnaire demo cancelled", "info");
				return;
			}
			ctx.ui.setEditorText(result.submittedText || renderSubmittedText("Questionnaire Demo", questions, result.answers));
			ctx.ui.notify("Demo answers copied into the editor. Press Enter to send them, or edit first.", "info");
		},
	});

	pi.registerTool({
		name: "questionnaire",
		label: "Questionnaire",
		description:
			"Ask the user an interactive multi-question clarification quiz in the pi TUI. Use when you need several pieces of information. Each question supports supplied options, custom written answers, editing previous answers, and adding notes/extensions before final submission.",
		promptSnippet: "Ask the user an interactive multi-question questionnaire with selectable options and custom answers.",
		promptGuidelines: [
			"Use questionnaire instead of plain numbered clarification lists when you need multiple answers from the user.",
			"When using questionnaire, provide concise option labels and include enough options to cover likely answers, but rely on the built-in custom answer path for anything not listed.",
		],
		parameters: QuestionnaireParams,

		async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
			const questions = normalizeQuestions(params);
			const title = params.title || "Questionnaire";

			if (!ctx.hasUI) {
				return errorResult("Error: questionnaire requires interactive UI mode.", questions);
			}
			if (questions.length === 0) {
				return errorResult("Error: questionnaire received no questions.", questions);
			}

			const result = await ctx.ui.custom<QuestionnaireResult>((tui, theme, _keybindings, done) => {
				let currentQuestionIndex = 0;
				let selectedOptionIndex = 0;
				let inputMode: "none" | "custom" | "extend" = "none";
				let inputQuestionId: string | undefined;
				let inputOption: { option: QuestionOption; optionIndex: number } | undefined;
				let cachedWidth: number | undefined;
				let cachedLines: string[] | undefined;
				const answers = new Map<string, Answer>();

				const editorTheme: EditorTheme = {
					borderColor: (s: string) => theme.fg("accent", s),
					selectList: {
						selectedPrefix: (s: string) => theme.fg("accent", s),
						selectedText: (s: string) => theme.fg("accent", s),
						description: (s: string) => theme.fg("muted", s),
						scrollInfo: (s: string) => theme.fg("dim", s),
						noMatch: (s: string) => theme.fg("warning", s),
					},
				};
				const editor = new Editor(tui, editorTheme);

				function refresh() {
					cachedWidth = undefined;
					cachedLines = undefined;
					tui.requestRender();
				}

				function currentQuestion(): Question | undefined {
					return questions[currentQuestionIndex];
				}

				function isReviewScreen(): boolean {
					return currentQuestionIndex === questions.length;
				}

				function currentOptions(): RenderOption[] {
					const question = currentQuestion();
					if (!question) return [];
					return [...question.options, { value: "__custom__", label: "Write my own response", isCustom: true }];
				}

				function allAnswered(): boolean {
					return questions.every((question) => answers.has(question.id));
				}

				function saveAnswer(answer: Answer) {
					answers.set(answer.id, answer);
				}

				function moveToNext() {
					currentQuestionIndex = Math.min(questions.length, currentQuestionIndex + 1);
					selectedOptionIndex = 0;
					refresh();
				}

				function moveToPrevious() {
					currentQuestionIndex = Math.max(0, currentQuestionIndex - 1);
					selectedOptionIndex = 0;
					refresh();
				}

				function startCustomInput(question: Question) {
					inputMode = "custom";
					inputQuestionId = question.id;
					inputOption = undefined;
					editor.setText(answers.get(question.id)?.kind === "custom" ? answers.get(question.id)?.label || "" : "");
					refresh();
				}

				function startExtendInput(question: Question, option: QuestionOption, optionIndex: number) {
					inputMode = "extend";
					inputQuestionId = question.id;
					inputOption = { option, optionIndex };
					const existing = answers.get(question.id);
					editor.setText(existing?.kind === "option" && existing.value === option.value ? existing.extension || "" : "");
					refresh();
				}

				function closeInputMode() {
					inputMode = "none";
					inputQuestionId = undefined;
					inputOption = undefined;
					editor.setText("");
					refresh();
				}

				editor.onSubmit = (value: string) => {
					const questionId = inputQuestionId;
					if (!questionId) return;
					const trimmed = value.trim();
					if (inputMode === "custom") {
						saveAnswer({
							id: questionId,
							kind: "custom",
							value: trimmed || "(no response)",
							label: trimmed || "(no response)",
						});
					} else if (inputMode === "extend" && inputOption) {
						saveAnswer({
							id: questionId,
							kind: "option",
							value: inputOption.option.value,
							label: inputOption.option.label,
							optionIndex: inputOption.optionIndex + 1,
							extension: trimmed,
						});
					}
					closeInputMode();
					moveToNext();
				};

				function submit(cancelled: boolean) {
					const finalAnswers = questions
						.map((question) => answers.get(question.id))
						.filter((answer): answer is Answer => answer !== undefined);
					const submittedText = cancelled
						? "User cancelled the questionnaire."
						: renderSubmittedText(title, questions, finalAnswers);
					done({ questions, answers: finalAnswers, cancelled, submittedText });
				}

				function handleInput(data: string) {
					if (inputMode !== "none") {
						if (matchesKey(data, Key.escape)) {
							closeInputMode();
							return;
						}
						editor.handleInput(data);
						refresh();
						return;
					}

					if (matchesKey(data, Key.escape)) {
						submit(true);
						return;
					}

					if (matchesKey(data, Key.left) || matchesKey(data, Key.shift("tab"))) {
						moveToPrevious();
						return;
					}
					if (matchesKey(data, Key.right) || matchesKey(data, Key.tab)) {
						moveToNext();
						return;
					}

					if (isReviewScreen()) {
						if (matchesKey(data, Key.enter) && allAnswered()) submit(false);
						return;
					}

					const question = currentQuestion();
					const options = currentOptions();
					if (!question) return;

					if (matchesKey(data, Key.up)) {
						selectedOptionIndex = Math.max(0, selectedOptionIndex - 1);
						refresh();
						return;
					}
					if (matchesKey(data, Key.down)) {
						selectedOptionIndex = Math.min(options.length - 1, selectedOptionIndex + 1);
						refresh();
						return;
					}

					const option = options[selectedOptionIndex];
					if ((data === "e" || data === "E") && option && !option.isCustom) {
						startExtendInput(question, option, selectedOptionIndex);
						return;
					}

					if (matchesKey(data, Key.enter) && option) {
						if (option.isCustom) {
							startCustomInput(question);
							return;
						}
						saveAnswer({
							id: question.id,
							kind: "option",
							value: option.value,
							label: option.label,
							optionIndex: selectedOptionIndex + 1,
						});
						moveToNext();
					}
				}

				function addWrapped(lines: string[], text: string, width: number, indent = "") {
					const wrapped = wrapTextWithAnsi(text, Math.max(1, width - indent.length));
					for (const line of wrapped) lines.push(truncateToWidth(indent + line, width));
				}

				function renderProgress(width: number): string[] {
					const chunks: string[] = [];
					for (let i = 0; i < questions.length; i++) {
						const question = questions[i];
						const answered = answers.has(question.id);
						const active = i === currentQuestionIndex;
						const mark = answered ? "■" : "□";
						const raw = ` ${mark} ${i + 1}:${question.label} `;
						chunks.push(active ? theme.bg("selectedBg", theme.fg("text", raw)) : theme.fg(answered ? "success" : "muted", raw));
					}
					const submitRaw = " ✓ Submit ";
					chunks.push(isReviewScreen() ? theme.bg("selectedBg", theme.fg("text", submitRaw)) : theme.fg(allAnswered() ? "success" : "dim", submitRaw));
					return wrapTextWithAnsi(chunks.join(" "), width).map((line) => truncateToWidth(line, width));
				}

				function render(width: number): string[] {
					if (cachedLines && cachedWidth === width) return cachedLines;
					const lines: string[] = [];
					const border = theme.fg("accent", "─".repeat(Math.max(0, width)));
					const add = (text = "") => lines.push(truncateToWidth(text, width));

					add(border);
					add(theme.fg("accent", theme.bold(` ${title}`)));
					for (const line of renderProgress(width)) add(line);
					add("");

					if (inputMode !== "none") {
						const question = questions.find((q) => q.id === inputQuestionId);
						if (question) addWrapped(lines, theme.fg("text", question.prompt), width, " ");
						add("");
						if (inputMode === "custom") {
							add(theme.fg("muted", " Write your custom response:"));
						} else if (inputOption) {
							add(theme.fg("muted", ` Extend selected option: ${inputOption.option.label}`));
							add(theme.fg("dim", " Leave blank and press Enter to select without extra notes."));
						}
						for (const line of editor.render(Math.max(1, width - 2))) add(` ${line}`);
						add("");
						add(theme.fg("dim", " Enter submit text • Esc return to options"));
						add(border);
						cachedWidth = width;
						cachedLines = lines;
						return lines;
					}

					if (isReviewScreen()) {
						add(theme.fg("accent", theme.bold(" Review before submission")));
						add("");
						for (let i = 0; i < questions.length; i++) {
							const question = questions[i];
							const answer = answers.get(question.id);
							addWrapped(lines, theme.fg("text", `${i + 1}. ${question.prompt}`), width, " ");
							if (answer) {
								const answerText = answer.kind === "custom"
									? `Answer: user wrote: ${answer.label}`
									: `Answer: user selected: ${answer.optionIndex}. ${answer.label}`;
								addWrapped(lines, theme.fg("success", answerText), width, "   ");
								if (answer.extension?.trim()) addWrapped(lines, theme.fg("muted", `Note/extension: ${answer.extension.trim()}`), width, "   ");
							} else {
								add(theme.fg("warning", "   Answer: missing"));
							}
							add("");
						}
						if (allAnswered()) add(theme.fg("success", " Enter submit • ←/Shift+Tab go back and change answers • Esc cancel"));
						else add(theme.fg("warning", " Some questions are unanswered. Use ←/Shift+Tab to go back."));
						add(border);
						cachedWidth = width;
						cachedLines = lines;
						return lines;
					}

					const question = currentQuestion();
					const options = currentOptions();
					if (question) {
						addWrapped(lines, theme.fg("text", `${currentQuestionIndex + 1}. ${question.prompt}`), width, " ");
						const existing = answers.get(question.id);
						if (existing) {
							const summary = existing.kind === "custom" ? `(current: custom — ${existing.label})` : `(current: ${existing.optionIndex}. ${existing.label}${existing.extension ? " + note" : ""})`;
							addWrapped(lines, theme.fg("muted", summary), width, " ");
						}
						add("");
						for (let i = 0; i < options.length; i++) {
							const option = options[i];
							const selected = i === selectedOptionIndex;
							const prefix = selected ? theme.fg("accent", "> ") : "  ";
							const label = option.isCustom ? `${i + 1}. ${option.label} ✎` : `${i + 1}. ${option.label}`;
							addWrapped(lines, `${prefix}${theme.fg(selected ? "accent" : "text", label)}`, width, "");
							if (option.description) addWrapped(lines, theme.fg("muted", option.description), width, "     ");
						}
					}
					add("");
					add(theme.fg("dim", " ↑↓ select • Enter choose • e extend option • Tab/→ next • Shift+Tab/← previous • Esc cancel"));
					add(border);

					cachedWidth = width;
					cachedLines = lines;
					return lines;
				}

				return { render, invalidate: refresh, handleInput };
			});

			if (result.cancelled) {
				return {
					content: [{ type: "text", text: result.submittedText || "User cancelled the questionnaire." }],
					details: result,
				};
			}

			const submittedText = result.submittedText || renderSubmittedText(title, questions, result.answers);
			return {
				content: [{ type: "text", text: submittedText }],
				details: { ...result, submittedText },
			};
		},

		renderCall(args, theme, _context) {
			const questionCount = Array.isArray(args.questions) ? args.questions.length : 0;
			const title = typeof args.title === "string" ? args.title : "Questionnaire";
			let text = theme.fg("toolTitle", theme.bold("questionnaire "));
			text += theme.fg("muted", `${title}: ${questionCount} question${questionCount === 1 ? "" : "s"}`);
			return new Text(text, 0, 0);
		},

		renderResult(result, _options, theme, _context) {
			const details = result.details as QuestionnaireResult | undefined;
			if (!details) {
				const text = result.content.find((part): part is { type: "text"; text: string } => part.type === "text")?.text || "";
				return new Text(text, 0, 0);
			}
			if (details.cancelled) {
				return new Text(theme.fg("warning", details.submittedText || "Questionnaire cancelled"), 0, 0);
			}
			return new Text(theme.fg("success", "✓ Submitted questionnaire") + "\n" + (details.submittedText || ""), 0, 0);
		},
	});
}
