/**
 * Shared next-action prompt for SF extensions.
 *
 * Renders a consistent "step complete" UI at the end of every SF stage:
 *
 *   ─────────────────────────────────────────
 *   ✓  Phase 1 research complete
 *
 *      [caller summary lines]
 *
 *      [optional extra content block]
 *
 *   Files written:
 *      .sf/phases/01-foo/01-RESEARCH.md
 *
 *   › 1. Plan phase 1           ← recommended, pre-selected
 *        Create PLAN.md files for execution
 *
 *     2. Not yet
 *        Run /sf-plan-phase 1 when ready.
 *   ─────────────────────────────────────────
 *
 * Usage:
 *
 *   const choice = await showNextAction(ctx, {
 *     title: "Phase 1 research complete",
 *     summary: ["6 libraries evaluated", "Stack: Phaser 3 + TypeScript"],
 *     files: ["/abs/path/to/01-RESEARCH.md"],
 *     extra: ["Wave 1: 01-01, 01-02  (parallel)", "Wave 2: 01-03"],
 *     actions: [
 *       { id: "plan",  label: "Plan phase 1",   description: "Create PLAN.md files for execution", recommended: true },
 *       { id: "later", label: "Discuss first",  description: "Capture constraints before planning" },
 *     ],
 *     notYetMessage: "Run /sf-plan-phase 1 when ready.",
 *   });
 *
 *   // choice is one of the action ids, or "not_yet"
 *   if (choice === "plan") { ... }
 *
 * "Not yet" is always appended automatically as the last option.
 * Pressing Escape also resolves as "not_yet".
 */
import { Key, matchesKey } from "@singularity-forge/tui";
import { makeUI } from "./ui.js";
/**
 * Show the next-action prompt and return the chosen action id, or "not_yet".
 */
export async function showNextAction(ctx, opts) {
	const cwd = opts.cwd ?? process.cwd();
	const notYetMessage = opts.notYetMessage ?? "Continue when ready.";
	const allActions = [
		...opts.actions,
		{ id: "not_yet", label: "Not yet", description: notYetMessage },
	];
	const recommendedIdx = allActions.findIndex((a) => a.recommended);
	const defaultIdx = recommendedIdx >= 0 ? recommendedIdx : 0;
	const relativeFiles = (opts.files ?? []).map((f) => {
		try {
			const rel = f.startsWith(cwd)
				? f.slice(cwd.length).replace(/^\//, "")
				: f;
			return rel || f;
		} catch {
			return f;
		}
	});
	const result = await ctx.ui.custom((_tui, theme, _kb, done) => {
		let cursorIdx = defaultIdx;
		let cachedLines;
		function refresh() {
			cachedLines = undefined;
			_tui.requestRender();
		}
		function handleInput(data) {
			if (matchesKey(data, Key.up)) {
				cursorIdx = Math.max(0, cursorIdx - 1);
				refresh();
				return;
			}
			if (matchesKey(data, Key.down)) {
				cursorIdx = Math.min(allActions.length - 1, cursorIdx + 1);
				refresh();
				return;
			}
			const num = parseInt(data, 10);
			if (!Number.isNaN(num) && num >= 1 && num <= allActions.length) {
				done(allActions[num - 1].id);
				return;
			}
			if (matchesKey(data, Key.enter) || matchesKey(data, Key.space)) {
				done(allActions[cursorIdx].id);
				return;
			}
			if (matchesKey(data, Key.escape)) {
				done("not_yet");
				return;
			}
		}
		function render(width) {
			if (cachedLines) return cachedLines;
			const ui = makeUI(theme, width);
			const lines = [];
			const push = (...rows) => {
				for (const r of rows) lines.push(...r);
			};
			// ── Header — uses success colour to signal completion ────────────
			// Note: next-action intentionally uses "success" for its bar/title
			// to distinguish it from regular accent-coloured screens.
			push(ui.bar());
			push(ui.blank());
			push(ui.header(`  ✓  ${opts.title}`));
			// ── Summary ──────────────────────────────────────────────────────
			if (opts.summary && opts.summary.length > 0) {
				push(ui.blank());
				for (const line of opts.summary) push(ui.subtitle(`     ${line}`));
			}
			// ── Files written ─────────────────────────────────────────────────
			if (relativeFiles.length > 0) {
				push(ui.blank());
				push(ui.meta("  Files written:"));
				for (const f of relativeFiles) push(ui.meta(`     ${f}`));
			}
			// ── Extra content ─────────────────────────────────────────────────
			if (opts.extra && opts.extra.length > 0) {
				push(ui.blank());
				for (const line of opts.extra) push(ui.subtitle(`  ${line}`));
			}
			// ── Actions ───────────────────────────────────────────────────────
			push(ui.blank());
			for (let i = 0; i < allActions.length; i++) {
				const action = allActions[i];
				const isSelected = i === cursorIdx;
				const isNotYet = action.id === "not_yet";
				const tag = action.recommended ? "(recommended)" : undefined;
				if (isSelected) {
					push(ui.actionSelected(i + 1, action.label, action.description, tag));
				} else if (isNotYet) {
					push(ui.actionDim(i + 1, action.label, action.description));
				} else {
					push(
						ui.actionUnselected(i + 1, action.label, action.description, tag),
					);
				}
				push(ui.blank());
			}
			// ── Footer ────────────────────────────────────────────────────────
			const numHint = allActions.map((_, i) => `${i + 1}`).join("/");
			push(
				ui.hints([
					`↑/↓ to choose`,
					`${numHint} to quick-select`,
					`enter to confirm`,
				]),
			);
			push(ui.bar());
			cachedLines = lines;
			return lines;
		}
		return {
			render,
			invalidate: () => {
				cachedLines = undefined;
			},
			handleInput,
		};
	});
	// Fallback for RPC mode where ctx.ui.custom() returns undefined (#447).
	// Fall back to ctx.ui.select() which IS implemented in RPC mode.
	if (result === undefined || result === null) {
		const labels = allActions.map((a) => {
			const tag = a.recommended ? " (recommended)" : "";
			return `${a.label}${tag}: ${a.description}`;
		});
		const selected = await ctx.ui.select(opts.title, labels);
		if (selected === undefined || selected === null) return "not_yet";
		const idx = labels.indexOf(selected);
		return idx >= 0 ? allActions[idx].id : "not_yet";
	}
	return result;
}
