import type { ThinkingLevel } from "@singularity-forge/agent-core";
import type { Transport } from "@singularity-forge/ai";
import {
	Container,
	getCapabilities,
	type SelectItem,
	SelectList,
	type SettingItem,
	SettingsList,
	Spacer,
	Text,
} from "@singularity-forge/tui";
import { PROXY_FAMILY_PRIORITY } from "../../../core/model-registry.js";
import {
	getSelectListTheme,
	getSettingsListTheme,
	theme,
} from "../theme/theme.js";
import { DynamicBorder } from "./dynamic-border.js";

export const THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {
	off: "No reasoning",
	minimal: "Very brief reasoning (~1k tokens)",
	low: "Light reasoning (~2k tokens)",
	medium: "Moderate reasoning (~8k tokens)",
	high: "Deep reasoning (~16k tokens)",
	xhigh: "Maximum reasoning (~32k tokens)",
};

export interface SettingsConfig {
	autoCompact: boolean;
	showImages: boolean;
	autoResizeImages: boolean;
	blockImages: boolean;
	enableSkillCommands: boolean;
	steeringMode: "all" | "one-at-a-time";
	followUpMode: "all" | "one-at-a-time";
	transport: Transport;
	thinkingLevel: ThinkingLevel;
	availableThinkingLevels: ThinkingLevel[];
	currentTheme: string;
	availableThemes: string[];
	hideThinkingBlock: boolean;
	collapseChangelog: boolean;
	doubleEscapeAction: "fork" | "tree" | "none";
	treeFilterMode: "default" | "no-tools" | "user-only" | "labeled-only" | "all";
	showHardwareCursor: boolean;
	editorPaddingX: number;
	autocompleteMaxVisible: number;
	respectGitignoreInPicker: boolean;
	quietStartup: boolean;
	clearOnShrink: boolean;
	timestampFormat: "date-time-iso" | "date-time-us";
	/** Top-preferred provider per model family for proxy resolution.
	 *  Keyed by family prefix (e.g. "gemini-", "claude-"). */
	proxyFamilyProviders: Record<string, string>;
}

export interface SettingsCallbacks {
	onAutoCompactChange: (enabled: boolean) => void;
	onShowImagesChange: (enabled: boolean) => void;
	onAutoResizeImagesChange: (enabled: boolean) => void;
	onBlockImagesChange: (blocked: boolean) => void;
	onEnableSkillCommandsChange: (enabled: boolean) => void;
	onSteeringModeChange: (mode: "all" | "one-at-a-time") => void;
	onFollowUpModeChange: (mode: "all" | "one-at-a-time") => void;
	onTransportChange: (transport: Transport) => void;
	onThinkingLevelChange: (level: ThinkingLevel) => void;
	onThemeChange: (theme: string) => void;
	onThemePreview?: (theme: string) => void;
	onHideThinkingBlockChange: (hidden: boolean) => void;
	onCollapseChangelogChange: (collapsed: boolean) => void;
	onDoubleEscapeActionChange: (action: "fork" | "tree" | "none") => void;
	onTreeFilterModeChange: (
		mode: "default" | "no-tools" | "user-only" | "labeled-only" | "all",
	) => void;
	onShowHardwareCursorChange: (enabled: boolean) => void;
	onEditorPaddingXChange: (padding: number) => void;
	onAutocompleteMaxVisibleChange: (maxVisible: number) => void;
	onRespectGitignoreInPickerChange: (enabled: boolean) => void;
	onQuietStartupChange: (enabled: boolean) => void;
	onClearOnShrinkChange: (enabled: boolean) => void;
	onTimestampFormatChange: (format: "date-time-iso" | "date-time-us") => void;
	onProxyFamilyProviderChange: (
		familyPrefix: string,
		preferredProvider: string,
	) => void;
	onCancel: () => void;
}

/**
 * A submenu component for selecting from a list of options.
 */
export class SelectSubmenu extends Container {
	private selectList: SelectList;

	constructor(
		title: string,
		description: string,
		options: SelectItem[],
		currentValue: string,
		onSelect: (value: string) => void,
		onCancel: () => void,
		onSelectionChange?: (value: string) => void,
	) {
		super();

		// Title
		this.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));

		// Description
		if (description) {
			this.addChild(new Spacer(1));
			this.addChild(new Text(theme.fg("muted", description), 0, 0));
		}

		// Spacer
		this.addChild(new Spacer(1));

		// Select list
		this.selectList = new SelectList(
			options,
			Math.min(options.length, 10),
			getSelectListTheme(),
		);

		// Pre-select current value
		const currentIndex = options.findIndex((o) => o.value === currentValue);
		if (currentIndex !== -1) {
			this.selectList.setSelectedIndex(currentIndex);
		}

		this.selectList.onSelect = (item) => {
			onSelect(item.value);
		};

		this.selectList.onCancel = onCancel;

		if (onSelectionChange) {
			this.selectList.onSelectionChange = (item) => {
				onSelectionChange(item.value);
			};
		}

		this.addChild(this.selectList);

		// Hint
		this.addChild(new Spacer(1));
		this.addChild(
			new Text(theme.fg("dim", "  Enter to select · Esc to go back"), 0, 0),
		);
	}

	handleInput(data: string): void {
		this.selectList.handleInput(data);
	}
}

/**
 * Two-level submenu for proxy provider priority:
 *   Level 1 — select a model family
 *   Level 2 — select preferred provider for that family
 *
 * Selecting a provider calls onChange(familyPrefix, provider) and closes via onDone.
 * Pressing Escape on level 2 returns to level 1; Escape on level 1 calls onCancel.
 */
export class ProxyPrioritySubmenu extends Container {
	private selectList!: SelectList;

	constructor(
		private proxyFamilyProviders: Record<string, string>,
		private onChange: (familyPrefix: string, provider: string) => void,
		_onDone: () => void,
		private onCancel: () => void,
	) {
		super();
		this.showFamilyList();
	}

	private clearAndRebuild(
		title: string,
		description: string,
		items: SelectItem[],
		onSelect: (item: SelectItem) => void,
		onEscape: () => void,
	): void {
		this.clear();
		this.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
		if (description) {
			this.addChild(new Spacer(1));
			this.addChild(new Text(theme.fg("muted", description), 0, 0));
		}
		this.addChild(new Spacer(1));
		this.selectList = new SelectList(
			items,
			Math.min(items.length, 12),
			getSelectListTheme(),
		);
		this.selectList.onSelect = onSelect;
		this.selectList.onCancel = onEscape;
		this.addChild(this.selectList);
		this.addChild(new Spacer(1));
		this.addChild(
			new Text(theme.fg("dim", "  Enter to select · Esc to go back"), 0, 0),
		);
	}

	private showFamilyList(): void {
		const families = PROXY_FAMILY_PRIORITY.filter(
			(r) => r.providers.length > 0,
		);
		const familyItems: SelectItem[] = families.map((r) => {
			const current = this.proxyFamilyProviders[r.prefix] ?? r.providers[0];
			return {
				value: r.prefix,
				label: r.prefix.replace(/-+$/i, ""),
				description: `Current: ${current}`,
			};
		});
		this.clearAndRebuild(
			"Proxy Priorities",
			"Select a model family to configure its preferred provider",
			familyItems,
			(item) => this.showProviderList(item.value),
			this.onCancel,
		);
	}

	private showProviderList(familyPrefix: string): void {
		const rule = PROXY_FAMILY_PRIORITY.find((r) => r.prefix === familyPrefix);
		if (!rule) return;
		const current =
			this.proxyFamilyProviders[familyPrefix] ?? rule.providers[0];
		const providerItems: SelectItem[] = rule.providers.map((p) => ({
			value: p,
			label: p,
			description: p === current ? "(current)" : "",
		}));
		this.clearAndRebuild(
			`${familyPrefix.replace(/-+$/i, "")} — choose provider`,
			`Preferred provider for ${familyPrefix}* proxy requests`,
			providerItems,
			(item) => {
				this.proxyFamilyProviders = {
					...this.proxyFamilyProviders,
					[familyPrefix]: item.value,
				};
				this.onChange(familyPrefix, item.value);
				this.showFamilyList(); // return to family list after selection
			},
			() => this.showFamilyList(), // Esc → back to family list
		);
		// Pre-select current provider
		const currentIndex = providerItems.findIndex((p) => p.value === current);
		if (currentIndex !== -1) this.selectList.setSelectedIndex(currentIndex);
	}

	handleInput(data: string): void {
		this.selectList.handleInput(data);
	}
}

/**
 * Main settings selector component.
 */
export class SettingsSelectorComponent extends Container {
	private settingsList: SettingsList;

	constructor(config: SettingsConfig, callbacks: SettingsCallbacks) {
		super();

		const supportsImages = getCapabilities().images;

		const items: SettingItem[] = [
			{
				id: "autocompact",
				label: "Auto-compact",
				description: "Automatically compact context when it gets too large",
				currentValue: config.autoCompact ? "true" : "false",
				values: ["true", "false"],
			},
			{
				id: "steering-mode",
				label: "Steering mode",
				description:
					"Enter while streaming queues steering messages. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.",
				currentValue: config.steeringMode,
				values: ["one-at-a-time", "all"],
			},
			{
				id: "follow-up-mode",
				label: "Follow-up mode",
				description: `${process.platform === "darwin" ? "⌥Enter" : "Alt+Enter"} queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.`,
				currentValue: config.followUpMode,
				values: ["one-at-a-time", "all"],
			},
			{
				id: "transport",
				label: "Transport",
				description:
					"Preferred transport for providers that support multiple transports",
				currentValue: config.transport,
				values: ["sse", "websocket", "auto"],
			},
			{
				id: "hide-thinking",
				label: "Hide thinking",
				description: "Hide thinking blocks in assistant responses",
				currentValue: config.hideThinkingBlock ? "true" : "false",
				values: ["true", "false"],
			},
			{
				id: "collapse-changelog",
				label: "Collapse changelog",
				description: "Show condensed changelog after updates",
				currentValue: config.collapseChangelog ? "true" : "false",
				values: ["true", "false"],
			},
			{
				id: "quiet-startup",
				label: "Quiet startup",
				description: "Disable verbose printing at startup",
				currentValue: config.quietStartup ? "true" : "false",
				values: ["true", "false"],
			},
			{
				id: "double-escape-action",
				label: "Double-escape action",
				description: "Action when pressing Escape twice with empty editor",
				currentValue: config.doubleEscapeAction,
				values: ["tree", "fork", "none"],
			},
			{
				id: "tree-filter-mode",
				label: "Tree filter mode",
				description: "Default filter when opening /tree",
				currentValue: config.treeFilterMode,
				values: ["default", "no-tools", "user-only", "labeled-only", "all"],
			},
			{
				id: "thinking",
				label: "Thinking level",
				description: "Reasoning depth for thinking-capable models",
				currentValue: config.thinkingLevel,
				submenu: (currentValue, done) =>
					new SelectSubmenu(
						"Thinking Level",
						"Select reasoning depth for thinking-capable models",
						config.availableThinkingLevels.map((level) => ({
							value: level,
							label: level,
							description: THINKING_DESCRIPTIONS[level],
						})),
						currentValue,
						(value) => {
							callbacks.onThinkingLevelChange(value as ThinkingLevel);
							done(value);
						},
						() => done(),
					),
			},
			{
				id: "theme",
				label: "Theme",
				description: "Color theme for the interface",
				currentValue: config.currentTheme,
				submenu: (currentValue, done) =>
					new SelectSubmenu(
						"Theme",
						"Select color theme",
						config.availableThemes.map((t) => ({
							value: t,
							label: t,
						})),
						currentValue,
						(value) => {
							callbacks.onThemeChange(value);
							done(value);
						},
						() => {
							// Restore original theme on cancel
							callbacks.onThemePreview?.(currentValue);
							done();
						},
						(value) => {
							// Preview theme on selection change
							callbacks.onThemePreview?.(value);
						},
					),
			},
		];

		// Only show image toggle if terminal supports it
		if (supportsImages) {
			// Insert after autocompact
			items.splice(1, 0, {
				id: "show-images",
				label: "Show images",
				description: "Render images inline in terminal",
				currentValue: config.showImages ? "true" : "false",
				values: ["true", "false"],
			});
		}

		// Image auto-resize toggle (always available, affects both attached and read images)
		items.splice(supportsImages ? 2 : 1, 0, {
			id: "auto-resize-images",
			label: "Auto-resize images",
			description:
				"Resize large images to 2000x2000 max for better model compatibility",
			currentValue: config.autoResizeImages ? "true" : "false",
			values: ["true", "false"],
		});

		// Block images toggle (always available, insert after auto-resize-images)
		const autoResizeIndex = items.findIndex(
			(item) => item.id === "auto-resize-images",
		);
		items.splice(autoResizeIndex + 1, 0, {
			id: "block-images",
			label: "Block images",
			description: "Prevent images from being sent to LLM providers",
			currentValue: config.blockImages ? "true" : "false",
			values: ["true", "false"],
		});

		// Skill commands toggle (insert after block-images)
		const blockImagesIndex = items.findIndex(
			(item) => item.id === "block-images",
		);
		items.splice(blockImagesIndex + 1, 0, {
			id: "skill-commands",
			label: "Skill commands",
			description: "Register skills as /skill:name commands",
			currentValue: config.enableSkillCommands ? "true" : "false",
			values: ["true", "false"],
		});

		// Hardware cursor toggle (insert after skill-commands)
		const skillCommandsIndex = items.findIndex(
			(item) => item.id === "skill-commands",
		);
		items.splice(skillCommandsIndex + 1, 0, {
			id: "show-hardware-cursor",
			label: "Show hardware cursor",
			description:
				"Show the terminal cursor while still positioning it for IME support",
			currentValue: config.showHardwareCursor ? "true" : "false",
			values: ["true", "false"],
		});

		// Editor padding toggle (insert after show-hardware-cursor)
		const hardwareCursorIndex = items.findIndex(
			(item) => item.id === "show-hardware-cursor",
		);
		items.splice(hardwareCursorIndex + 1, 0, {
			id: "editor-padding",
			label: "Editor padding",
			description: "Horizontal padding for input editor (0-3)",
			currentValue: String(config.editorPaddingX),
			values: ["0", "1", "2", "3"],
		});

		// Autocomplete max visible toggle (insert after editor-padding)
		const editorPaddingIndex = items.findIndex(
			(item) => item.id === "editor-padding",
		);
		items.splice(editorPaddingIndex + 1, 0, {
			id: "autocomplete-max-visible",
			label: "Autocomplete max items",
			description: "Max visible items in autocomplete dropdown (3-20)",
			currentValue: String(config.autocompleteMaxVisible),
			values: ["3", "5", "7", "10", "15", "20"],
		});

		// Clear on shrink toggle (insert after autocomplete-max-visible)
		const autocompleteIndex = items.findIndex(
			(item) => item.id === "autocomplete-max-visible",
		);
		items.splice(autocompleteIndex + 1, 0, {
			id: "clear-on-shrink",
			label: "Clear on shrink",
			description: "Clear empty rows when content shrinks (may cause flicker)",
			currentValue: config.clearOnShrink ? "true" : "false",
			values: ["true", "false"],
		});

		// Respect .gitignore in file picker toggle (insert after clear-on-shrink)
		const clearOnShrinkIndex = items.findIndex(
			(item) => item.id === "clear-on-shrink",
		);
		items.splice(clearOnShrinkIndex + 1, 0, {
			id: "respect-gitignore-in-picker",
			label: "Respect .gitignore in file picker",
			description: "When false, @ file picker shows gitignored files too",
			currentValue: config.respectGitignoreInPicker ? "true" : "false",
			values: ["true", "false"],
		});

		// Timestamp format (insert after respect-gitignore-in-picker)
		const gitignoreIndex = items.findIndex(
			(item) => item.id === "respect-gitignore-in-picker",
		);
		items.splice(gitignoreIndex + 1, 0, {
			id: "timestamp-format",
			label: "Timestamp format",
			description: "Date/time format for message timestamps",
			currentValue: config.timestampFormat,
			values: ["date-time-iso", "date-time-us"],
		});

		// Single entry that opens a two-level submenu: family → provider
		const timestampIndex = items.findIndex(
			(item) => item.id === "timestamp-format",
		);
		items.splice(timestampIndex + 1, 0, {
			id: "proxy-priorities",
			label: "Proxy priorities",
			description:
				"Configure preferred provider per model family for proxy requests",
			currentValue: "",
			values: [""],
			submenu: (_currentValue, done) =>
				new ProxyPrioritySubmenu(
					config.proxyFamilyProviders,
					(familyPrefix, provider) => {
						callbacks.onProxyFamilyProviderChange(familyPrefix, provider);
					},
					() => done(),
					() => done(),
				),
		});

		// Add borders
		this.addChild(new DynamicBorder());

		this.settingsList = new SettingsList(
			items,
			10,
			getSettingsListTheme(),
			(id, newValue) => {
				switch (id) {
					case "autocompact":
						callbacks.onAutoCompactChange(newValue === "true");
						break;
					case "show-images":
						callbacks.onShowImagesChange(newValue === "true");
						break;
					case "auto-resize-images":
						callbacks.onAutoResizeImagesChange(newValue === "true");
						break;
					case "block-images":
						callbacks.onBlockImagesChange(newValue === "true");
						break;
					case "skill-commands":
						callbacks.onEnableSkillCommandsChange(newValue === "true");
						break;
					case "steering-mode":
						callbacks.onSteeringModeChange(newValue as "all" | "one-at-a-time");
						break;
					case "follow-up-mode":
						callbacks.onFollowUpModeChange(newValue as "all" | "one-at-a-time");
						break;
					case "transport":
						callbacks.onTransportChange(newValue as Transport);
						break;
					case "hide-thinking":
						callbacks.onHideThinkingBlockChange(newValue === "true");
						break;
					case "collapse-changelog":
						callbacks.onCollapseChangelogChange(newValue === "true");
						break;
					case "quiet-startup":
						callbacks.onQuietStartupChange(newValue === "true");
						break;
					case "double-escape-action":
						callbacks.onDoubleEscapeActionChange(newValue as "fork" | "tree");
						break;
					case "tree-filter-mode":
						callbacks.onTreeFilterModeChange(
							newValue as
								| "default"
								| "no-tools"
								| "user-only"
								| "labeled-only"
								| "all",
						);
						break;
					case "show-hardware-cursor":
						callbacks.onShowHardwareCursorChange(newValue === "true");
						break;
					case "editor-padding":
						callbacks.onEditorPaddingXChange(parseInt(newValue, 10));
						break;
					case "autocomplete-max-visible":
						callbacks.onAutocompleteMaxVisibleChange(parseInt(newValue, 10));
						break;
					case "clear-on-shrink":
						callbacks.onClearOnShrinkChange(newValue === "true");
						break;
					case "respect-gitignore-in-picker":
						callbacks.onRespectGitignoreInPickerChange(newValue === "true");
						break;
					case "timestamp-format":
						callbacks.onTimestampFormatChange(
							newValue as "date-time-iso" | "date-time-us",
						);
						break;
				}
			},
			callbacks.onCancel,
			{ enableSearch: true },
		);

		this.addChild(this.settingsList);
		this.addChild(new DynamicBorder());
	}

	getSettingsList(): SettingsList {
		return this.settingsList;
	}
}
