/**
 * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
 * Copyright 2025 by Keenthemes Inc
 */

import KTData from '../../helpers/data';
import KTDom from '../../helpers/dom';
import KTComponent from '../component';
import {
	KTSelectConfigInterface,
	KTSelectState,
	KTSelectOption as KTSelectOptionData,
} from './config';
import { KTSelectOption } from './option';
import { KTSelectRemote } from './remote';
import { KTSelectSearch } from './search';
import { defaultTemplates } from './templates';
import { KTSelectCombobox } from './combobox';
import { KTSelectDropdown } from './dropdown';
import {
	FocusManager,
	EventManager,
	renderTemplateString,
	TypeToSearchBuffer,
} from './utils';
import { KTSelectTags } from './tags';

export class KTSelect extends KTComponent {
	// Core properties
	protected override readonly _name: string = 'select';
	protected override readonly _dataOptionPrefix: string = 'kt-'; // Use 'kt-' prefix to support data-kt-select-option attributes
	protected override _element!: HTMLElement;
	protected override _config: KTSelectConfigInterface;
	protected override _defaultConfig: KTSelectConfigInterface;

	// Static global configuration
	private static globalConfig: Partial<KTSelectConfigInterface> = {};

	// Static registry for tracking open dropdowns (global dropdown management)
	private static openDropdowns: Set<KTSelect> = new Set();

	// DOM elements
	private _wrapperElement: HTMLElement;
	private _displayElement: HTMLElement;
	private _dropdownContentElement: HTMLElement;
	private _searchInputElement: HTMLInputElement | null;
	private _options: NodeListOf<HTMLElement>;

	// Cached DOM references for performance
	private _optionsContainer: HTMLElement | null = null;

	// State
	private _dropdownIsOpen: boolean = false;
	private _state: KTSelectState;
	private _searchModule: KTSelectSearch;
	private _remoteModule: KTSelectRemote;
	private _comboboxModule: KTSelectCombobox | null = null;
	private _tagsModule: KTSelectTags | null = null;
	private _dropdownModule: KTSelectDropdown | null = null;
	private _loadMoreIndicator: HTMLElement | null = null;
	private _selectAllButton: HTMLElement | null = null;
	private _selectAllButtonToggle: HTMLButtonElement | null = null;
	private _focusManager: FocusManager;
	private _eventManager: EventManager;
	private _typeToSearchBuffer: TypeToSearchBuffer = new TypeToSearchBuffer();
	private _mutationObserver: MutationObserver | null = null;
	private _preSelectedValues: string[] = [];

	/**
	 * Constructor: Initializes the select component
	 */
	constructor(element: HTMLElement, config?: KTSelectConfigInterface) {
		super();

		if (KTData.has(element, this._name)) {
			return;
		}

		this._init(element);
		this._buildConfig(config);

		this._state = new KTSelectState(this._config);
		this._config = this._state.getConfig();

		(this._element as HTMLElement & { instance?: KTSelect }).instance = this;

		// Initialize event manager
		this._eventManager = new EventManager();

		// Initialize remote module if remote data is enabled
		if (this._config.remote) {
			this._remoteModule = new KTSelectRemote(this._config, this._element);
			this._initializeRemoteData();
		} else {
			this._state
				.setItems()
				.then(() => {
					this._setupComponent();
				})
				.catch((error) => {
					console.error('Error setting items:', error);
					// Handle the error, e.g., display an error message to the user
				});
		}
	}

	/**
	 * Set global select configuration options.
	 * This allows setting default configuration that will be applied to all new KTSelect instances.
	 * @param options Partial select config to merge with global config.
	 * @example
	 * KTSelect.config({
	 *   enableSearch: true,
	 *   searchPlaceholder: 'Type to search...',
	 *   dropdownZindex: 9999,
	 *   height: 300
	 * });
	 */
	static config(options: Partial<KTSelectConfigInterface>): void {
		this.globalConfig = { ...this.globalConfig, ...options };
	}

	/**
	 * Override _buildConfig to include static globalConfig in the merge chain
	 */
	protected override _buildConfig(config: object = {}): void {
		if (!this._element) return;

		this._config = {
			...this._defaultConfig,
			...KTSelect.globalConfig,
			...this._getGlobalConfig(),
			...KTDom.getDataAttributes(
				this._element,
				this._dataOptionPrefix + this._name,
			),
			...config,
		} as KTSelectConfigInterface;
	}

	/**
	 * Override _dispatchEvent to also dispatch on document for global listeners (jQuery compatibility)
	 */
	protected override _dispatchEvent(
		eventType: string,
		payload: object | null = null,
	): void {
		// Call parent method to dispatch on element (existing behavior)
		super._dispatchEvent(eventType, payload);

		// Also dispatch on document if configured
		const dispatchGlobalEvents = this._config.dispatchGlobalEvents !== false; // Default to true
		if (dispatchGlobalEvents) {
			// Create event detail structure
			const eventDetail = {
				payload,
				instance: this, // Include component instance reference
				element: this._element, // Include element reference
			};

			// Dispatch non-namespaced event on document (for jQuery compatibility: $(document).on('show', ...))
			const nonNamespacedEvent = new CustomEvent(eventType, {
				detail: eventDetail,
				bubbles: true,
				cancelable: true,
				composed: true, // Allow event to cross shadow DOM boundaries
			});
			document.dispatchEvent(nonNamespacedEvent);

			// Also dispatch namespaced event on document (for namespaced listeners: $(document).on('kt-select:show', ...))
			const namespacedEventType = `kt-select:${eventType}`;
			const namespacedEvent = new CustomEvent(namespacedEventType, {
				detail: eventDetail,
				bubbles: true,
				cancelable: true,
				composed: true, // Allow event to cross shadow DOM boundaries
			});
			document.dispatchEvent(namespacedEvent);
		}
	}

	public override getElement(): HTMLElement {
		return this._element;
	}

	/**
	 * Initialize remote data fetching
	 */
	private _initializeRemoteData() {
		if (!this._remoteModule || !this._config.remote) return;

		// For remote data, we need to create the HTML structure first
		// so that the component can be properly initialized
		this._createHtmlStructure();
		this._setupElementReferences();

		// Show loading state
		this._renderLoadingState();

		// Fetch remote data
		this._remoteModule
			.fetchData()
			.then((items) => {
				// Remove placeholder/loading options before setting new items
				this._clearExistingOptions();

				// Update state with fetched items
				this._state
					.setItems(items)
					.then(() => {
						// Generate options from the fetched data
						this._generateOptionsHtml(this._element);

						// Update the dropdown to show the new options
						this._updateDropdownWithNewOptions();

						// Complete the component setup with the fetched data
						this._completeRemoteSetup();

						// Add pagination "Load More" button if needed
						if (this._config.pagination && this._remoteModule.hasMorePages()) {
							this._addLoadMoreButton();
						}
					})
					.catch((error) => {
						console.error('Error setting items:', error);
						this._renderErrorState(error.message || 'Failed to load data');
					});
			})
			.catch((error) => {
				console.error('Error fetching remote data:', error);
				this._renderErrorState(
					this._remoteModule.getErrorMessage() || 'Failed to load data',
				);
			});
	}

	/**
	 * Clear existing options from the select element
	 */
	private _clearExistingOptions() {
		// Capture pre-selected values before clearing (for remote data)
		const selectedOptions = Array.from(
			this._element.querySelectorAll('option[selected]:not([value=""])'),
		) as HTMLOptionElement[];

		if (selectedOptions.length > 0) {
			this._preSelectedValues = selectedOptions.map((opt) => opt.value);
		}

		// Keep only the empty/placeholder option and remove the rest
		const options = Array.from(
			this._element.querySelectorAll('option:not([value=""])'),
		);
		options.forEach((option) => option.remove());
	}

	/**
	 * Unified method to render options in dropdown - eliminates code duplication
	 */
	private _renderOptionsInDropdown(
		optionsData: KTSelectOptionData[] | HTMLOptionElement[],
		clearContainer: boolean = true,
	): void {
		if (!this._dropdownContentElement) return;

		// Use cached options container for better performance
		const optionsContainer =
			this._optionsContainer ||
			this._dropdownContentElement.querySelector('[data-kt-select-options]');
		if (!optionsContainer) return;

		// Clear container if requested
		if (clearContainer) {
			optionsContainer.innerHTML = '';
		}

		// Use DocumentFragment for efficient DOM manipulation
		const fragment = document.createDocumentFragment();

		// Process options data
		optionsData.forEach((optionData) => {
			let optionElement: HTMLOptionElement;

			// Handle different input types
			if (optionData instanceof HTMLOptionElement) {
				// Skip empty placeholder options
				if (optionData.value === '' && optionData.textContent.trim() === '') {
					return;
				}
				optionElement = optionData;
			} else {
				// Handle KTSelectOptionData objects - cast to ensure type safety
				const dataItem = optionData as KTSelectOptionData;
				optionElement = document.createElement('option');
				optionElement.value = dataItem.id || '';
				optionElement.textContent = dataItem.title || '';

				if (dataItem.selected) {
					optionElement.setAttribute('selected', 'selected');
				}
				if (dataItem.disabled) {
					optionElement.setAttribute('disabled', 'disabled');
				}
			}

			// Create KTSelectOption instance for proper rendering
			const selectOption = new KTSelectOption(optionElement, this._config);
			const renderedOption = selectOption.render();

			// Add to fragment for batch DOM operation
			fragment.appendChild(renderedOption);
		});

		// Batch append all options at once
		optionsContainer.appendChild(fragment);

		// Update options NodeList
		this._options = this._dropdownContentElement.querySelectorAll(
			'[data-kt-select-option]',
		) as NodeListOf<HTMLElement>;
	}

	/**
	 * Update dropdown with new options from the original select element
	 */
	private _updateDropdownWithNewOptions() {
		// Get all options from the original select element
		const options = Array.from(this._element.querySelectorAll('option'));

		// Use unified renderer
		this._renderOptionsInDropdown(options, true);
	}

	/**
	 * Complete the setup for remote data after HTML structure is created
	 */
	private _completeRemoteSetup() {
		// Initialize options
		this._preSelectOptions(this._element);

		// Prevent browser auto-selection when placeholder is configured
		if (
			this._config.placeholder &&
			this._state.getSelectedOptions().length === 0 &&
			this._preSelectedValues.length === 0
		) {
			(this._element as HTMLSelectElement).value = '';
		}

		// Apply pre-selected values captured before remote data was loaded
		if (this._preSelectedValues.length > 0) {
			// Get all available option values from the loaded remote data
			const availableValues = Array.from(
				this._element.querySelectorAll('option'),
			).map((opt) => (opt as HTMLOptionElement).value);

			// Filter pre-selected values to only those that exist in remote data
			const validPreSelectedValues = this._preSelectedValues.filter((value) =>
				availableValues.includes(value),
			);

			if (validPreSelectedValues.length > 0) {
				// For single-select mode, only use the first value
				const valuesToSelect = this._config.multiple
					? validPreSelectedValues
					: [validPreSelectedValues[0]];

				// Get any existing selections from _preSelectOptions (e.g., data-kt-select-pre-selected)
				const existingSelections = this._state.getSelectedOptions();

				// Merge existing selections with native pre-selected values (no duplicates)
				const allSelections = this._config.multiple
					? Array.from(new Set([...existingSelections, ...valuesToSelect]))
					: valuesToSelect;

				// Set all selections at once to avoid toggling issues
				this._state.setSelectedOptions(allSelections);

				// Update the native select element to match
				Array.from(this._element.querySelectorAll('option')).forEach((opt) => {
					(opt as HTMLOptionElement).selected = allSelections.includes(
						opt.value,
					);
				});

				// Update the visual display
				this.updateSelectedOptionDisplay();
				this._updateSelectedOptionClass();
			}

			// Clear the pre-selected values array after processing
			this._preSelectedValues = [];
		}

		// Apply disabled state if needed
		this._applyInitialDisabledState();

		// Initialize search if enabled
		if (this._config.enableSearch) {
			this._initializeSearchModule();
		}

		// Initialize combobox if enabled
		if (this._config.combobox) {
			this._comboboxModule = new KTSelectCombobox(this);
		}

		// Initialize tags if enabled
		if (this._config.tags) {
			this._tagsModule = new KTSelectTags(this);
		}

		// Initialize focus manager after dropdown element is created
		this._focusManager = new FocusManager(
			this._dropdownContentElement,
			'[data-kt-select-option]',
			this._config,
		);

		// Initialize dropdown module after all elements are created
		this._dropdownModule = new KTSelectDropdown(
			this._wrapperElement,
			this._displayElement,
			this._dropdownContentElement,
			this._config,
			this, // Pass the KTSelect instance to KTSelectDropdown
		);

		// Update display and set ARIA attributes
		this._updateDisplayAndAriaAttributes();
		this.updateSelectedOptionDisplay();
		this._setAriaAttributes();

		// Update select all button state
		this.updateSelectAllButtonState();

		// Focus the first selected option or first option if nothing selected
		this._focusSelectedOption();

		// Attach event listeners after all modules are initialized
		this._attachEventListeners();

		this._observeNativeSelect();
	}

	/**
	 * Helper to show a dropdown message (error, loading, noResults)
	 */
	private _showDropdownMessage(
		type: 'error' | 'loading' | 'empty',
		message?: string,
	) {
		if (!this._dropdownContentElement) return;
		const optionsContainer = this._dropdownContentElement.querySelector(
			'[data-kt-select-options]',
		);
		if (!optionsContainer) return;

		// Clear previous messages
		optionsContainer.innerHTML = '';

		switch (type) {
			case 'error':
				optionsContainer.appendChild(
					defaultTemplates.error({
						...this._config,
						errorMessage:
							message ??
							this._config.remoteErrorMessage ??
							'Failed to load data',
					}),
				);
				break;
			case 'loading':
				optionsContainer.appendChild(
					defaultTemplates.loading(this._config, message || 'Loading...'),
				);
				break;
			case 'empty':
				optionsContainer.appendChild(
					defaultTemplates.searchEmpty(this._config),
				);
				break;
		}
	}

	/**
	 * Render loading state in dropdown
	 */
	private _renderLoadingState() {
		if (this._element.querySelectorAll('option').length <= 1) {
			const existingLoadingOptions = this._element.querySelectorAll(
				'option[disabled][selected][value=""]',
			);
			existingLoadingOptions.forEach((option) => option.remove());
			this._showDropdownMessage('loading', 'Loading options...');
		}
	}

	/**
	 * Render error state
	 * @param message Error message
	 */
	private _renderErrorState(message: string) {
		// If dropdown is already created, show error message there
		this._showDropdownMessage('error', message);

		if (!this._wrapperElement) {
			this._setupComponent();
		}
	}

	/**
	 * Add "Load More" button for pagination
	 */
	private _addLoadMoreButton() {
		if (!this._dropdownContentElement || !this._config.pagination) return;

		// Remove existing button if any
		if (this._loadMoreIndicator) {
			this._loadMoreIndicator.remove();
			this._loadMoreIndicator = null;
		}

		// Create load more button using template
		this._loadMoreIndicator = defaultTemplates.loadMore(this._config);

		// Add to dropdown
		const optionsContainer = this._dropdownContentElement.querySelector(
			'[data-kt-select-options]',
		);
		if (optionsContainer) {
			optionsContainer.appendChild(this._loadMoreIndicator);
		} else {
			this._dropdownContentElement.appendChild(this._loadMoreIndicator);
		}

		// Add event listener
		this._loadMoreIndicator.addEventListener(
			'click',
			this._handleLoadMore.bind(this),
		);
	}

	/**
	 * Handle load more button click
	 */
	private _handleLoadMore() {
		if (!this._remoteModule || !this._config.pagination) return;

		// Show loading state
		if (this._loadMoreIndicator) {
			this._loadMoreIndicator.textContent = 'Loading...';
		}

		// Fetch next page
		this._remoteModule
			.loadNextPage()
			.then((newItems) => {
				// Get existing items
				const existingItems = this._state.getItems();

				// Combine new items with existing items
				this._state
					.setItems([...existingItems, ...newItems])
					.then(() => {
						// Update options in the dropdown
						this._updateOptionsInDropdown(newItems);

						// Check if there are more pages
						if (this._remoteModule.hasMorePages()) {
							// Reset load more button
							if (this._loadMoreIndicator) {
								this._loadMoreIndicator.textContent =
									this._config.loadMoreText || 'Load more...';
							}
						} else {
							// Remove load more button if no more pages
							if (this._loadMoreIndicator) {
								this._loadMoreIndicator.remove();
								this._loadMoreIndicator = null;
							}
						}
					})
					.catch((error) => {
						console.error('Error updating items:', error);

						// Reset load more button
						if (this._loadMoreIndicator) {
							this._loadMoreIndicator.textContent = 'Error loading more items';
						}
					});
			})
			.catch((error) => {
				console.error('Error loading more items:', error);

				// Reset load more button
				if (this._loadMoreIndicator) {
					this._loadMoreIndicator.textContent = 'Error loading more items';
				}
			});
	}

	/**
	 * Update options in the dropdown
	 * @param newItems New items to add to the dropdown
	 */
	private _updateOptionsInDropdown(newItems: KTSelectOptionData[]) {
		if (!this._dropdownContentElement || !newItems.length) return;

		const optionsContainer = this._dropdownContentElement.querySelector(
			`[data-kt-select-options]`,
		);
		if (!optionsContainer) return;

		// Get the load more button
		const loadMoreButton = optionsContainer.querySelector(
			`[data-kt-select-load-more]`,
		);

		// Process each new item
		newItems.forEach((item) => {
			// Create option for the original select
			const selectOption = document.createElement('option');
			selectOption.value = item.id || '';

			// Add to dropdown container
			if (loadMoreButton) {
				// Insert before the load more button
				optionsContainer.insertBefore(selectOption, loadMoreButton);
			} else {
				// Append to the end
				optionsContainer.appendChild(selectOption);
			}
		});

		// Update options NodeList to include the new options
		this._options = this._dropdownContentElement.querySelectorAll(
			`[data-kt-select-option]`,
		) as NodeListOf<HTMLElement>;
	}

	/**
	 * ========================================================================
	 * INITIALIZATION METHODS
	 * ========================================================================
	 */

	/**
	 * Set up the component after everything is initialized
	 */
	private _setupComponent() {
		// Setup HTML structure
		this._createHtmlStructure();
		this._setupElementReferences();

		// Initialize options
		this._preSelectOptions(this._element);

		// Prevent browser auto-selection when placeholder is configured
		if (
			this._config.placeholder &&
			this._state.getSelectedOptions().length === 0
		) {
			(this._element as HTMLSelectElement).value = '';
		}

		// Apply disabled state if needed
		this._applyInitialDisabledState();

		// Initialize search if enabled
		if (this._config.enableSearch) {
			this._initializeSearchModule();
		}

		// Initialize combobox if enabled
		if (this._config.combobox) {
			this._comboboxModule = new KTSelectCombobox(this);
		}

		// Initialize tags if enabled
		if (this._config.tags) {
			this._tagsModule = new KTSelectTags(this);
		}

		// Initialize focus manager after dropdown element is created
		this._focusManager = new FocusManager(
			this._dropdownContentElement,
			'[data-kt-select-option]',
			this._config,
		);

		// Initialize dropdown module after all elements are created
		this._dropdownModule = new KTSelectDropdown(
			this._wrapperElement,
			this._displayElement,
			this._dropdownContentElement,
			this._config,
			this, // Pass the KTSelect instance to KTSelectDropdown
		);

		// Update display and set ARIA attributes
		this._updateDisplayAndAriaAttributes();
		this.updateSelectedOptionDisplay();
		this._setAriaAttributes();

		// Update select all button state
		this.updateSelectAllButtonState();

		// Focus the first selected option or first option if nothing selected
		this._focusSelectedOption();

		// Attach event listeners after all modules are initialized
		this._attachEventListeners();

		this._observeNativeSelect();
	}

	/**
	 * Creates the HTML structure for the select component
	 */
	private _createHtmlStructure() {
		const options = Array.from(this._element.querySelectorAll('option'));

		// Create wrapper and display elements
		const wrapperElement = defaultTemplates.wrapper(this._config);

		const displayElement = defaultTemplates.display(this._config);

		// Add the display element to the wrapper
		wrapperElement.appendChild(displayElement);

		// Move classes from original select to wrapper and display elements
		if (this._element.classList.length > 0) {
			const originalClasses = Array.from(this._element.classList);
			const displaySpecificClasses = [
				'kt-select',
				'kt-select-sm',
				'kt-select-lg',
			];

			const classesForWrapper = originalClasses.filter(
				(className) => !displaySpecificClasses.includes(className),
			);
			if (classesForWrapper.length > 0) {
				wrapperElement.classList.add(...classesForWrapper);
			}

			// Move display-specific classes to display element
			const classesForDisplay = originalClasses.filter((className) =>
				displaySpecificClasses.includes(className),
			);
			if (classesForDisplay.length > 0) {
				displayElement.classList.add(...classesForDisplay);
			}

			this._element.className = ''; // Clear classes from original select element
		}

		// Create an empty dropdown first (without options) using template
		const dropdownElement = defaultTemplates.dropdown({
			...this._config,
			zindex: this._config.dropdownZindex ?? undefined,
		});

		// Add search input if needed
		if (this._config.enableSearch) {
			const searchElement = defaultTemplates.search(this._config);
			dropdownElement.appendChild(searchElement);
		}

		// Add select all button if needed
		if (this._config.multiple && this._config.enableSelectAll) {
			const selectAllElement = defaultTemplates.selectAll(this._config);
			dropdownElement.appendChild(selectAllElement);
		}

		// Create options container using template
		const optionsContainer = defaultTemplates.options(this._config);

		// Add each option directly to the container (only if options exist)
		options.forEach((optionElement) => {
			// Skip empty placeholder options (only if BOTH value AND text are empty)
			// This allows options with empty value but visible text to display in dropdown
			if (
				optionElement.value === '' &&
				optionElement.textContent.trim() === ''
			) {
				return;
			}

			// Create new KTSelectOption instance for each option
			const selectOption = new KTSelectOption(optionElement, this._config);
			const renderedOption = selectOption.render();

			// Append directly to options container
			optionsContainer.appendChild(renderedOption);
		});

		// Add options container to dropdown
		dropdownElement.appendChild(optionsContainer);

		// Add dropdown to wrapper
		wrapperElement.appendChild(dropdownElement);

		// Insert after the original element
		this._element.after(wrapperElement);
		this._element.classList.add('hidden');
	}

	/**
	 * Setup all element references after DOM is created
	 */
	private _setupElementReferences() {
		this._wrapperElement = this._element.nextElementSibling as HTMLElement;

		// Safety check - ensure wrapper element exists
		if (!this._wrapperElement) {
			console.error(
				'KTSelect: Wrapper element not found. HTML structure may not be created properly.',
			);
			return;
		}

		// Get display element
		this._displayElement = this._wrapperElement.querySelector(
			`[data-kt-select-display]`,
		) as HTMLElement;

		// Get dropdown content element - this is critical for dropdown functionality
		this._dropdownContentElement = this._wrapperElement.querySelector(
			`[data-kt-select-dropdown]`,
		) as HTMLElement;

		if (!this._dropdownContentElement) {
			console.error(
				'KTSelect: Dropdown content element not found',
				this._wrapperElement,
			);
			return;
		}

		// Get search input element - this is used for the search functionality
		// First try to find the actual input element (not the wrapper div)
		this._searchInputElement = this._dropdownContentElement.querySelector(
			`input[data-kt-select-search]`,
		) as HTMLInputElement;

		// If not found, try the wrapper selector (for backward compatibility)
		if (!this._searchInputElement) {
			const searchWrapper = this._dropdownContentElement.querySelector(
				`[data-kt-select-search]`,
			) as HTMLElement;
			if (searchWrapper) {
				this._searchInputElement = searchWrapper.querySelector(
					'input',
				) as HTMLInputElement;
			}
		}

		// If still not found in dropdown, check if it's the display element itself (combobox mode)
		if (!this._searchInputElement) {
			this._searchInputElement = this._displayElement.querySelector(
				'input[data-kt-select-search]',
			) as HTMLInputElement;
		}

		this._selectAllButton = this._wrapperElement.querySelector(
			'[data-kt-select-select-all]',
		) as HTMLElement;

		// Cache the options container for performance
		this._optionsContainer = this._dropdownContentElement.querySelector(
			'[data-kt-select-options]',
		) as HTMLElement;

		this._options = this._dropdownContentElement.querySelectorAll(
			`[data-kt-select-option]`,
		) as NodeListOf<HTMLElement>;
	}

	/**
	 * Attach all event listeners to elements
	 */
	private _attachEventListeners() {
		// Document level event listeners
		document.addEventListener('click', this._handleDocumentClick.bind(this));

		// Dropdown option click events
		this._eventManager.addListener(
			this._dropdownContentElement,
			'click',
			this._handleDropdownOptionClick.bind(this),
		);

		if (this._selectAllButton) {
			this._selectAllButtonToggle =
				this._selectAllButton.querySelector('button');
			if (this._selectAllButtonToggle) {
				this._eventManager.addListener(
					this._selectAllButtonToggle,
					'click',
					this._handleSelectAllClick.bind(this),
				);
			}
		}

		// Attach centralized keyboard handler to the wrapper element.
		// Events from focusable children like _displayElement or _searchInputElement (if present) will bubble up.
		if (this._wrapperElement) {
			this._wrapperElement.addEventListener(
				'keydown',
				this._handleKeyboardEvent.bind(this),
			);
		}
	}

	/**
	 * Initialize search module if search is enabled
	 */
	private _initializeSearchModule() {
		if (this._config.enableSearch) {
			this._searchModule = new KTSelectSearch(this);
			this._searchModule.init();

			// If remote search is enabled, add event listener for search input
			if (
				this._config.remote &&
				this._config.searchParam &&
				this._searchInputElement
			) {
				this._searchInputElement.addEventListener(
					'input',
					this._handleRemoteSearch.bind(this),
				);
			}
		}
	}

	/**
	 * Apply ARIA attributes and update display
	 */
	private _updateDisplayAndAriaAttributes() {
		this.updateSelectedOptionDisplay();
		this._setAriaAttributes();
	}

	/**
	 * Apply initial disabled state if configured
	 */
	private _applyInitialDisabledState() {
		if (this._config.disabled) {
			this.getElement().classList.add('disabled');
			this.getElement().setAttribute('disabled', 'disabled');
			this._wrapperElement.classList.add('disabled');
		}
	}

	/**
	 * Generate options HTML from data items
	 */
	private _generateOptionsHtml(element: HTMLElement) {
		const items = this._state.getItems() || [];

		// Only modify options if we have items to replace them with
		if (items && items.length > 0) {
			// Clear existing options except the first empty one
			const options = element.querySelectorAll('option:not(:first-child)');
			options.forEach((option) => option.remove());

			// Generate options from data
			items.forEach((item) => {
				const optionElement = document.createElement('option');

				// Get value - use item.id directly if available, otherwise try dataValueField
				let value = '';
				if (item.id !== undefined) {
					value = String(item.id);
				} else if (this._config.dataValueField) {
					const extractedValue = this._getValueByKey(
						item,
						this._config.dataValueField,
					);
					value = extractedValue !== null ? String(extractedValue) : '';
				}

				// Get label - use item.title directly if available, otherwise try dataFieldText
				let label = '';
				if (item.title !== undefined) {
					label = String(item.title);
				} else if (this._config.dataFieldText) {
					const extractedLabel = this._getValueByKey(
						item,
						this._config.dataFieldText,
					);
					label =
						extractedLabel !== null ? String(extractedLabel) : 'Unnamed option';
				}

				// Set option attributes
				optionElement.value = value;
				optionElement.textContent = label || 'Unnamed option';

				if (item.selected) {
					optionElement.setAttribute('selected', 'selected');
				}

				element.appendChild(optionElement);
			});
		}
	}

	/**
	 * Extract nested property value from object using dot notation
	 */
	private _getValueByKey(obj: unknown, key: string): unknown {
		if (!key || !obj) return null;

		// Use reduce to walk through the object by splitting the key on dots
		const result = key.split('.').reduce((o: unknown, k: string) => {
			if (
				o &&
				typeof o === 'object' &&
				(o as Record<string, unknown>)[k] !== undefined
			) {
				return (o as Record<string, unknown>)[k];
			}
			return null;
		}, obj);

		return result;
	}

	/**
	 * Pre-select options that have the selected attribute
	 */
	private _preSelectOptions(element: HTMLElement) {
		// Handle options with selected attribute
		Array.from(element.querySelectorAll('option[selected]')).forEach(
			(option) => {
				const value = (option as HTMLOptionElement).value;
				this._selectOption(value);
			},
		);

		// Handle data-kt-select-pre-selected attribute for React compatibility
		const preSelectedValues = element.getAttribute(
			'data-kt-select-pre-selected',
		);
		if (preSelectedValues) {
			const values = preSelectedValues.split(',').map((v) => v.trim());
			values.forEach((value) => {
				if (value) {
					this._selectOption(value);
				}
			});
		}
	}

	/**
	 * ========================================================================
	 * DROPDOWN MANAGEMENT
	 * ========================================================================
	 */

	/**
	 * Open the dropdown
	 */
	public openDropdown() {
		if (this._config.disabled) {
			return;
		}

		if (!this._dropdownModule) {
			return;
		}

		// Don't open dropdown if the select is disabled
		if (this._config.disabled) {
			return;
		}

		// Global dropdown management: close other open dropdowns if configured
		const closeOnOtherOpen = this._config.closeOnOtherOpen !== false; // Default to true
		if (closeOnOtherOpen) {
			// Close all other open dropdowns
			const otherSelectsToClose: KTSelect[] = [];
			KTSelect.openDropdowns.forEach((otherSelect) => {
				const isOther = otherSelect !== this;
				const isOpen = otherSelect._dropdownIsOpen;
				if (isOther && isOpen) {
					otherSelectsToClose.push(otherSelect);
				}
			});
			otherSelectsToClose.forEach((otherSelect) => {
				otherSelect.closeDropdown();
			});
		}

		// Set our internal flag to match what we're doing
		this._dropdownIsOpen = true;

		// Add to registry
		KTSelect.openDropdowns.add(this);

		// Open the dropdown via the module
		this._dropdownModule.open();

		// Dispatch custom events
		this._dispatchEvent('show');
		this._fireEvent('show');

		// Dispatch dropdown.show event on wrapper for search module
		const dropdownShowEvent = new CustomEvent('dropdown.show', {
			bubbles: true,
			cancelable: true,
		});
		this._wrapperElement.dispatchEvent(dropdownShowEvent);

		// Update ARIA states
		this._setAriaAttributes();

		// Update select all button state
		this.updateSelectAllButtonState();

		// Focus the first selected option or first option if nothing selected
		// BUT: Skip this if search autofocus is enabled, as we want search input to get focus
		if (!(this._config.enableSearch && this._config.searchAutofocus)) {
			this._focusSelectedOption();
		}

		// Dispatch dropdown.show event on the wrapper element for search module
		// Use requestAnimationFrame to ensure dropdown is visible and transition has started
		requestAnimationFrame(() => {
			requestAnimationFrame(() => {
				if (this._wrapperElement) {
					const dropdownShowEvent = new CustomEvent('dropdown.show', {
						bubbles: true,
						cancelable: true,
					});
					this._wrapperElement.dispatchEvent(dropdownShowEvent);
				}
			});
		});
	}

	/**
	 * Close the dropdown
	 */
	public closeDropdown() {
		// Only check if dropdown module exists, not dropdownIsOpen flag
		if (!this._dropdownModule) {
			return;
		}

		// Clear search input if the dropdown is closing
		if (this._searchModule && this._searchInputElement) {
			// Clear search input if configured to do so
			if (this._config.clearSearchOnClose) {
				this._searchInputElement.value = '';
			}

			// Clear search input when dropdown closes
			this._searchModule.clearSearch();
		}

		// Set our internal flag to match what we're doing
		this._dropdownIsOpen = false;

		// Remove from registry
		KTSelect.openDropdowns.delete(this);

		// Call the dropdown module's close method
		this._dropdownModule.close();

		// Reset all focus states
		if (this._focusManager) {
			this._focusManager.resetFocus();
		}

		// Dispatch custom events on the select element
		this._dispatchEvent('close');
		this._fireEvent('close');

		// Dispatch dropdown.close event on wrapper for search module
		const dropdownCloseEvent = new CustomEvent('dropdown.close', {
			bubbles: true,
			cancelable: true,
		});
		this._wrapperElement.dispatchEvent(dropdownCloseEvent);

		// Update ARIA states
		this._setAriaAttributes();
	}

	/**
	 * Update dropdown position
	 */
	public updateDropdownPosition() {
		if (this._dropdownModule) {
			this._dropdownModule.updatePosition();
		}
	}

	/**
	 * Focus on the first selected option if any exists in the dropdown
	 */
	private _focusSelectedOption() {
		// Get selected options
		const selectedOptions = this.getSelectedOptions();
		if (selectedOptions.length === 0) return;

		// Iterate through selected options and focus the first one that is visible
		for (const value of selectedOptions) {
			if (this._focusManager && this._focusManager.focusOptionByValue(value)) {
				break; // Stop after focusing the first found selected and visible option
			}
		}
	}

	/**
	 * ========================================================================
	 * SELECTION MANAGEMENT
	 * ========================================================================
	 */

	/**
	 * Select an option by value
	 */
	private _selectOption(value: string) {
		// Prevent selection if the option is disabled (in dropdown or original select)
		if (this._isOptionDisabled(value)) {
			return;
		}

		// Get current selection state
		const isSelected = this._state.isSelected(value);

		// Toggle selection in state
		if (this._config.multiple) {
			// Toggle in multiple mode
			this._state.toggleSelectedOptions(value);
		} else {
			// Set as only selection in single mode
			this._state.setSelectedOptions(value);
		}

		// Update the original select element's option selected state
		const optionEl = Array.from(this._element.querySelectorAll('option')).find(
			(opt) => opt.value === value,
		) as HTMLOptionElement;

		if (optionEl) {
			if (this._config.multiple) {
				// Toggle the selection for multiple select
				optionEl.selected = !isSelected;
			} else {
				// Set as only selection for single select
				Array.from(this._element.querySelectorAll('option')).forEach((opt) => {
					(opt as HTMLOptionElement).selected = opt.value === value;
				});
			}
		}

		// Update the visual display of selected options
		this.updateSelectedOptionDisplay();

		// Update option classes without re-rendering the dropdown content
		this._updateSelectedOptionClass();

		// Dispatch standard and custom change events
		this._dispatchEvent('change', {
			value: value,
			selected: !isSelected,
			selectedOptions: this.getSelectedOptions(),
		});
		this._fireEvent('change', {
			value: value,
			selected: !isSelected,
			selectedOptions: this.getSelectedOptions(),
		});
	}

	/**
	 * Sync native select value attribute for FormData support
	 */
	private _syncNativeSelectValue(): void {
		const selectedOptions = this.getSelectedOptions();
		const selectEl = this._element as HTMLSelectElement;

		if (this._config.multiple) {
			// For multiple select, set each native option's selected from internal state
			const selectedSet = new Set(selectedOptions);
			Array.from(selectEl.options).forEach((option) => {
				option.selected = selectedSet.has(option.value);
			});
		} else {
			// For single select, set the value attribute explicitly
			const selectedValue =
				selectedOptions.length > 0 ? selectedOptions[0] : '';
			selectEl.value = selectedValue;
		}
	}

	/**
	 * Update selected option display value
	 */
	public updateSelectedOptionDisplay() {
		const selectedOptions = this.getSelectedOptions();
		const tagsEnabled = this._config.tags && this._tagsModule;
		const valueDisplayEl = this.getValueDisplayElement();

		// Sync native select value for FormData support
		this._syncNativeSelectValue();

		if (this._config.tags && this._tagsModule) {
			// Tags module will render tags if selectedOptions > 0, or clear them if selectedOptions === 0.
			this._tagsModule.updateTagsDisplay(selectedOptions);
		}

		// Guard against valueDisplayEl being null due to template modifications
		if (!valueDisplayEl) {
			if (this._config.debug) {
				console.warn(
					'KTSelect: Value display element is null. Cannot update display or placeholder. Check template for [data-kt-select-value].',
				);
			}
			return; // Nothing to display on if the element is missing
		}

		if (typeof this._config.renderSelected === 'function') {
			valueDisplayEl.innerHTML = this._config.renderSelected(selectedOptions);
		} else {
			if (selectedOptions.length === 0) {
				// No options selected: display placeholder.
				// This runs if tags are off, OR if tags are on but no items are selected (tags module would have cleared tags).
				const placeholderEl = defaultTemplates.placeholder(this._config);
				valueDisplayEl.replaceChildren(placeholderEl);
			} else {
				// Options are selected.
				if (tagsEnabled) {
					// Tags are enabled AND options are selected: tags module has rendered them.
					// Completely clear all content to avoid redundancy with tags
					// Don't touch the innerHTML here as tags module manages it
					// Just ensure no text content is generated
					return; // Exit early to prevent any text generation
				} else {
					// Tags are not enabled AND options are selected: render normal text display.
					// Wrap content in .kt-select-option-text so long text truncates in single-select (see Asana #1212821478465094).
					const wrapper = document.createElement('div');
					wrapper.className = 'kt-select-option-text';
					wrapper.setAttribute('data-kt-text-container', 'true');
					if (this._config.displayTemplate) {
						wrapper.innerHTML = this.renderDisplayTemplateForSelected(
							this.getSelectedOptions(),
						);
					} else {
						wrapper.textContent = this.getSelectedOptionsText();
					}
					valueDisplayEl.replaceChildren(wrapper);
				}
			}
		}
	}

	/**
	 * Check if an option was originally disabled in the HTML
	 */
	private _isOptionOriginallyDisabled(value: string): boolean {
		const originalOption = Array.from(
			this._element.querySelectorAll('option'),
		).find((opt) => opt.value === value) as HTMLOptionElement;
		return originalOption ? originalOption.disabled : false;
	}

	/**
	 * Update CSS classes for selected options
	 */
	private _updateSelectedOptionClass(): void {
		if (!this._dropdownContentElement) return;
		const allOptions = this._dropdownContentElement.querySelectorAll(
			`[data-kt-select-option]`,
		);
		const selectedValues = this._state.getSelectedOptions();
		const maxReached =
			typeof this._config.maxSelections === 'number' &&
			selectedValues.length >= this._config.maxSelections;

		allOptions.forEach((option) => {
			const optionValue = option.getAttribute('data-value');
			if (!optionValue) return;

			const isSelected = selectedValues.includes(optionValue);
			const isOriginallyDisabled =
				this._isOptionOriginallyDisabled(optionValue);

			if (isSelected) {
				option.classList.add('selected');
				option.setAttribute('aria-selected', 'true');
				// Selected options should not be visually hidden or disabled by maxSelections logic
				option.classList.remove('hidden');
				option.classList.remove('disabled');
				option.removeAttribute('aria-disabled');
			} else {
				option.classList.remove('selected');
				option.setAttribute('aria-selected', 'false');

				// An option should be disabled if it was originally disabled OR if maxSelections is reached
				if (isOriginallyDisabled || maxReached) {
					option.classList.add('disabled');
					option.setAttribute('aria-disabled', 'true');
				} else {
					option.classList.remove('disabled');
					option.removeAttribute('aria-disabled');
				}
			}
		});
	}

	/**
	 * Clear all selected options
	 */
	public clearSelection() {
		// Clear the current selection
		this._state.setSelectedOptions([]);

		// Clear all native select options
		Array.from(this._element.querySelectorAll('option')).forEach((opt) => {
			(opt as HTMLOptionElement).selected = false;
		});

		// Clear native select value
		(this._element as HTMLSelectElement).value = '';

		this.updateSelectedOptionDisplay();
		this._updateSelectedOptionClass();

		// Update select all button state
		this.updateSelectAllButtonState();

		// Dispatch change event
		this._dispatchEvent('change');
		this._fireEvent('change');
	}

	/**
	 * Deselect a specific option by value
	 * @param value The value of the option to deselect
	 * @public
	 */
	public deselectOption(value: string): void {
		// Check if the option is currently selected
		if (!this._state.isSelected(value)) {
			return; // Already deselected
		}

		// For single-select mode, check if clearing is allowed
		if (!this._config.multiple && !this._config.allowClear) {
			return; // Cannot deselect in single-select mode unless allowClear is true
		}

		// Remove from selected options
		if (this._config.multiple) {
			// For multiple select, just toggle it off
			this._state.toggleSelectedOptions(value);
		} else {
			// For single select, clear all selections
			this._state.setSelectedOptions([]);
		}

		// Update the native select element
		const optionEl = Array.from(this._element.querySelectorAll('option')).find(
			(opt) => opt.value === value,
		) as HTMLOptionElement;

		if (optionEl) {
			optionEl.selected = false;
		}

		// For single select, clear the native select value
		if (!this._config.multiple) {
			(this._element as HTMLSelectElement).value = '';
		}

		// Update the display
		this.updateSelectedOptionDisplay();
		this._updateSelectedOptionClass();

		// Update select all button state
		this.updateSelectAllButtonState();

		// Dispatch change event
		this._dispatchEvent('change', {
			value: value,
			selected: false,
			selectedOptions: this.getSelectedOptions(),
		});
		this._fireEvent('change', {
			value: value,
			selected: false,
			selectedOptions: this.getSelectedOptions(),
		});
	}

	/**
	 * Set selected options programmatically
	 */
	public setSelectedOptions(options: HTMLOptionElement[]) {
		const values = Array.from(options).map((option) => option.value);
		this._state.setSelectedOptions(values);
		this.updateSelectedOptionDisplay();
		this._updateSelectedOptionClass();
	}

	/**
	 * Select the currently focused option
	 */
	public selectFocusedOption() {
		const focusedOption = this._focusManager.getFocusedOption();

		if (focusedOption) {
			const selectedValue = focusedOption.dataset.value;

			// First trigger the selection to ensure state is updated properly
			if (selectedValue) {
				this._selectOption(selectedValue);
			}
		}
	}

	/**
	 * ========================================================================
	 * EVENT HANDLERS
	 * ========================================================================
	 */

	/**
	 * Handle click within the dropdown
	 */
	private _handleDropdownOptionClick(event: Event) {
		const optionElement = (event.target as HTMLElement).closest(
			`[data-kt-select-option]`,
		);

		// If an option is clicked, handle the option click
		if (optionElement) {
			this._handleOptionClick(event);
		}
	}

	/**
	 * Handle clicking on an option in the dropdown
	 */
	private _handleOptionClick(event: Event) {
		event.preventDefault();
		event.stopPropagation();

		// Find the clicked option element
		const clickedOption = (event.target as HTMLElement).closest(
			`[data-kt-select-option]`,
		) as HTMLElement;

		if (!clickedOption) {
			return;
		}

		// Check if the option is disabled
		if (clickedOption.getAttribute('aria-disabled') === 'true') {
			return;
		}

		// Use dataset.value to get the option value
		const optionValue = clickedOption.dataset.value;
		if (optionValue === undefined) {
			return;
		}

		// If in single-select mode and the clicked option is already selected, just close the dropdown.
		if (!this._config.multiple && this._state.isSelected(optionValue)) {
			this.closeDropdown();
			return;
		}

		// Use toggleSelection instead of _selectOption to prevent re-rendering
		this.toggleSelection(optionValue);
	}

	/**
	 * Handle document click for closing dropdown
	 */
	private _handleDocumentClick(event: MouseEvent) {
		const targetElement = event.target as HTMLElement;
		// Check if the click is outside the dropdown and the display element
		if (!this._wrapperElement.contains(targetElement)) {
			this.closeDropdown();
		}
	}

	/**
	 * ========================================================================
	 * ACCESSIBILITY METHODS
	 * ========================================================================
	 */

	/**
	 * Set ARIA attributes for accessibility
	 */
	private _setAriaAttributes() {
		this._displayElement.setAttribute(
			'aria-expanded',
			this._dropdownIsOpen.toString(),
		);
	}

	/**
	 * ========================================================================
	 * PUBLIC API
	 * ========================================================================
	 */

	/**
	 * Get the search input element
	 */
	public getSearchInput(): HTMLInputElement | null {
		return this._searchInputElement;
	}

	/**
	 * Get selected options
	 */
	public getSelectedOptions() {
		return this._state.getSelectedOptions();
	}

	/**
	 * Get configuration
	 */
	public getConfig(): KTSelectConfigInterface {
		return this._config;
	}

	/**
	 * Get option elements
	 */
	public getOptionsElement(): NodeListOf<HTMLElement> {
		return this._options;
	}

	/**
	 * Get dropdown element
	 */
	public getDropdownElement() {
		return this._dropdownContentElement;
	}

	/**
	 * Get value display element
	 */
	public getValueDisplayElement() {
		return this._displayElement;
	}

	/**
	 * Get wrapper element
	 */
	public getWrapperElement(): HTMLElement {
		return this._wrapperElement;
	}

	/**
	 * Show all options in the dropdown
	 */
	public showAllOptions() {
		// Get all options in the dropdown
		const options = Array.from(
			this._dropdownContentElement.querySelectorAll(`[data-kt-select-option]`),
		);

		// Show all options by removing the hidden class and any inline styles
		options.forEach((option) => {
			// Remove hidden class
			option.classList.remove('hidden');

			// Clean up any existing inline styles for backward compatibility
			if (option.hasAttribute('style')) {
				const styleAttr = option.getAttribute('style');

				if (styleAttr && styleAttr.includes('display:')) {
					// If style only contains display property, remove the entire attribute
					if (
						styleAttr.trim() === 'display: none;' ||
						styleAttr.trim() === 'display: block;'
					) {
						option.removeAttribute('style');
					} else {
						// Otherwise, remove just the display property
						option.setAttribute(
							'style',
							styleAttr.replace(/display:\s*[^;]+;?/gi, '').trim(),
						);
					}
				}
			}
		});

		// If search input exists, clear it
		if (this._searchInputElement) {
			this._searchInputElement.value = '';
			// If we have a search module, clear any search filtering
			if (this._searchModule) {
				this._searchModule.clearSearch();
			}
		}
	}

	/**
	 * Toggle multi-select functionality
	 */
	public enableMultiSelect() {
		this._state.modifyConfig({ multiple: true });
	}

	/**
	 * Disable multi-select functionality
	 */
	public disableMultiSelect() {
		this._state.modifyConfig({ multiple: false });
	}

	/**
	 * Toggle the selection of an option
	 */
	public toggleSelection(value: string): void {
		// Prevent selection if the option is disabled (in dropdown or original select)
		if (this._isOptionDisabled(value)) {
			return;
		}

		// Get current selection state
		const isSelected = this._state.isSelected(value);

		// If already selected in single select mode, allow deselecting only if allowClear is true
		if (isSelected && !this._config.multiple) {
			if (this._config.allowClear) {
				// Use the deselectOption method to handle clearing
				this.deselectOption(value);
				return;
			}
			return; // Can't deselect in single select mode when allowClear is false
		}

		// Ensure any search input is cleared when selection changes
		if (this._searchModule) {
			this._searchModule.clearSearch();
		}

		// Toggle the selection in the state
		this._state.toggleSelectedOptions(value);

		// Update the original select element's option selected state
		const optionEl = Array.from(this._element.querySelectorAll('option')).find(
			(opt) => opt.value === value,
		) as HTMLOptionElement;

		if (optionEl) {
			// For multiple select, toggle the 'selected' attribute
			if (this._config.multiple) {
				optionEl.selected = !isSelected;
			} else {
				// For single select, deselect all other options and select this one
				Array.from(this._element.querySelectorAll('option')).forEach((opt) => {
					(opt as HTMLOptionElement).selected = opt.value === value;
				});
			}
		}

		// Update the display element value
		this.updateSelectedOptionDisplay();

		// Update option classes without re-rendering the dropdown content
		this._updateSelectedOptionClass();

		// For single select mode, close the dropdown after selection unless closeOnEnter is false
		// For multiple select mode, keep the dropdown open to allow multiple selections
		if (!this._config.multiple) {
			// Check if we should close based on closeOnEnter config
			// closeOnEnter only applies to Enter key selections, but for backward compatibility,
			// we'll respect it for all selections when explicitly set to false
			const shouldClose = this._config.closeOnEnter !== false; // Default to true
			if (shouldClose) {
				this.closeDropdown();
			} else {
				this.updateSelectAllButtonState();
			}
		} else {
			// Don't close dropdown in multiple select mode to allow multiple selections
			this.updateSelectAllButtonState();
		}

		// Dispatch custom change event with additional data
		this._dispatchEvent('change', {
			value: value,
			selected: !isSelected,
			selectedOptions: this.getSelectedOptions(),
		});
		this._fireEvent('change', {
			value: value,
			selected: !isSelected,
			selectedOptions: this.getSelectedOptions(),
		});
	}

	/**
	 * Clean up all resources when the component is destroyed
	 * This overrides the parent dispose method
	 */
	public override dispose(): void {
		// Clean up event listeners
		this._eventManager.removeAllListeners(this._wrapperElement);

		// Dispose modules
		if (this._dropdownModule) {
			this._dropdownModule.dispose();
		}

		if (this._comboboxModule) {
			if (typeof this._comboboxModule.destroy === 'function') {
				this._comboboxModule.destroy();
			}
		}

		if (this._tagsModule) {
			if (typeof this._tagsModule.destroy === 'function') {
				this._tagsModule.destroy();
			}
		}

		if (this._searchModule) {
			if (typeof this._searchModule.destroy === 'function') {
				this._searchModule.destroy();
			}
		}

		// Remove DOM elements
		if (this._wrapperElement && this._wrapperElement.parentNode) {
			this._wrapperElement.parentNode.removeChild(this._wrapperElement);
		}

		// Call parent dispose to clean up data
		super.dispose();
	}

	/**
	 * ========================================================================
	 * DYNAMIC CONTROL METHODS
	 * ========================================================================
	 */

	/**
	 * Programmatically enable the select component
	 * @public
	 */
	public enable(): void {
		// Update config state
		this._config.disabled = false;

		// Remove disabled attribute from native select
		this._element.removeAttribute('disabled');
		this._element.classList.remove('disabled');

		// Remove disabled state from wrapper and display elements
		if (this._wrapperElement) {
			this._wrapperElement.classList.remove('disabled');
		}

		if (this._displayElement) {
			this._displayElement.removeAttribute('aria-disabled');
		}

		// Dispatch enabled event
		this._dispatchEvent('enabled');
		this._fireEvent('enabled');
	}

	/**
	 * Programmatically disable the select component
	 * @public
	 */
	public disable(): void {
		// Update config state
		this._config.disabled = true;

		// Close dropdown if currently open
		if (this._dropdownIsOpen) {
			this.closeDropdown();
		}

		// Add disabled attribute to native select
		this._element.setAttribute('disabled', 'disabled');
		this._element.classList.add('disabled');

		// Add disabled state to wrapper and display elements
		if (this._wrapperElement) {
			this._wrapperElement.classList.add('disabled');
		}

		if (this._displayElement) {
			this._displayElement.setAttribute('aria-disabled', 'true');
		}

		// Dispatch disabled event
		this._dispatchEvent('disabled');
		this._fireEvent('disabled');
	}

	/**
	 * Update the dropdown to sync with native select element changes
	 * For remote selects, refetches data from the server and preserves selections
	 * Optionally accepts new options to replace existing ones (static selects only)
	 *
	 * @param newOptions Optional array of new options [{value, text}, ...] (static selects only)
	 * @public
	 * @remarks
	 * - For static selects: rebuilds dropdown from native select or new options
	 * - For remote selects: fetches fresh data, preserves matching selections
	 * - Selections are preserved if their values exist in new remote data
	 * - Selections are cleared if their values don't exist in new data
	 * @fires updated - After update completes successfully
	 * @fires updateError - If remote data fetch fails
	 */
	public update(newOptions?: Array<{ value: string; text: string }>): void {
		// For remote selects, refetch data
		if (this._config.remote && this._remoteModule) {
			this._remoteModule
				.fetchData()
				.then((items) => {
					// Capture currently selected values before clearing
					const currentlySelected = this._state.getSelectedOptions();

					// Clear existing options (also captures to _preSelectedValues)
					this._clearExistingOptions();

					// Get all available values from new remote data
					const availableValues = items.map((item) => item.id);

					// Filter to only values that exist in new data
					const validSelections = currentlySelected.filter((value) =>
						availableValues.includes(value),
					);

					// Add new options from remote data and restore selection state
					items.forEach((item) => {
						const option = document.createElement('option');
						option.value = item.id;
						option.textContent = item.title;
						if (item.disabled) option.disabled = true;

						// Restore selected attribute for preserved selections
						if (validSelections.includes(item.id)) {
							option.selected = true;
						}

						this._element.appendChild(option);
					});

					// Rebuild dropdown
					this._rebuildOptionsFromNative();

					// Sync selection state from native select (now has selected attributes)
					this._syncSelectionFromNative();

					// Dispatch updated event
					this._dispatchEvent('updated');
					this._fireEvent('updated');
				})
				.catch((error) => {
					console.error('Error updating remote data:', error);
					this._dispatchEvent('updateError');
					this._fireEvent('updateError');
				});
		} else {
			// For static selects, handle new options
			if (newOptions) {
				// Clear existing options except placeholder
				this._clearExistingOptions();

				// Add new options to native select
				newOptions.forEach((opt) => {
					const option = document.createElement('option');
					option.value = opt.value;
					option.textContent = opt.text;
					this._element.appendChild(option);
				});
			}

			// Rebuild dropdown from native select
			this._rebuildOptionsFromNative();

			// Dispatch updated event
			this._dispatchEvent('updated');
			this._fireEvent('updated');
		}
	}

	/**
	 * Reload remote data and rebuild the dropdown
	 * Only works with remote data enabled
	 * @returns Promise that resolves when reload completes
	 * @public
	 */
	public reload(): Promise<void> {
		// Guard clause: only works with remote data
		if (!this._config.remote || !this._remoteModule) {
			console.warn('reload() only works with remote data enabled');
			return Promise.resolve();
		}

		// Dispatch reload start event
		this._dispatchEvent('reloadStart');
		this._fireEvent('reloadStart');

		// Capture currently selected values before clearing
		const currentlySelected = this._state.getSelectedOptions();

		// Fetch fresh remote data
		return this._remoteModule
			.fetchData()
			.then((items) => {
				// Clear existing options (captures to _preSelectedValues)
				this._clearExistingOptions();

				// Update state with new items
				return this._state.setItems(items).then(() => {
					// Generate new options HTML
					this._generateOptionsHtml(this._element);

					// Preserve selections by marking matching options as selected
					const availableValues = items.map((item) =>
						item.id !== undefined ? String(item.id) : '',
					);
					const validSelections = currentlySelected.filter((value) =>
						availableValues.includes(value),
					);

					// Mark preserved selections on new options
					validSelections.forEach((value) => {
						const option = Array.from(
							this._element.querySelectorAll('option'),
						).find((opt) => opt.value === value) as HTMLOptionElement;
						if (option) {
							option.selected = true;
						}
					});

					// Update the dropdown
					this._updateDropdownWithNewOptions();

					// Sync selection state from native select (now has selected attributes)
					this._syncSelectionFromNative();

					// Update visual display
					this.updateSelectedOptionDisplay();
					this._updateSelectedOptionClass();

					// Update select all button state if applicable
					if (this._config.multiple && this._config.enableSelectAll) {
						this.updateSelectAllButtonState();
					}

					// Dispatch reload complete event
					this._dispatchEvent('reloadComplete');
					this._fireEvent('reloadComplete');
				});
			})
			.catch((error) => {
				console.error('Error reloading remote data:', error);

				// Dispatch reload error event with error details
				this._dispatchEvent('reloadError', { error });
				this._fireEvent('reloadError', { error });

				// Re-throw error so caller can handle it
				throw error;
			});
	}

	/**
	 * Refresh the visual display and state without rebuilding options
	 * For remote selects, refetches data from the server and preserves selections
	 * that exist in the newly fetched data
	 *
	 * @public
	 * @remarks
	 * - For static selects: syncs visual state with native select
	 * - For remote selects: fetches fresh data, preserves matching selections
	 * - Selections are preserved if their values exist in new remote data
	 * - Selections are cleared if their values don't exist in new data
	 * @fires refreshed - After refresh completes successfully
	 * @fires refreshError - If remote data fetch fails
	 */
	public refresh(): void {
		// For remote selects, refetch data
		if (this._config.remote && this._remoteModule) {
			this._remoteModule
				.fetchData()
				.then((items) => {
					// Capture currently selected values before clearing
					const currentlySelected = this._state.getSelectedOptions();

					// Clear existing options (also captures to _preSelectedValues)
					this._clearExistingOptions();

					// Get all available values from new remote data
					const availableValues = items.map((item) => item.id);

					// Filter to only values that exist in new data
					const validSelections = currentlySelected.filter((value) =>
						availableValues.includes(value),
					);

					// Add new options and restore selection state
					items.forEach((item) => {
						const option = document.createElement('option');
						option.value = item.id;
						option.textContent = item.title;
						if (item.disabled) option.disabled = true;

						// Restore selected attribute for preserved selections
						if (validSelections.includes(item.id)) {
							option.selected = true;
						}

						this._element.appendChild(option);
					});

					// Rebuild dropdown
					this._rebuildOptionsFromNative();

					// Sync selection state from native select (now has selected attributes)
					this._syncSelectionFromNative();

					// Reapply ARIA attributes
					this._setAriaAttributes();

					// Dispatch refreshed event
					this._dispatchEvent('refreshed');
					this._fireEvent('refreshed');
				})
				.catch((error) => {
					console.error('Error refreshing remote data:', error);
					this._dispatchEvent('refreshError');
					this._fireEvent('refreshError');
				});
		} else {
			// For static selects, bail out if called before init (e.g. right after getOrCreateInstance)
			if (!this._dropdownContentElement) return;
			// Sync visual state
			this._syncSelectionFromNative();

			// Reapply ARIA attributes
			this._setAriaAttributes();

			// Dispatch refreshed event
			this._dispatchEvent('refreshed');
			this._fireEvent('refreshed');
		}
	}

	/**
	 * ========================================================================
	 * STATIC METHODS
	 * ========================================================================
	 */

	/**
	 * Create instances of KTSelect for all matching elements
	 */
	public static createInstances(): void {
		const elements = document.querySelectorAll<HTMLElement>('[data-kt-select]');

		elements.forEach((element) => {
			if (
				element.hasAttribute('data-kt-select') &&
				!element.classList.contains('data-kt-select-initialized')
			) {
				new KTSelect(element);
			}
		});
	}

	/**
	 * Initialize all KTSelect instances
	 */
	public static init(): void {
		KTSelect.createInstances();
	}

	/**
	 * Get an existing KTSelect instance from an element
	 */
	public static getInstance(element: HTMLElement): KTSelect | null {
		if (!element) return null;

		if (KTData.has(element, 'select')) {
			return KTData.get(element, 'select') as KTSelect;
		}

		if (element.getAttribute('data-kt-select')) {
			return new KTSelect(element);
		}

		return null;
	}

	/**
	 * Get an existing KTSelect instance or create a new one
	 */
	public static getOrCreateInstance(
		element: HTMLElement,
		config?: KTSelectConfigInterface,
	): KTSelect {
		return this.getInstance(element) || new KTSelect(element, config);
	}

	/**
	 * Handle remote search
	 * @param event Input event
	 */
	private _handleRemoteSearch(event: Event) {
		if (
			!this._remoteModule ||
			!this._config.remote ||
			!this._config.searchParam
		)
			return;

		const query = (event.target as HTMLInputElement).value;

		// Check if the query is long enough
		if (query.length < (this._config.searchMinLength || 0)) {
			// Restore original options if query is too short
			this._restoreOriginalOptions();
			return;
		}

		// Debounce the search
		if (this._searchDebounceTimeout) {
			clearTimeout(this._searchDebounceTimeout);
		}

		this._searchDebounceTimeout = window.setTimeout(() => {
			// Show loading state
			this._renderSearchLoadingState();

			// Fetch remote data with search query
			this._remoteModule
				.fetchData(query)
				.then((items) => {
					// Update state with fetched items
					this._state
						.setItems(items)
						.then(() => {
							// Update options in the dropdown
							this._updateSearchResults(items);

							// Refresh the search module to update focus and cache
							if (this._searchModule) {
								this._searchModule.refreshAfterSearch();
							}
							this.updateSelectAllButtonState();
						})
						.catch((error) => {
							console.error('Error updating search results:', error);
							this._renderSearchErrorState(
								error.message || 'Failed to load search results',
							);
						});
				})
				.catch((error) => {
					console.error('Error fetching search results:', error);
					this._renderSearchErrorState(
						this._remoteModule.getErrorMessage() ||
							'Failed to load search results',
					);
				});
		}, this._config.searchDebounce || 300);
	}

	// Search debounce timeout
	private _searchDebounceTimeout: number | null = null;

	/**
	 * Render loading state for search
	 */
	private _renderSearchLoadingState() {
		if (!this._originalOptionsHtml && this._dropdownContentElement) {
			const optionsContainer = this._dropdownContentElement.querySelector(
				'[data-kt-select-options]',
			);
			if (optionsContainer) {
				this._originalOptionsHtml = optionsContainer.innerHTML;
			}
		}
		this._showDropdownMessage('loading', 'Searching...');
	}

	// Store original options HTML for restoring after search
	private _originalOptionsHtml: string | null = null;

	/**
	 * Render error state for search
	 * @param message Error message
	 */
	private _renderSearchErrorState(message: string) {
		this._showDropdownMessage('error', message);

		// Restore original options after error with a delay
		setTimeout(() => {
			this._restoreOriginalOptions();
		}, 2000);
	}

	/**
	 * Restore original options when search is cleared
	 */
	private _restoreOriginalOptions() {
		if (!this._dropdownContentElement || !this._originalOptionsHtml) return;

		// Use cached options container for better performance
		const optionsContainer =
			this._optionsContainer ||
			this._dropdownContentElement.querySelector('[data-kt-select-options]');
		if (!optionsContainer) return;

		// Restore original options
		optionsContainer.innerHTML = this._originalOptionsHtml;

		// Update options NodeList
		this._options = this._dropdownContentElement.querySelectorAll(
			'[data-kt-select-option]',
		) as NodeListOf<HTMLElement>;

		// Refresh search module
		if (this._searchModule) {
			this._searchModule.refreshAfterSearch();
		}
		this.updateSelectAllButtonState();
	}

	/**
	 * Update search results in the dropdown
	 * @param items Search result items
	 */
	private _updateSearchResults(items: KTSelectOptionData[]) {
		if (!this._dropdownContentElement) return;

		// Use cached options container for better performance
		const optionsContainer =
			this._optionsContainer ||
			this._dropdownContentElement.querySelector('[data-kt-select-options]');
		if (!optionsContainer) return;

		// Handle empty results
		if (items.length === 0) {
			optionsContainer.innerHTML = '';
			const noResultsElement = defaultTemplates.searchEmpty(this._config);
			optionsContainer.appendChild(noResultsElement);
			return;
		}

		// First update the original select element with search results
		this._updateOriginalSelectWithSearchResults(items);

		// Then update dropdown using the standard flow
		this._updateDropdownWithNewOptions();

		// Add pagination "Load More" button if needed
		if (this._config.pagination && this._remoteModule.hasMorePages()) {
			this._addLoadMoreButton();
		}
	}

	/**
	 * Update original select element with search results
	 * @param items Search result items
	 */
	private _updateOriginalSelectWithSearchResults(items: KTSelectOptionData[]) {
		// Clear existing options except placeholder
		this._clearExistingOptions();

		// Add search result items to original select element
		items.forEach((item) => {
			const optionElement = document.createElement('option');
			optionElement.value = item.id || '';
			optionElement.textContent = item.title || '';

			if (item.selected) {
				optionElement.setAttribute('selected', 'selected');
			}
			if (item.disabled) {
				optionElement.setAttribute('disabled', 'disabled');
			}

			this._element.appendChild(optionElement);
		});
	}

	/**
	 * Check if dropdown is open
	 */
	public isDropdownOpen(): boolean {
		return this._dropdownIsOpen;
	}

	public getSelectedOptionsText(): string {
		const selectedValues = this.getSelectedOptions();
		const displaySeparator = this._config.displaySeparator || ', ';
		const texts = selectedValues
			.map((value) => {
				const option = Array.from(this._options).find(
					(opt) => opt.getAttribute('data-value') === value,
				);
				return option?.getAttribute('data-text') || '';
			})
			.filter(Boolean);
		return texts.join(displaySeparator);
	}

	/**
	 * Check if an option is disabled (either in dropdown or original select)
	 */
	private _isOptionDisabled(value: string): boolean {
		const dropdownOption = Array.from(this._options).find(
			(opt) => opt.getAttribute('data-value') === value,
		);
		const isDropdownDisabled =
			dropdownOption &&
			(dropdownOption.classList.contains('disabled') ||
				dropdownOption.getAttribute('aria-disabled') === 'true');
		const selectOption = Array.from(
			this._element.querySelectorAll('option'),
		).find((opt) => opt.value === value) as HTMLOptionElement;
		const isNativeDisabled = selectOption && selectOption.disabled;
		return Boolean(isDropdownDisabled || isNativeDisabled);
	}

	/**
	 * Centralized keyboard event handler for all select modes
	 */
	private _handleKeyboardEvent(event: KeyboardEvent) {
		// When search is enabled and focus is on the search input, let the search module be the sole
		// handler for Enter (and Space). This avoids the select's FocusManager from selecting the wrong option.
		if (
			this._searchInputElement &&
			event.target === this._searchInputElement &&
			(event.key === 'Enter' || event.key === ' ')
		) {
			return;
		}
		// If the event target is the search input and the event was already handled (defaultPrevented),
		// then return early to avoid duplicate processing by this broader handler.
		if (event.target === this._searchInputElement && event.defaultPrevented) {
			return;
		}

		const isOpen = this._dropdownIsOpen;
		const focusManager = this._focusManager;
		const buffer = this._typeToSearchBuffer;

		// If the event target is the search input, let it handle most typing keys naturally.
		if (event.target === this._searchInputElement) {
			// Allow navigation keys like ArrowDown, ArrowUp, Escape, Enter (for search/selection) to be handled by the logic below.
			// For other keys (characters, space, backspace, delete), let the input field process them.
			if (
				event.key !== 'ArrowDown' &&
				event.key !== 'ArrowUp' &&
				event.key !== 'Escape' &&
				event.key !== 'Enter' &&
				event.key !== 'Tab' &&
				event.key !== 'Home' &&
				event.key !== 'End' &&
				event.key !== ' '
			) {
				// If it's a character key and we are NOT type-to-searching (because search has focus)
				// then let the input field handle it for its own value.
				// The search module's 'input' event will handle filtering based on the input's value.
				buffer.clear(); // Clear type-to-search buffer when typing in search field
				return;
			}
			// For Enter specifically in search input, we might want to select the focused option or submit search.
			// This is handled later in the switch.
		}

		// Ignore modifier keys (except for specific combinations if added later)
		if (event.altKey || event.ctrlKey || event.metaKey) return;

		// Type-to-search: only for single char keys, when search input does not have focus
		if (
			event.key.length === 1 &&
			!event.repeat &&
			!event.key.match(/\s/) &&
			document.activeElement !== this._searchInputElement
		) {
			buffer.push(event.key);
			const str = buffer.getBuffer();
			if (isOpen) {
				focusManager.focusByString(str);
			} else {
				// If closed, type-to-search could potentially open and select.
				// For now, let's assume it only works when open or opens it first.
				// Or, we could find the matching option and set it directly without opening.
			}
			return; // Type-to-search handles the event
		}

		switch (event.key) {
			case 'ArrowDown':
				event.preventDefault();
				if (!isOpen) {
					this.openDropdown();
				} else {
					focusManager.focusNext();
				}
				break;
			case 'ArrowUp':
				event.preventDefault();
				if (!isOpen) {
					this.openDropdown();
				} else {
					focusManager.focusPrevious();
				}
				break;
			case 'Home':
				event.preventDefault();
				if (isOpen) focusManager.focusFirst();
				break;
			case 'End':
				event.preventDefault();
				if (isOpen) focusManager.focusLast();
				break;
			case 'Enter':
			case ' ': // Space
				if (isOpen) {
					const focusedOptionEl = this._focusManager.getFocusedOption();
					if (focusedOptionEl) {
						const val = focusedOptionEl.dataset.value;
						// If single select, and the item is already selected, just close.
						if (
							val !== undefined &&
							!this._config.multiple &&
							this._state.isSelected(val)
						) {
							this.closeDropdown();
							event.preventDefault();
							break;
						}
					}

					// Proceed with selection if not handled above
					this.selectFocusedOption();

					// Close dropdown only for single select mode (for new selections)
					// Keep dropdown open for multiple select mode to allow additional selections
					if (!this._config.multiple) {
						// This will also be true for the case handled above, but closeDropdown is idempotent.
						// However, the break above prevents this from being reached for that specific case.
						this.closeDropdown();
					}
					event.preventDefault(); // Prevent form submission or other default actions
					break;
				} else {
					this.openDropdown();
				}
				break;
			case 'Escape':
				if (isOpen) {
					this.closeDropdown();
					(event.target as HTMLElement).blur();
				}
				break;
			case 'Tab':
				// Let Tab propagate for normal focus movement
				break;
			default:
				break;
		}
	}

	public renderDisplayTemplateForSelected(selectedValues: string[]): string {
		const optionsConfig = this._config.optionsConfig || {};
		const displaySeparator = this._config.displaySeparator || ', ';
		const contentArray = Array.from(
			new Set(
				selectedValues
					.map((value) => {
						const option = Array.from(this._options).find(
							(opt) => opt.getAttribute('data-value') === value,
						);
						if (!option) return '';

						let displayTemplate = this._config.displayTemplate || '{{text}}';
						const text = option.getAttribute('data-text') || '';

						// Replace all {{varname}} in option.innerHTML with values from _config
						Object.entries(optionsConfig[value] || {}).forEach(([key, val]) => {
							if (['string', 'number', 'boolean'].includes(typeof val)) {
								displayTemplate = displayTemplate.replace(
									new RegExp(`{{${key}}}`, 'g'),
									String(val),
								);
							}
						});

						return renderTemplateString(displayTemplate, {
							selectedCount: selectedValues.length || 0,
							selectedTexts: this.getSelectedOptionsText() || '',
							text,
						});
					})
					.filter(Boolean),
			),
		);
		return contentArray.join(displaySeparator);
	}

	public getDisplayElement(): HTMLElement {
		return this._displayElement;
	}

	private _observeNativeSelect() {
		if (this._mutationObserver) return; // Prevent double observers
		this._mutationObserver = new MutationObserver((mutations) => {
			let needsRebuild = false;
			let needsSelectionSync = false;

			for (const mutation of mutations) {
				if (mutation.type === 'childList') {
					// Option(s) added or removed
					needsRebuild = true;
				} else if (
					mutation.type === 'attributes' &&
					mutation.target instanceof HTMLOptionElement
				) {
					if (mutation.attributeName === 'selected') {
						needsSelectionSync = true;
					}
				}
			}

			if (needsRebuild) {
				// Rebuild the custom dropdown options
				this._rebuildOptionsFromNative();
			}
			if (needsSelectionSync) {
				this._syncSelectionFromNative();
			}
		});

		this._mutationObserver.observe(this._element, {
			childList: true,
			attributes: true,
			subtree: true,
			attributeFilter: ['selected'],
		});
	}

	private _rebuildOptionsFromNative() {
		// Remove and rebuild the custom dropdown options from the native select
		if (this._dropdownContentElement) {
			const optionsContainer = this._dropdownContentElement.querySelector(
				'[data-kt-select-options]',
			);
			if (optionsContainer) {
				optionsContainer.innerHTML = '';
				const options = Array.from(this._element.querySelectorAll('option'));
				options.forEach((optionElement) => {
					if (
						optionElement.value === '' &&
						optionElement.textContent.trim() === ''
					) {
						return;
					}
					const selectOption = new KTSelectOption(optionElement, this._config);
					const renderedOption = selectOption.render();
					optionsContainer.appendChild(renderedOption);
				});
				// Update internal references
				this._options = this._dropdownContentElement.querySelectorAll(
					'[data-kt-select-option]',
				) as NodeListOf<HTMLElement>;
			}
		}
		// Sync selection after rebuilding
		this._syncSelectionFromNative();
		this.updateSelectedOptionDisplay();
		this._updateSelectedOptionClass();
	}

	private _syncSelectionFromNative() {
		// Sync internal state from the native select's selected options
		const selected = Array.from(
			this._element.querySelectorAll('option:checked'),
		).map((opt) => (opt as HTMLOptionElement).value);
		this._state.setSelectedOptions(
			this._config.multiple ? selected : selected[0] || '',
		);
		this.updateSelectedOptionDisplay();
		this._updateSelectedOptionClass();
		this.updateSelectAllButtonState();
	}

	private _handleSelectAllClick(event: Event): void {
		event.preventDefault();
		event.stopPropagation();

		const visibleOptions = this._focusManager
			.getVisibleOptions()
			.filter((opt) => opt.getAttribute('aria-disabled') !== 'true');
		if (visibleOptions.length === 0) return;

		const visibleValues = visibleOptions.map(
			(opt) => opt.dataset.value as string,
		);
		const selectedValues = new Set(this.getSelectedOptions());
		const isAllSelected = visibleOptions.every((opt) =>
			selectedValues.has(opt.dataset.value as string),
		);

		if (isAllSelected) {
			// Deselect all visible
			visibleValues.forEach((value) => selectedValues.delete(value));
		} else {
			// Select all visible
			visibleValues.forEach((value) => selectedValues.add(value));
		}

		this._state.setSelectedOptions(Array.from(selectedValues));
		this.updateSelectedOptionDisplay();
		this._updateSelectedOptionClass();
		this.updateSelectAllButtonState();

		this._dispatchEvent('change');
		this._fireEvent('change');
	}

	public updateSelectAllButtonState(): void {
		if (
			!this._config.multiple ||
			!this._config.enableSelectAll ||
			!this._selectAllButtonToggle
		) {
			return;
		}

		const visibleOptions = this._focusManager
			.getVisibleOptions()
			.filter((opt) => opt.getAttribute('aria-disabled') !== 'true');

		const selectAllButton = this._selectAllButton;
		const selectAllButtonToggle = this._selectAllButtonToggle;
		if (!selectAllButton || !selectAllButtonToggle) return;

		if (visibleOptions.length === 0) {
			selectAllButton.style.display = 'none';
			return;
		}

		selectAllButton.style.display = '';

		const selectedValues = new Set(this.getSelectedOptions());
		const isAllSelected = visibleOptions.every((opt) =>
			selectedValues.has(opt.dataset.value as string),
		);

		selectAllButtonToggle.textContent = isAllSelected
			? (this._config.clearAllText ?? 'Clear all')
			: (this._config.selectAllText ?? 'Select all');
	}

	/**
	 * Destroy the component and clean up resources
	 */
	public destroy(): void {
		// Remove from global dropdown registry
		KTSelect.openDropdowns.delete(this);

		// Close dropdown if open
		if (this._dropdownIsOpen) {
			this.closeDropdown();
		}

		// Call parent dispose method
		super.dispose();
	}
}

if (typeof window !== 'undefined') {
	window.KTSelect = KTSelect;
}
