/* eslint-disable ts/no-this-alias */
/* eslint-disable no-useless-call */

import type { VNode } from 'vue';

import type { DropdownButtonItemArgs } from '../dropdown-button/dropdown-button-item';
import type { FormItemWrapperArgs, MarginType } from '../form/form-item-wrapper';
import { Component, Prop, Watch } from 'vue-facing-decorator';
import PowerduckState from '../../app/powerduck-state';
import TsxComponent from '../../app/vuetsx';
import AccessibilityUtils from '../../common/utils/accessibility-utils';
import FormItemWrapper from '../form/form-item-wrapper';
import './css/smart-dropdown.scss';

// --- Resources ---
export class SmartDropdownResources {
	static placeholderDefault = 'Vyberte...';
	static doneButtonText = 'Hotovo';
	static loadingTextShort = '...';
	static loadingTextLong = 'Načítavam...';
	static noResultsSearch = 'Nenašli sa žiadne výsledky.';
	static noResultsDefault = 'Žiadne možnosti.';
	static searchResultTitle = 'Výsledky vyhľadávania';
	static selectedTitle = 'Vybrané';
	static categoriesTitle = 'Kategórie';
	static summarySuffix = ' vybrané';
	static closeLabel = PowerduckState.getResourceValue('close');
	static searchOnlyPrompt = 'Zadajte hľadaný výraz...';

	// Typewriter effect timing configuration (in milliseconds)
	static typewriterTypingSpeed = 50; // ms per character when typing
	static typewriterDeletingSpeed = 30; // ms per character when deleting
	static typewriterPauseAfterTyping = 1500; // ms to pause after completing a word
	static typewriterPauseAfterDeleting = 300; // ms to pause after deleting
	static typewriterFadeInDuration = 300; // ms for fade-in effect on last item

	/** Icon class for the mobile back button (e.g., 'gp gp-arrow-left' or 'fa fa-arrow-left') */
	static mobileBackIconClass: string | null = 'icon icon-arrow-left';
}

// --- Interfaces ---
export interface SmartDropdownCategoryItem {
	id: number | string;
	text: string;
	[key: string]: any;
}

export interface SmartDropdownSearchResultItem {
	id: number | string;
	text: string;
	subtitle?: string;
	imageUrl?: string;
	[key: string]: any;
}

export type SmartDropdownItem = SmartDropdownCategoryItem | SmartDropdownSearchResultItem;

export interface SmartDropdownSection {
	id: string;
	title?: string;
	items: SmartDropdownItem[];
}

/** Callback to change selection state of an item */
export type SelectionChangedCallback = (forceSelected?: boolean) => void;

/** Arguments passed to customListItemRender */
export interface CustomListItemRenderArgs {
	/** The item being rendered */
	item: SmartDropdownItem;
	/** Whether the item is currently selected */
	isSelected: boolean;
	/** Callback to change selection state. Call with true to select, false to deselect, or undefined to toggle */
	selectionChanged: SelectionChangedCallback;
	/** Renders the item using the default SmartDropdown renderer */
	baseRender: () => VNode;
}

interface SmartDropdownArgs extends FormItemWrapperArgs {
	categories: SmartDropdownCategoryItem[];
	customSections: SmartDropdownSection[];
	searchData: (text: string) => Promise<SmartDropdownSearchResultItem[]>;
	value: SmartDropdownItem[];
	multiselect: boolean;
	placeholder: string;
	selectionDisplay: 'chips' | 'text';
	buttonLayout: 'footer' | 'inline';
	searchMode: 'dropdown' | 'input';
	customTriggerScope?: 'mobile' | 'desktop' | 'all';
	/** Custom renderer for list items. Receives args object with item, isSelected, selectionChanged, and baseRender */
	customListItemRender?: (args: CustomListItemRenderArgs) => VNode;
	customTriggerRender?: () => VNode;
	changed: (e: SmartDropdownItem[]) => void;
	/** Called when an item is clicked. Return true to prevent default selection behavior. */
	onItemClick?: (item: SmartDropdownItem) => boolean;
	/** Delay in ms before showing the loading indicator. Default 650ms. */
	loadingIndicatorDelay?: number;
	/** Optional header shown at the top of the list to guide users. Can be a string or VNode. */
	listHeader?: string | VNode;
	/** Structured placeholder configuration with separate mobile/desktop settings */
	placeholderConfig?: SmartDropdownPlaceholderConfig;
	/** When true, only shows search results (no sections/categories until user searches) */
	searchOnlyMode?: boolean;
	/** Called before the dropdown opens. If defined, must resolve before dropdown opens. */
	beforeDropdownOpen?: (context: BeforeDropdownOpenContext) => Promise<void>;
	/** Called before the dropdown closes. */
	beforeDropdownClose?: () => void;
	/** When true, hides the confirm button (useful for search-only mode) */
	hideConfirmButton?: boolean;
	/** Debounce timeout in ms for search input. Set to 0 for no debouncing (instant search). Default 800ms. */
	searchDebounceTimeout?: number;
	/**
	 * Called when the user confirms via Enter key. Fires after any selection/auto-match
	 * has been applied and the dropdown has been closed. Useful for advancing focus to
	 * the next field in a multi-step form.
	 */
	confirmedByEnter?: () => void;
}

/** Context passed to beforeDropdownOpen callback */
export interface BeforeDropdownOpenContext {
	/** How the dropdown open was triggered */
	trigger: 'click' | 'programmatic';
}

/** Placeholder effect type */
export type PlaceholderEffect = 'typewriter' | 'none';

/** Placeholder configuration for a specific viewport */
export interface PlaceholderViewportConfig {
	/** Array of placeholder strings. For typewriter effect, cycles through them. For 'none', uses first value. */
	values: string[];
	/** Effect type: 'typewriter' animates through values, 'none' shows first value statically */
	effect: PlaceholderEffect;
}

/** Structured placeholder configuration */
export interface SmartDropdownPlaceholderConfig {
	mobile: PlaceholderViewportConfig;
	desktop: PlaceholderViewportConfig;
}

@Component
export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> implements SmartDropdownArgs {
	@Prop() label!: string;
	@Prop() labelButtons!: DropdownButtonItemArgs[];
	@Prop() subtitle!: string;
	@Prop() cssClass!: string;
	@Prop() mandatory!: boolean;
	@Prop() disabled!: boolean;
	@Prop() wrap!: boolean;
	@Prop() hint: string;
	@Prop() appendIcon: string;
	@Prop() prependIcon: string;
	@Prop() maxWidth?: number;
	@Prop() marginType?: MarginType;
	@Prop() appendClicked: () => void;
	@Prop() prependClicked: () => void;
	@Prop() prependIconClicked: () => void;
	@Prop() appendIconClicked: () => void;
	@Prop() keyDown: (e: KeyboardEvent) => void;
	@Prop() keyUp: (e: KeyboardEvent) => void;
	@Prop() enterPressed: (e: KeyboardEvent) => void;
	@Prop() showClearValueButton!: boolean;

	// --- Props from SmartDropdown---
	@Prop({ type: Array, required: true }) readonly categories!: SmartDropdownCategoryItem[];
	@Prop({ type: Array, default: () => [] }) readonly customSections!: SmartDropdownSection[];
	@Prop({ type: Function, required: true }) readonly searchData!: (text: string) => Promise<SmartDropdownSearchResultItem[]>;
	@Prop({ type: Array, default: () => [] }) readonly value!: SmartDropdownItem[];
	@Prop({ type: Boolean, default: false }) readonly multiselect!: boolean;
	@Prop({ type: String, default: () => SmartDropdownResources.placeholderDefault }) readonly placeholder!: string;
	@Prop({ type: String, default: 'text' }) readonly selectionDisplay!: 'chips' | 'text';
	@Prop({ type: String, default: 'footer' }) readonly buttonLayout!: 'footer' | 'inline';
	@Prop({ type: String, default: 'dropdown' }) readonly searchMode!: 'dropdown' | 'input';
	@Prop({ type: String, default: 'all' }) readonly customTriggerScope!: 'mobile' | 'desktop' | 'all';
	@Prop({ type: Function }) readonly customListItemRender?: (args: CustomListItemRenderArgs) => VNode;
	@Prop({ type: Function }) readonly customTriggerRender?: () => VNode;
	@Prop() readonly changed: (e: SmartDropdownItem[]) => void;
	@Prop({ type: Function }) readonly onItemClick?: (item: SmartDropdownItem) => boolean;
	@Prop({ type: Number, default: 650 }) readonly loadingIndicatorDelay!: number;
	@Prop() readonly listHeader?: string | VNode;
	@Prop({ type: Object }) readonly placeholderConfig?: SmartDropdownPlaceholderConfig;
	@Prop({ type: Boolean, default: false }) readonly searchOnlyMode!: boolean;
	@Prop({ type: Function }) readonly beforeDropdownOpen?: (context: BeforeDropdownOpenContext) => Promise<void>;
	@Prop({ type: Function }) readonly beforeDropdownClose?: () => void;
	@Prop({ type: Boolean, default: null }) readonly hideConfirmButton!: boolean | null;
	@Prop({ type: Number, default: 800 }) readonly searchDebounceTimeout!: number;
	@Prop({ type: Function }) readonly confirmedByEnter?: () => void;

	// --- State ---
	isOpen = false;
	isClosing = false;
	searchQuery = '';
	searchResults: SmartDropdownSearchResultItem[] = [];
	isLoading = false;
	isSearchPending = false;
	debounceTimer: number | null = null;
	loadingIndicatorTimer: number | null = null;
	focusedIndex = -1;
	triggerInputValue = '';

	// Typewriter effect state
	typewriterText = '';
	typewriterIndex = 0;
	typewriterCharIndex = 0;
	typewriterTimer: number | null = null;
	typewriterComplete = false;
	typewriterStarted = false;
	typewriterFadingIn = false; // True when showing last item with fade-in
	isMobileViewport = false;

	// Accessibility ID
	uid = `smart-dd-${Math.random().toString(36).slice(2, 9)}`;

	// --- Watchers ---
	@Watch('value', { deep: true, immediate: true })
	onSelectionChange() {
		if (!this.isOpen || this.searchQuery === '') {
			this.triggerInputValue = this.displayText;
		}
	}

	// --- Computed ---
	get listboxId(): string {
		return `${this.uid}-listbox`;
	}

	get activeDescendantId(): string | undefined {
		return this.focusedIndex >= 0 ? `${this.uid}-option-${this.focusedIndex}` : undefined;
	}

	get flattenedDisplayItems(): SmartDropdownItem[] {
		// Helper to get flat list for keyboard navigation
		if (this.isSearchActive) {
			return this.searchResults;
		}

		const items: SmartDropdownItem[] = [];
		if (this.pinnedSelectedItems.length > 0) {
			items.push(...this.pinnedSelectedItems);
		}

		this.customSections.forEach(s => items.push(...s.items));
		if (this.standardDisplayItems.length > 0) {
			items.push(...this.standardDisplayItems);
		}

		return items;
	}

	/** Normalizes text for case- and accent-insensitive comparison */
	private normalizeForMatch(text: string): string {
		if (text == null) {
			return '';
		}

		return text
			.toString()
			.toLowerCase()
			.normalize('NFD')
			.replace(/\p{Diacritic}/gu, '')
			.trim();
	}

	/**
	 * Finds an exact (case- and accent-insensitive) match for the given query
	 * across the currently visible items, custom sections, and categories.
	 */
	private findExactMatchForQuery(query: string): SmartDropdownItem | null {
		const normalizedQuery = this.normalizeForMatch(query);
		if (normalizedQuery.length === 0) {
			return null;
		}

		const candidates: SmartDropdownItem[] = [
			...this.searchResults,
			...this.customSections.flatMap(s => s.items),
			...this.categories,
		];

		return candidates.find(item => this.normalizeForMatch(item.text) === normalizedQuery) ?? null;
	}

	/**
	 * Handles the Enter key while the dropdown is open. Selects the focused item,
	 * or — if nothing is focused — tries to auto-select an exact match for the
	 * current search query. Always closes the dropdown and notifies the parent
	 * via confirmedByEnter so it can advance focus to the next field.
	 */
	private handleEnterKey() {
		if (this.focusedIndex >= 0) {
			this.handleItemClick(this.flattenedDisplayItems[this.focusedIndex]);
		} else if (this.isSearchActive) {
			const exactMatch = this.findExactMatchForQuery(this.searchQuery);
			if (exactMatch) {
				this.handleItemClick(exactMatch, true);
			}
		}

		this.confirmAndClose();
		this.confirmedByEnter?.();
	}

	get standardDisplayItems(): SmartDropdownItem[] {
		if (this.multiselect && this.selectionDisplay === 'text' && this.value.length > 0) {
			const selectedIds = new Set(this.value.map(i => i.id));
			return this.categories.filter(c => !selectedIds.has(c.id));
		}

		return this.categories;
	}

	get pinnedSelectedItems(): SmartDropdownItem[] {
		if (this.multiselect && this.selectionDisplay === 'text' && !this.isSearchActive) {
			return this.value;
		}

		return [];
	}

	get isSearchActive(): boolean {
		return this.searchQuery.trim().length > 0;
	}

	/** Gets the current viewport config based on screen size */
	get currentViewportConfig(): PlaceholderViewportConfig | null {
		if (!this.placeholderConfig) {
			return null;
		}

		return this.isMobileViewport ? this.placeholderConfig.mobile : this.placeholderConfig.desktop;
	}

	/** Returns the effective placeholder - either typewriter text or static placeholder */
	get effectivePlaceholder(): string {
		const config = this.currentViewportConfig;
		if (config && config.values.length > 0) {
			if (config.effect === 'typewriter' && this.typewriterStarted) {
				return this.typewriterText;
			}

			// For 'none' effect or typewriter not started yet, return first value
			return config.values[0];
		}

		return this.placeholder;
	}

	/** Returns true if typewriter effect is currently animating */
	get isTypewriterActive(): boolean {
		const config = this.currentViewportConfig;
		return !!(config && config.effect === 'typewriter' && this.typewriterStarted && !this.typewriterComplete);
	}

	/** Returns true if confirm button should be shown */
	get showConfirmButton(): boolean {
		// If explicitly set, use that value
		if (this.hideConfirmButton !== null) {
			return !this.hideConfirmButton;
		}

		// Default: hide in searchOnlyMode, show otherwise
		return !this.searchOnlyMode;
	}

	get displayText(): string {
		if (this.value.length === 0) {
			return '';
		}

		if (!this.multiselect) {
			return this.value[0].text;
		}

		if (this.value.length === 1) {
			return this.value[0].text;
		}

		return `${this.value.length}${SmartDropdownResources.summarySuffix}`;
	}

	mounted() {
		// Detect initial viewport
		this.updateViewport();
	}

	// Vue 3 hook — the previous `beforeDestroy` name is a Vue 2 API and never ran,
	// leaking the document-level click/keydown listeners on unmount-while-open.
	beforeUnmount() {
		this.removeEventHandlers();
		this.stopTypewriter();
	}

	// --- Methods ---

	/** Updates the viewport state based on current window width */
	updateViewport() {
		this.isMobileViewport = window.innerWidth <= 768;
	}

	/** Starts the typewriter effect if configured for current viewport */
	startTypewriter() {
		// Update viewport first
		this.updateViewport();

		const config = this.currentViewportConfig;
		if (!config || config.effect !== 'typewriter' || config.values.length === 0) {
			return;
		}

		// Respect accessibility: skip animation for users with reduced motion or screen readers
		if (AccessibilityUtils.shouldSkipAnimation()) {
			// Show the last string immediately without animation
			this.typewriterText = config.values[config.values.length - 1];
			this.typewriterComplete = true;
			this.typewriterStarted = true;
			return;
		}

		// Already started or completed
		if (this.typewriterStarted) {
			return;
		}

		this.typewriterStarted = true;
		this.typewriterIndex = 0;
		this.typewriterCharIndex = 0;
		this.typewriterComplete = false;
		this.typewriterFadingIn = false;
		this.typewriterText = '';

		// Use configurable speeds from resources
		const TYPING_SPEED = SmartDropdownResources.typewriterTypingSpeed;
		const DELETING_SPEED = SmartDropdownResources.typewriterDeletingSpeed;
		const PAUSE_AFTER_TYPING = SmartDropdownResources.typewriterPauseAfterTyping;
		const PAUSE_AFTER_DELETING = SmartDropdownResources.typewriterPauseAfterDeleting;

		let isDeleting = false;

		const tick = () => {
			const strings = config.values;
			const currentString = strings[this.typewriterIndex];
			const isLastString = this.typewriterIndex === strings.length - 1;
			const isSecondToLast = this.typewriterIndex === strings.length - 2;

			if (!isDeleting) {
				// Typing
				this.typewriterCharIndex++;
				this.typewriterText = currentString.substring(0, this.typewriterCharIndex);

				if (this.typewriterCharIndex === currentString.length) {
					// Finished typing current string
					if (isLastString) {
						// Last string - stop and keep it
						this.typewriterComplete = true;
						return;
					}

					// Pause then start deleting
					this.typewriterTimer = window.setTimeout(() => {
						isDeleting = true;
						tick();
					}, PAUSE_AFTER_TYPING);
					return;
				}
			} else {
				// Deleting
				this.typewriterCharIndex--;
				this.typewriterText = currentString.substring(0, this.typewriterCharIndex);

				if (this.typewriterCharIndex === 0) {
					// Finished deleting, move to next string
					isDeleting = false;
					this.typewriterIndex++;

					// If next string is the last one, use fade-in instead of typing
					if (isSecondToLast) {
						this.typewriterFadingIn = true;
						this.typewriterText = strings[strings.length - 1];
						// After fade completes, mark as done
						this.typewriterTimer = window.setTimeout(() => {
							this.typewriterFadingIn = false;
							this.typewriterComplete = true;
						}, SmartDropdownResources.typewriterFadeInDuration);
						return;
					}

					this.typewriterTimer = window.setTimeout(tick, PAUSE_AFTER_DELETING);
					return;
				}
			}

			this.typewriterTimer = window.setTimeout(tick, isDeleting ? DELETING_SPEED : TYPING_SPEED);
		};

		tick();
	}

	/** Stops the typewriter effect */
	stopTypewriter() {
		if (this.typewriterTimer) {
			clearTimeout(this.typewriterTimer);
			this.typewriterTimer = null;
		}
	}

	/** Opens the dropdown programmatically */
	openDropdown() {
		setTimeout(() => {
			this.toggleDropdownInternal(true, 'programmatic');
		}, 0);
	}

	/** Toggles the dropdown (public API, used by click handlers) */
	private toggleDropdown(force?: boolean) {
		this.toggleDropdownInternal(force, 'click');
	}

	/** Internal toggle implementation with trigger context */
	private async toggleDropdownInternal(force: boolean | undefined, trigger: 'click' | 'programmatic') {
		const nextState = typeof force === 'boolean' ? force : !this.isOpen;
		if (nextState) {
			// Call beforeDropdownOpen callback if defined
			if (this.beforeDropdownOpen) {
				try {
					await this.beforeDropdownOpen({ trigger });
				} catch {
					// If callback rejects, don't open the dropdown
					return;
				}
			}

			this.isOpen = true;
			this.focusedIndex = -1; // Reset focus logic

			// Start typewriter when dropdown opens (if configured)
			this.startTypewriter();

			this.$nextTick(() => {
				const isMobile = window.innerWidth <= 768;
				const dropdownInput = this.$el.querySelector('.dropdown-search-input') as HTMLInputElement;
				const triggerInput = this.$el.querySelector('.trigger-input') as HTMLInputElement;

				// Focus Logic
				const shouldUseExternalFocus = this.searchMode === 'input' && !isMobile && this.customTriggerScope !== 'desktop';

				if (shouldUseExternalFocus) {
					triggerInput?.select();
				} else {
					dropdownInput?.focus();
				}

				// Initial Scroll Logic
				if (this.pinnedSelectedItems.length > 0) {
					const listContainer = this.$el.querySelector('.list-container') as HTMLElement;
					const categoryHeader = this.$el.querySelector('.section-categories') as HTMLElement;
					if (listContainer && categoryHeader) {
						listContainer.scrollTop = categoryHeader.offsetTop;
					}
				}
			});

			this.removeEventHandlers();

			const self = this;
			this.handleClickOutside = (e: Event) => {
				if (!self.$el.contains(e.target as Node)) {
					self.closeDropdown.call(self);
				}
			};

			this.handleGlobalKeydown = (e: KeyboardEvent) => {
				if (!this.isOpen) {
					// Allow opening with Enter or Down Arrow/Space if focused on trigger
					if ((e.key === 'Enter' || e.key === 'ArrowDown' || e.key === ' ') && (e.target as HTMLElement).classList.contains('filter-trigger')) {
						self.toggleDropdownInternal.call(
							self,
							true,
							'click',
						);
						e.preventDefault();
					}

					return;
				}

				const totalItems = self.flattenedDisplayItems.length;
				switch (e.key) {
					case 'Escape':
						self.closeDropdown.call(self);
						e.preventDefault();
						break;
					case 'Enter':
						e.preventDefault();
						(self as any).handleEnterKey.call(self);
						break;
					case 'ArrowDown':
						e.preventDefault();
						self.focusedIndex = (self.focusedIndex + 1) % totalItems;
						self.scrollItemIntoView.call(self, self.focusedIndex);
						break;
					case 'ArrowUp':
						e.preventDefault();
						self.focusedIndex = (self.focusedIndex - 1 + totalItems) % totalItems;
						self.scrollItemIntoView.call(self, self.focusedIndex);
						break;
					case 'Home':
						e.preventDefault();
						self.focusedIndex = 0;
						self.scrollItemIntoView.call(self, self.focusedIndex);
						break;
					case 'End':
						e.preventDefault();
						self.focusedIndex = totalItems - 1;
						self.scrollItemIntoView.call(self, self.focusedIndex);
						break;
					case 'Tab':
						self.closeDropdown.call(self); // Tab out closes dropdown
						break;
				}
			};

			document.addEventListener('click', this.handleClickOutside);
			document.addEventListener('keydown', this.handleGlobalKeydown);
		} else {
			this.closeDropdown();
		}
	}

	removeEventHandlers() {
		if (this.handleClickOutside) {
			document.removeEventListener('click', this.handleClickOutside);
		}

		if (this.handleGlobalKeydown) {
			document.removeEventListener('keydown', this.handleGlobalKeydown);
		}
	}

	closeDropdown() {
		const isMobile = window.innerWidth <= 768;

		if (isMobile && this.isOpen && !this.isClosing) {
			// Trigger closing animation on mobile
			this.isClosing = true;

			// Listen for animation end to actually close
			this.$nextTick(() => {
				const dropdown = this.$el.querySelector('.filter-dropdown') as HTMLElement;
				if (dropdown) {
					let hasCompleted = false;
					this.beforeDropdownClose?.();

					const completeClose = () => {
						if (hasCompleted) {
							return;
						}

						hasCompleted = true;
						// eslint-disable-next-line ts/no-use-before-define
						dropdown.removeEventListener('animationend', onAnimationEnd);
						this.isOpen = false;
						this.isClosing = false;
						this.focusedIndex = -1;
						this.triggerInputValue = this.displayText;
						this.searchQuery = '';
					};

					const onAnimationEnd = () => completeClose();
					dropdown.addEventListener('animationend', onAnimationEnd);

					// Fallback timeout in case animationend doesn't fire
					setTimeout(completeClose, 1000);
				}
			});
		} else if (!this.isClosing) {
			// Desktop: close immediately
			this.isOpen = false;
			this.focusedIndex = -1;
			this.triggerInputValue = this.displayText;
			this.searchQuery = '';
			this.beforeDropdownClose?.();
		}
	}

	confirmAndClose() {
		if (this.isLoading) {
			return;
		}

		this.searchQuery = '';
		this.searchResults = [];
		this.closeDropdown();
	}

	handleClickOutside = (e: Event) => {
		if (!this.$el.contains(e.target as Node)) {
			this.closeDropdown();
		}
	};

	handleGlobalKeydown = (e: KeyboardEvent) => {
		if (!this.isOpen) {
			// Allow opening with Enter or Down Arrow/Space if focused on trigger
			if ((e.key === 'Enter' || e.key === 'ArrowDown' || e.key === ' ') && (e.target as HTMLElement).classList.contains('filter-trigger')) {
				this.toggleDropdown(true);
				e.preventDefault();
			}

			return;
		}

		const totalItems = this.flattenedDisplayItems.length;

		switch (e.key) {
			case 'Escape':
				this.closeDropdown();
				e.preventDefault();
				break;
			case 'Enter':
				e.preventDefault();
				(this as any).handleEnterKey();
				break;
			case 'ArrowDown':
				e.preventDefault();
				this.focusedIndex = (this.focusedIndex + 1) % totalItems;
				this.scrollItemIntoView(this.focusedIndex);
				break;
			case 'ArrowUp':
				e.preventDefault();
				this.focusedIndex = (this.focusedIndex - 1 + totalItems) % totalItems;
				this.scrollItemIntoView(this.focusedIndex);
				break;
			case 'Home':
				e.preventDefault();
				this.focusedIndex = 0;
				this.scrollItemIntoView(this.focusedIndex);
				break;
			case 'End':
				e.preventDefault();
				this.focusedIndex = totalItems - 1;
				this.scrollItemIntoView(this.focusedIndex);
				break;
			case 'Tab':
				this.closeDropdown(); // Tab out closes dropdown
				break;
		}
	};

	scrollItemIntoView(index: number) {
		// Use nextTick to ensure DOM is updated if virtualized (not here, but good practice)
		this.$nextTick(() => {
			const itemId = `${this.uid}-option-${index}`;
			const el = document.getElementById(itemId);
			el?.scrollIntoView({ block: 'nearest' });
		});
	}

	/**
	 * Suppresses the browser's default focus-driven scroll when a dropdown item
	 * containing a checkbox/radio input is clicked (QA__G-162). When the user
	 * clicks a `<label for="...">`, the browser focuses the associated input,
	 * which then auto-scrolls the document to bring the (often off-screen) input
	 * into view. We walk up to the `label[for]`, locate the associated input,
	 * call `preventDefault()` to suppress the auto-focus, then manually focus
	 * the input with `preventScroll: true`. The label's click-through toggle
	 * pipeline is preserved, so the existing `onClick`/`changed` handlers still
	 * fire and the input is still keyboard-focusable.
	 */
	private handleItemMousedown(e: MouseEvent): void {
		const target = e.target as HTMLElement | null;

		if (target == null) {
			return;
		}

		const label = target.closest('label[for]') as HTMLLabelElement | null;

		if (label == null) {
			return;
		}

		const inputId = label.getAttribute('for');

		if (!inputId) {
			return;
		}

		const input = document.getElementById(inputId) as HTMLInputElement | null;

		if (input == null) {
			return;
		}

		if (input.type !== 'checkbox' && input.type !== 'radio') {
			return;
		}

		e.preventDefault();
		input.focus({ preventScroll: true });
	}

	handleInput(e: Event) {
		const val = (e.target as HTMLInputElement).value;
		this.triggerInputValue = val;
		this.searchQuery = val;

		if (this.debounceTimer) {
			clearTimeout(this.debounceTimer);
		}

		if (this.loadingIndicatorTimer) {
			clearTimeout(this.loadingIndicatorTimer);
			this.loadingIndicatorTimer = null;
		}

		if (val.trim().length === 0) {
			this.isLoading = false;
			this.isSearchPending = false;
			this.searchResults = [];
			return;
		}

		this.isSearchPending = true;

		const executeSearch = async () => {
			// Start timer to show loading indicator after delay (only if debouncing)
			if (this.searchDebounceTimeout > 0) {
				this.loadingIndicatorTimer = window.setTimeout(() => {
					this.isLoading = true;
				}, this.loadingIndicatorDelay);
			}

			try {
				this.searchResults = await this.searchData(val);
			} catch (err) {
				this.searchResults = [];
			} finally {
				if (this.loadingIndicatorTimer) {
					clearTimeout(this.loadingIndicatorTimer);
					this.loadingIndicatorTimer = null;
				}

				this.isLoading = false;
				this.isSearchPending = false;
			}
		};

		// If debounce timeout is 0, execute search immediately without debouncing
		if (this.searchDebounceTimeout === 0) {
			executeSearch();
		} else {
			this.debounceTimer = window.setTimeout(executeSearch, this.searchDebounceTimeout);
		}
	}

	handleItemClick(item: SmartDropdownItem, forceSelected?: boolean) {
		// Allow parent to intercept and handle the click
		if (this.onItemClick?.(item)) {
			this.closeDropdown();
			return;
		}

		const isCurrentlySelected = this.isSelected(item);
		// Use forceSelected if provided, otherwise toggle based on current state
		const shouldBeSelected = forceSelected !== undefined ? forceSelected : !isCurrentlySelected;

		let newSelection: SmartDropdownItem[] = [];

		if (this.multiselect) {
			if (shouldBeSelected && !isCurrentlySelected) {
				// Add item
				newSelection = [
					...this.value,
					item,
				];
			} else if (!shouldBeSelected && isCurrentlySelected) {
				// Remove item
				newSelection = this.value.filter(i => i.id !== item.id);
			} else {
				// No change needed
				newSelection = [...this.value];
			}
		} else {
			newSelection = shouldBeSelected ? [item] : [];
			this.closeDropdown();
		}

		this.changed(newSelection);
		this.$nextTick(() => {
			this.triggerInputValue = this.displayText;
		});
	}

	removeSelection(e: Event, item: SmartDropdownItem) {
		e.stopPropagation();
		const newSelection = this.value.filter(i => i.id !== item.id);
		this.changed(newSelection);
	}

	isSelected(item: SmartDropdownItem): boolean {
		return this.value.some(i => i.id === item.id);
	}

	// --- Renders ---

	renderStandardTrigger() {
		if (this.multiselect && this.selectionDisplay === 'chips' && this.value.length > 0) {
			return (
				<div class="trigger-content">
					{this.value.map(item => (
						<span class="chip" onClick={e => e.stopPropagation()} key={item.id}>
							{item.text}
							<span
								class="chip-remove"
								onClick={e => this.removeSelection(e, item)}
								role="button"
								aria-label={`Remove ${item.text}`}
								tabindex={0}
							>
								&times;
							</span>
						</span>
					))}
					{this.searchMode === 'input' && (
						<input
							class="trigger-input-inline"
							value={this.triggerInputValue === this.displayText ? '' : this.triggerInputValue}
							onInput={this.handleInput}
							aria-label={this.effectivePlaceholder}
						/>
					)}
				</div>
			);
		}

		if (this.searchMode === 'input') {
			return (
				<input
					class={{
						'trigger-input': true,
						'typewriter-active': this.isTypewriterActive,
						'typewriter-fade-in': this.typewriterFadingIn,
					}}
					type="text"
					placeholder={this.effectivePlaceholder}
					value={this.triggerInputValue}
					onInput={this.handleInput}
					onClick={e => (e.target as HTMLInputElement).select()}
					onFocus={() => {
						// Open the dropdown when the input is focused (e.g. via Tab).
						if (!this.isOpen) {
							this.openDropdown();
						}
					}}
					aria-autocomplete="list"
					aria-controls={this.listboxId}
					aria-activedescendant={this.isOpen ? this.activeDescendantId : undefined}
					aria-expanded={this.isOpen}
				/>
			);
		}

		return (
			<div class="trigger-content">
				<span class={this.value.length === 0 ? 'placeholder-text' : ''}>
					{this.displayText || this.effectivePlaceholder}
				</span>
			</div>
		);
	}

	renderTrigger() {
		if (!this.customTriggerRender) {
			return this.renderStandardTrigger();
		}

		const customEl = (
			<div class="trigger-custom-wrapper">
				{this.customTriggerRender()}
			</div>
		);

		const standardEl = this.renderStandardTrigger();

		switch (this.customTriggerScope) {
			case 'all': return customEl;
			case 'mobile': return [
				<div class="view-mobile-only">{customEl}</div>,
				<div class="view-desktop-only">{standardEl}</div>,
			];
			case 'desktop': return [
				<div class="view-desktop-only">{customEl}</div>,
				<div class="view-mobile-only">{standardEl}</div>,
			];
			default: return standardEl;
		}
	}

	// Main List Renderer (Manages Groups)
	renderList() {
		if (this.isLoading) {
			return <div class="loading-state" role="status">{SmartDropdownResources.loadingTextLong}</div>;
		}

		// --- Helper to track global index for keyboard nav ---
		let globalIndex = 0;
		const getIndex = () => globalIndex++;

		// A. Search Results
		if (this.isSearchActive) {
			if (this.searchResults.length === 0) {
				// Show loading if search is pending or in progress, otherwise show no results
				if (this.isSearchPending) {
					return <div class="loading-state" role="status">{SmartDropdownResources.loadingTextLong}</div>;
				}

				return <div class="empty-state" role="status">{SmartDropdownResources.noResultsSearch}</div>;
			}

			return (
				<div class="list-group" role="group" aria-label={SmartDropdownResources.searchResultTitle}>
					<div class="list-group-label" aria-hidden="true">{SmartDropdownResources.searchResultTitle}</div>
					{this.searchResults.map(item => this.renderListItem(item, getIndex()))}
				</div>
			);
		}

		// B. Search-only mode: show prompt when no search is active
		if (this.searchOnlyMode) {
			return <div class="empty-state search-only-prompt" role="status">{SmartDropdownResources.searchOnlyPrompt}</div>;
		}

		const hasPinned = this.pinnedSelectedItems.length > 0;
		const hasCustom = this.customSections.length > 0;
		const hasStandard = this.standardDisplayItems.length > 0;

		if (!hasPinned && !hasCustom && !hasStandard) {
			return <div class="empty-state" role="status">{SmartDropdownResources.noResultsDefault}</div>;
		}

		return (
			<div class="list-wrapper">
				{/* 0. List Header */}
				{this.listHeader && (
					<div class="list-header" role="note">{this.listHeader}</div>
				)}

				{/* 1. Pinned Selected */}
				{hasPinned && (
					<div class="list-group section-pinned" role="group" aria-label={SmartDropdownResources.selectedTitle}>
						<div class="list-group-label sticky-selected" aria-hidden="true">
							{SmartDropdownResources.selectedTitle}
							{' '}
							(
							{this.value.length}
							)
						</div>
						{this.pinnedSelectedItems.map(item => this.renderListItem(item, getIndex()))}
					</div>
				)}

				{/* 2. Custom Sections */}
				{this.customSections.map(section => (
					<div class="list-group section-custom" key={section.id} role="group" aria-label={section.title || 'Section'}>
						{section.title && <div class="list-group-label" aria-hidden="true">{section.title}</div>}
						{section.items.map(item => this.renderListItem(item, getIndex()))}
					</div>
				))}

				{/* 3. Standard Categories */}
				{hasStandard && (
					<div class="list-group section-categories" role="group" aria-label={SmartDropdownResources.categoriesTitle}>
						<div class="list-group-label" aria-hidden="true">{SmartDropdownResources.categoriesTitle}</div>
						{this.standardDisplayItems.map(item => this.renderListItem(item, getIndex()))}
					</div>
				)}
			</div>
		);
	}

	/** Renders the default list item content (without wrapper) */
	renderDefaultListItemContent(item: SmartDropdownItem, isSelected: boolean): VNode {
		const searchResult = item as SmartDropdownSearchResultItem;
		const hasSubtitle = !!searchResult.subtitle;
		const hasImage = !!searchResult.imageUrl;
		const showCheckbox = this.multiselect && !this.isSearchActive && !hasImage;

		return (
			<>
				{hasImage
					? (
							<img src={searchResult.imageUrl} class="result-image" alt="" />
						)
					: (
							showCheckbox && <div class={`checkbox-visual ${isSelected ? 'checked' : ''}`} aria-hidden="true"></div>
						)}

				<div class="item-content">
					<span class="item-text">{item.text}</span>
					{hasSubtitle && (
						<span class="item-subtitle">{searchResult.subtitle}</span>
					)}
				</div>
			</>
		);
	}

	renderListItem(item: SmartDropdownItem, index: number) {
		const isSelected = this.isSelected(item);
		const isFocused = this.focusedIndex === index;
		const uniqueId = `${this.uid}-option-${index}`;

		// Create selectionChanged callback for this item
		const selectionChanged: SelectionChangedCallback = (forceSelected?: boolean) => {
			this.handleItemClick(item, forceSelected);
		};

		// Create baseRender function that returns the default rendering
		const baseRender = (): VNode => this.renderDefaultListItemContent(item, isSelected);

		// Wrapper properties for accessibility
		const wrapperProps = {
			'id': uniqueId,
			'class': {
				'list-item-custom-container': !!this.customListItemRender,
				'list-item': !this.customListItemRender,
				'selected': isSelected,
				'focused': isFocused, // Visual focus state
				'no-checkbox': !this.multiselect,
			},
			'role': 'option',
			'aria-selected': isSelected,
			'key': item.id,
		};

		// 1. Custom Renderer - no onClick on wrapper, custom renderer handles clicks via selectionChanged
		if (this.customListItemRender) {
			return (
				<div {...wrapperProps}>
					{this.customListItemRender({ item, isSelected, selectionChanged, baseRender })}
				</div>
			);
		}

		// 2. Default Renderer - attach onClick handler
		return (
			<div {...wrapperProps} onClick={() => this.handleItemClick(item)}>
				{this.renderDefaultListItemContent(item, isSelected)}
			</div>
		);
	}

	render() {
		const showCaret = this.searchMode !== 'input' && !this.customTriggerRender;

		return (
			<FormItemWrapper label={this.label} cssClass={this.cssClass} mandatory={this.mandatory} wrap={this.wrap} appendIcon={this.appendIcon} prependIcon={this.prependIcon} hint={this.hint} marginType={this.marginType} appendClicked={this.appendClicked} prependClicked={this.prependClicked} prependIconClicked={this.prependIconClicked} appendIconClicked={this.appendIconClicked} maxWidth={this.maxWidth} validationState={this.validationState} labelButtons={this.labelButtons} subtitle={this.subtitle} showClearValueButton={this.showClearValueButton}>
				<div
					class={`smart-dropdown-wrapper mode-${this.searchMode}`}
					// Only the outer wrapper acts as combobox if not in direct input mode
					role={this.searchMode !== 'input' ? 'combobox' : undefined}
					aria-expanded={this.isOpen}
					aria-haspopup="listbox"
					aria-controls={this.listboxId}
					aria-label={this.effectivePlaceholder}
				>

					{/* TRIGGER */}
					<div
						class={{ 'filter-trigger': true, 'is-open': this.isOpen }}
						onClick={() => this.toggleDropdown(true)}
						tabindex={this.searchMode === 'input' && !this.customTriggerRender ? undefined : 0}
					>
						{this.renderTrigger()}
						{showCaret && <span class="caret" aria-hidden="true"></span>}
					</div>

					{/* DROPDOWN */}
					{this.isOpen && (
						<div class={{ 'filter-dropdown': true, 'is-closing': this.isClosing }} onClick={e => e.stopPropagation()}>

							{/* INTERNAL HEADER */}
							<div class="search-header internal-search-header">
								{/* Mobile back button - inside input area like Airbnb/Chrome */}
								<button
									class="mobile-back-btn"
									onClick={() => this.confirmAndClose()}
									aria-label={SmartDropdownResources.closeLabel}
								>
									{SmartDropdownResources.mobileBackIconClass
										? <i class={SmartDropdownResources.mobileBackIconClass} />
										: (
												<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
													<path d="M19 12H5M12 19l-7-7 7-7" />
												</svg>
											)}
								</button>
								<input
									type="text"
									class="search-input dropdown-search-input"
									placeholder={this.effectivePlaceholder}
									value={this.triggerInputValue}
									onInput={this.handleInput}
									// Accessibility for internal input
									aria-autocomplete="list"
									aria-controls={this.listboxId}
									aria-activedescendant={this.activeDescendantId}
								/>
								{this.buttonLayout === 'inline' && this.showConfirmButton && (
									<button
										class="btn-confirm"
										disabled={this.isLoading}
										onClick={() => this.confirmAndClose()}
									>
										{this.isLoading ? SmartDropdownResources.loadingTextShort : SmartDropdownResources.doneButtonText}
									</button>
								)}
							</div>

							{/* LIST */}
							<div
								class="list-container"
								id={this.listboxId}
								role="listbox"
								aria-multiselectable={this.multiselect}
								onMousedown={(e: MouseEvent) => this.handleItemMousedown(e)}
							>
								{this.renderList()}
							</div>

							{/* FOOTER */}
							{this.buttonLayout === 'footer' && this.showConfirmButton && (
								<div class="dropdown-footer">
									<button
										class="btn-confirm"
										onClick={() => this.confirmAndClose()}
									>
										{SmartDropdownResources.doneButtonText}
									</button>
								</div>
							)}
						</div>
					)}
				</div>
			</FormItemWrapper>
		);
	}
}
