/**
 * UX Behaviors Tests for KTSelect
 * Tests the enhancements: search autofocus, Enter key behavior, global dropdown management, and global event dispatch
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { KTSelect } from '../select';
import { waitFor } from '../../datatable/__tests__/setup';

describe('KTSelect UX Behaviors', () => {
	let container: HTMLElement;

	/**
	 * Helper to create a select element with options
	 */
	const createSelectElement = (
		options: Array<{ value: string; text: string }> = [
			{ value: '1', text: 'Option 1' },
			{ value: '2', text: 'Option 2' },
			{ value: '3', text: 'Option 3' },
		],
	): HTMLSelectElement => {
		const select = document.createElement('select');
		select.className = 'kt-select';
		options.forEach((opt) => {
			const option = document.createElement('option');
			option.value = opt.value;
			option.textContent = opt.text;
			select.appendChild(option);
		});
		return select;
	};

	/**
	 * Helper to wait for KTSelect to fully initialize
	 */
	const waitForInit = async (_select: KTSelect): Promise<void> => {
		// Wait for async initialization - KTSelect uses promises for setup
		await waitFor(200);
		// Wait for next tick to ensure all modules are initialized
		await new Promise((resolve) => setTimeout(resolve, 0));
		// Additional wait for DOM to be ready
		await waitFor(50);
	};

	beforeEach(() => {
		container = document.createElement('div');
		document.body.appendChild(container);
	});

	afterEach(() => {
		// Clean up all KTSelect instances
		const selects = document.querySelectorAll('.kt-select');
		selects.forEach((select) => {
			const instance = (select as HTMLElement & { instance?: KTSelect })
				.instance;
			if (instance && typeof instance.destroy === 'function') {
				instance.destroy();
			}
		});

		// Clear document body
		document.body.innerHTML = '';
		container = null as unknown as HTMLElement;

		// Clear all event listeners
		vi.clearAllMocks();
	});

	describe('refresh() before init (issue #109)', () => {
		it('should not throw when refresh() is called immediately after getOrCreateInstance()', () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			// Simulate framework (e.g. Angular) setting default value by code before KTSelect init
			(selectEl as HTMLSelectElement).value = '2';

			// getOrCreateInstance returns synchronously; _setupComponent runs in a later microtask.
			// Calling refresh() here used to throw because _dropdownContentElement was undefined.
			const instance = KTSelect.getOrCreateInstance(selectEl, { height: 250 });

			expect(() => {
				instance.refresh();
			}).not.toThrow();
		});
	});

	describe('Search Autofocus Enhancement', () => {
		it('should focus search input when dropdown opens with searchAutofocus enabled', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				searchAutofocus: true,
				height: 250,
			});

			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(200); // Wait for autofocus retry mechanism

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();
			expect(document.activeElement).toBe(searchInput);
		});

		it('should not focus search input when searchAutofocus is disabled', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				searchAutofocus: false,
				height: 250,
			});

			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();
			expect(document.activeElement).not.toBe(searchInput);
		});

		it('should retry focus if initial focus fails', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				searchAutofocus: true,
				height: 250,
			});

			await waitForInit(select);

			// Get search input and set up spy before opening dropdown
			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Spy on focus method
			const focusSpy = vi.spyOn(searchInput, 'focus');

			// Open dropdown - this will trigger autofocus
			select.openDropdown();
			await waitFor(350); // Wait for retry mechanism (0ms, 50ms, 100ms, 200ms)

			// Focus should have been called at least once (initial attempt + retries)
			expect(focusSpy).toHaveBeenCalled();
			focusSpy.mockRestore();
		});
	});

	describe('Enter Key Behavior', () => {
		it('should close dropdown when Enter is pressed with closeOnEnter enabled (default)', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);

			// Press Enter
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(150);

			// Dropdown should be closed
			expect(select.isDropdownOpen()).toBe(false);
		});

		it('should keep dropdown open when Enter is pressed with closeOnEnter disabled', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: false,
				height: 250,
			});

			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);

			// Press Enter
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(150);

			// Dropdown should remain open
			expect(select.isDropdownOpen()).toBe(true);
		});

		it('should select first option when Enter is pressed', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);

			// Press Enter
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(150);

			// First option should be selected
			expect(select.getSelectedOptions()).toContain('1');
		});

		it('should select focused option (not first) when Enter is pressed after ArrowDown with search enabled (issue #108)', async () => {
			const selectEl = createSelectElement([
				{ value: 'apple', text: 'Apple' },
				{ value: 'google', text: 'Google' },
				{ value: 'amazon', text: 'Amazon' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input (user has not used arrow keys yet)
			searchInput.focus();
			await waitFor(50);

			// Simulate user pressing ArrowDown twice: first moves to first option, second to second (Google).
			// Enter must select the currently focused option (Google), not always the first (Apple).
			const arrowDownEvent = (opts?: Partial<KeyboardEvent>) =>
				new KeyboardEvent('keydown', {
					key: 'ArrowDown',
					bubbles: true,
					cancelable: true,
					...opts,
				});
			searchInput.dispatchEvent(arrowDownEvent());
			await waitFor(20);
			searchInput.dispatchEvent(arrowDownEvent());
			await waitFor(20);

			// Press Enter - should select the focused option (Google), not the first (Apple)
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(150);

			// The highlighted option (google) must be selected, not the first (apple)
			expect(select.getSelectedOptions()).toContain('google');
			expect(select.getSelectedOptions()).not.toContain('apple');
			expect(select.isDropdownOpen()).toBe(false);
		});

		it('should select focused option when Enter is pressed after ArrowDown then ArrowUp (last option focused)', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Option 1' },
				{ value: '2', text: 'Option 2' },
				{ value: '3', text: 'Option 3' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			searchInput!.focus();
			await waitFor(50);

			// ArrowDown focuses first option; ArrowUp from first wraps to last (3). Enter selects focused.
			searchInput.dispatchEvent(
				new KeyboardEvent('keydown', {
					key: 'ArrowDown',
					bubbles: true,
					cancelable: true,
				}),
			);
			await waitFor(20);
			searchInput.dispatchEvent(
				new KeyboardEvent('keydown', {
					key: 'ArrowUp',
					bubbles: true,
					cancelable: true,
				}),
			);
			await waitFor(20);

			searchInput.dispatchEvent(
				new KeyboardEvent('keydown', {
					key: 'Enter',
					bubbles: true,
					cancelable: true,
				}),
			);
			await waitFor(150);

			expect(select.getSelectedOptions()).toContain('3');
		});

		it('should close dropdown and trigger selection when Enter is pressed after typing search query', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Apple' },
				{ value: '2', text: 'Banana' },
				{ value: '3', text: 'Cherry' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Set up change event listener to verify selection-complete lifecycle
			const changeHandler = vi.fn();
			selectEl.addEventListener('change', changeHandler);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);

			// Type search query
			searchInput.value = 'App';
			const inputEvent = new Event('input', { bubbles: true });
			searchInput.dispatchEvent(inputEvent);
			await waitFor(100); // Wait for filtering

			// Verify dropdown is still open
			expect(select.isDropdownOpen()).toBe(true);

			// Press Enter
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(200);

			// Dropdown should be closed
			expect(select.isDropdownOpen()).toBe(false);

			// Selection should be made
			expect(select.getSelectedOptions()).toContain('1');

			// Change event should be dispatched (selection-complete lifecycle)
			expect(changeHandler).toHaveBeenCalledTimes(1);

			// Cleanup
			selectEl.removeEventListener('change', changeHandler);
		});

		it('should close dropdown and trigger selection when Enter is pressed with filtered results', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Red Apple' },
				{ value: '2', text: 'Green Apple' },
				{ value: '3', text: 'Banana' },
				{ value: '4', text: 'Cherry' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Set up change event listener
			const changeHandler = vi.fn();
			selectEl.addEventListener('change', changeHandler);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);

			// Type search query that filters to multiple results
			searchInput.value = 'Apple';
			const inputEvent = new Event('input', { bubbles: true });
			searchInput.dispatchEvent(inputEvent);
			await waitFor(150); // Wait for filtering

			// Verify dropdown is still open and options are filtered
			expect(select.isDropdownOpen()).toBe(true);

			// Press Enter - should select first filtered option
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(200);

			// Dropdown should be closed
			expect(select.isDropdownOpen()).toBe(false);

			// First filtered option should be selected
			expect(select.getSelectedOptions()).toContain('1');

			// Change event should be dispatched
			expect(changeHandler).toHaveBeenCalledTimes(1);
			const event = changeHandler.mock.calls[0][0] as CustomEvent;
			expect(event.detail?.payload?.value).toBe('1');
			expect(event.detail?.payload?.selected).toBe(true);

			// Cleanup
			selectEl.removeEventListener('change', changeHandler);
		});

		it('should trigger change event when Enter selects option (selection-complete lifecycle)', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Option 1' },
				{ value: '2', text: 'Option 2' },
				{ value: '3', text: 'Option 3' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Set up listeners for both element and document events
			const elementChangeHandler = vi.fn();
			const documentChangeHandler = vi.fn();
			selectEl.addEventListener('change', elementChangeHandler);
			document.addEventListener('kt-select:change', documentChangeHandler);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);

			// Press Enter
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(200);

			// Element change event should be dispatched
			expect(elementChangeHandler).toHaveBeenCalledTimes(1);
			const elementEvent = elementChangeHandler.mock.calls[0][0] as CustomEvent;
			expect(elementEvent.detail?.payload?.value).toBe('1');
			expect(elementEvent.detail?.payload?.selected).toBe(true);

			// Document change event should be dispatched (global events enabled by default)
			expect(documentChangeHandler).toHaveBeenCalledTimes(1);
			const docEvent = documentChangeHandler.mock.calls[0][0] as CustomEvent;
			expect(docEvent.detail?.instance).toBe(select);
			expect(docEvent.detail?.element).toBe(selectEl);
			expect(docEvent.detail?.payload?.value).toBe('1');

			// Cleanup
			selectEl.removeEventListener('change', elementChangeHandler);
			document.removeEventListener('kt-select:change', documentChangeHandler);
		});

		it('should close dropdown when Enter is pressed even if first option is already selected', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Option 1' },
				{ value: '2', text: 'Option 2' },
				{ value: '3', text: 'Option 3' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Select the first option first
			select.toggleSelection('1');
			await waitFor(100);
			expect(select.getSelectedOptions()).toContain('1');

			// Open dropdown again
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);

			// Press Enter - even though first option is already selected, dropdown should close
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(200);

			// Dropdown should be closed even though option was already selected
			expect(select.isDropdownOpen()).toBe(false);
		});

		it('should not close dropdown when Enter is pressed with no available options', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Apple' },
				{ value: '2', text: 'Banana' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);

			// Type search query that matches no results
			searchInput.value = 'XYZ123NoMatch';
			const inputEvent = new Event('input', { bubbles: true });
			searchInput.dispatchEvent(inputEvent);
			await waitFor(150); // Wait for filtering

			// Verify dropdown is still open
			expect(select.isDropdownOpen()).toBe(true);

			// Press Enter - should not close dropdown since no options available
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			await waitFor(150);

			// Dropdown should remain open (no option to select)
			expect(select.isDropdownOpen()).toBe(true);

			// No selection should be made
			expect(select.getSelectedOptions().length).toBe(0);
		});

		it('should focus display element (button) after closing dropdown with Enter key', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Option 1' },
				{ value: '2', text: 'Option 2' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				closeOnEnter: true,
				height: 250,
			});

			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();

			// Focus search input
			searchInput.focus();
			await waitFor(50);
			expect(document.activeElement).toBe(searchInput);

			// Press Enter to select and close
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);

			// Wait for dropdown to close and focus to move
			await waitFor(200);

			// Dropdown should be closed
			expect(select.isDropdownOpen()).toBe(false);

			// Display element (button) should be focused so user can press Enter again
			const displayElement = select.getDisplayElement();
			expect(displayElement).toBeTruthy();
			expect(document.activeElement).toBe(displayElement);
		});
	});

	describe('Global Dropdown Management', () => {
		it('should close other open dropdowns when opening a new one (default behavior)', async () => {
			const selectEl1 = createSelectElement();
			const selectEl2 = createSelectElement([
				{ value: 'a', text: 'Option A' },
				{ value: 'b', text: 'Option B' },
			]);
			container.appendChild(selectEl1);
			container.appendChild(selectEl2);

			const select1 = new KTSelect(selectEl1, { height: 250 });
			const select2 = new KTSelect(selectEl2, { height: 250 });

			await waitForInit(select1);
			await waitForInit(select2);

			// Open first dropdown
			select1.openDropdown();
			await waitFor(200);
			expect(select1.isDropdownOpen()).toBe(true);

			// Open second dropdown - should close first
			select2.openDropdown();
			await waitFor(200);

			// First dropdown should be closed
			expect(select1.isDropdownOpen()).toBe(false);

			// Second dropdown should be open
			expect(select2.isDropdownOpen()).toBe(true);
		});

		it('should allow multiple dropdowns when closeOnOtherOpen is disabled', async () => {
			const selectEl1 = createSelectElement();
			const selectEl2 = createSelectElement([
				{ value: 'a', text: 'Option A' },
				{ value: 'b', text: 'Option B' },
			]);
			container.appendChild(selectEl1);
			container.appendChild(selectEl2);

			const select1 = new KTSelect(selectEl1, {
				closeOnOtherOpen: false,
				height: 250,
			});
			const select2 = new KTSelect(selectEl2, {
				closeOnOtherOpen: false,
				height: 250,
			});

			await waitForInit(select1);
			await waitForInit(select2);

			// Open first dropdown
			select1.openDropdown();
			await waitFor(200);
			expect(select1.isDropdownOpen()).toBe(true);

			// Open second dropdown - first should remain open
			select2.openDropdown();
			await waitFor(200);

			// Both dropdowns should be open
			expect(select1.isDropdownOpen()).toBe(true);
			expect(select2.isDropdownOpen()).toBe(true);
		});

		it('should remove instance from registry when dropdown closes', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, { height: 250 });
			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(100);

			// Close dropdown
			select.closeDropdown();
			await waitFor(100);

			// Registry should be empty (we can't directly access private static, but we can verify behavior)
			// Opening another dropdown should work without issues
			const selectEl2 = createSelectElement([{ value: 'a', text: 'Option A' }]);
			container.appendChild(selectEl2);
			const select2 = new KTSelect(selectEl2, { height: 250 });
			await waitForInit(select2);
			select2.openDropdown();
			await waitFor(100);

			// Should work without errors
			expect(select2).toBeTruthy();
		});

		it('should clean up registry when instance is destroyed', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, { height: 250 });
			await waitForInit(select);

			// Open dropdown
			select.openDropdown();
			await waitFor(100);

			// Destroy instance
			select.destroy();
			await waitFor(100);

			// Creating a new select should work without issues
			const selectEl2 = createSelectElement([{ value: 'a', text: 'Option A' }]);
			container.appendChild(selectEl2);
			const select2 = new KTSelect(selectEl2, { height: 250 });
			await waitForInit(select2);
			select2.openDropdown();
			await waitFor(100);

			// Should work without errors
			expect(select2).toBeTruthy();
		});
	});

	describe('Global Event Dispatch', () => {
		it('should dispatch events on document when dispatchGlobalEvents is enabled (default)', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				dispatchGlobalEvents: true,
				height: 250,
			});

			await waitForInit(select);

			// Set up document listener
			const showHandler = vi.fn();
			document.addEventListener('kt-select:show', showHandler);

			// Open dropdown
			select.openDropdown();
			await waitFor(100);

			// Event should be dispatched on document
			expect(showHandler).toHaveBeenCalledTimes(1);
			const event = showHandler.mock.calls[0][0] as CustomEvent;
			expect(event.detail.instance).toBe(select);
			expect(event.detail.element).toBe(selectEl);

			// Cleanup
			document.removeEventListener('kt-select:show', showHandler);
		});

		it('should not dispatch events on document when dispatchGlobalEvents is disabled', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				dispatchGlobalEvents: false,
				height: 250,
			});

			await waitForInit(select);

			// Set up document listener
			const showHandler = vi.fn();
			document.addEventListener('kt-select:show', showHandler);

			// Open dropdown
			select.openDropdown();
			await waitFor(100);

			// Event should NOT be dispatched on document
			expect(showHandler).not.toHaveBeenCalled();

			// Cleanup
			document.removeEventListener('kt-select:show', showHandler);
		});

		it('should dispatch events on element regardless of dispatchGlobalEvents setting', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				dispatchGlobalEvents: false,
				height: 250,
			});

			await waitForInit(select);

			// Set up element listener
			const showHandler = vi.fn();
			selectEl.addEventListener('show', showHandler);

			// Open dropdown
			select.openDropdown();
			await waitFor(100);

			// Event should be dispatched on element
			expect(showHandler).toHaveBeenCalledTimes(1);

			// Cleanup
			selectEl.removeEventListener('show', showHandler);
		});

		it('should dispatch both namespaced and non-namespaced events on document', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				dispatchGlobalEvents: true,
				height: 250,
			});

			await waitForInit(select);

			// Set up listeners for both namespaced and non-namespaced
			const namespacedHandler = vi.fn();
			const nonNamespacedHandler = vi.fn();

			// Use capture phase to catch events before they bubble
			document.addEventListener('kt-select:show', namespacedHandler, true);
			document.addEventListener('show', nonNamespacedHandler, true);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			// Both events should fire on document
			expect(namespacedHandler).toHaveBeenCalledTimes(1);
			// Non-namespaced event should also be dispatched on document (for jQuery compatibility)
			const nonNamespacedCalls = nonNamespacedHandler.mock.calls.filter(
				(call) => call[0].type === 'show' && call[0].target === document,
			);
			expect(nonNamespacedCalls.length).toBe(1);

			// Verify event detail structure is consistent
			const namespacedEvent = namespacedHandler.mock.calls[0][0] as CustomEvent;
			const nonNamespacedEvent = nonNamespacedCalls[0][0] as CustomEvent;
			expect(nonNamespacedEvent.detail.instance).toBe(select);
			expect(nonNamespacedEvent.detail.element).toBe(selectEl);
			expect(nonNamespacedEvent.detail).toEqual(namespacedEvent.detail);

			// Cleanup
			document.removeEventListener('kt-select:show', namespacedHandler, true);
			document.removeEventListener('show', nonNamespacedHandler, true);
		});

		it('should support jQuery-style non-namespaced event listeners on document', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				dispatchGlobalEvents: true,
				height: 250,
			});

			await waitForInit(select);

			// Simulate jQuery-style listener: $(document).on('show', ...)
			const showHandler = vi.fn();
			document.addEventListener('show', showHandler);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			// Event should be dispatched on document and handler should be called
			// Filter to only count events dispatched directly on document (not bubbled from element)
			const documentEvents = showHandler.mock.calls.filter(
				(call) => call[0].target === document,
			);
			expect(documentEvents.length).toBe(1);
			const event = documentEvents[0][0] as CustomEvent;
			expect(event.type).toBe('show');
			expect(event.target).toBe(document);
			expect(event.detail.instance).toBe(select);
			expect(event.detail.element).toBe(selectEl);

			// Cleanup
			document.removeEventListener('show', showHandler);
		});

		it('should include component instance and element in event detail', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				dispatchGlobalEvents: true,
				height: 250,
			});

			await waitForInit(select);

			// Set up document listener
			const closeHandler = vi.fn();
			document.addEventListener('kt-select:close', closeHandler);

			// Open dropdown first
			select.openDropdown();
			await waitFor(200);

			// Clear handler calls from open event
			closeHandler.mockClear();

			// Close dropdown
			select.closeDropdown();
			await waitFor(200);

			// Event should include instance and element
			expect(closeHandler).toHaveBeenCalledTimes(1);
			const event = closeHandler.mock.calls[0][0] as CustomEvent;
			expect(event.detail.instance).toBe(select);
			expect(event.detail.element).toBe(selectEl);

			// Cleanup
			document.removeEventListener('kt-select:close', closeHandler);
		});

		it('should dispatch change events on document when configured', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				dispatchGlobalEvents: true,
				height: 250,
			});

			await waitForInit(select);

			// Set up document listener
			const changeHandler = vi.fn();
			document.addEventListener('kt-select:change', changeHandler);

			// Select an option by clicking on it
			select.openDropdown();
			await waitFor(200);

			const option = select
				.getDropdownElement()
				?.querySelector(
					'[data-kt-select-option][data-value="1"]',
				) as HTMLElement;

			expect(option).toBeTruthy();
			option.click();
			await waitFor(200);

			// Event should be dispatched on document
			expect(changeHandler).toHaveBeenCalled();

			// Cleanup
			document.removeEventListener('kt-select:change', changeHandler);
		});
	});

	describe('Integration Tests', () => {
		it('should work correctly with all features enabled', async () => {
			const selectEl = createSelectElement();
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				enableSearch: true,
				searchAutofocus: true,
				closeOnEnter: true,
				closeOnOtherOpen: true,
				dispatchGlobalEvents: true,
				height: 250,
			});

			await waitForInit(select);

			// Set up document listener
			const showHandler = vi.fn();
			document.addEventListener('kt-select:show', showHandler);

			// Open dropdown
			select.openDropdown();
			await waitFor(200);

			// Verify autofocus
			const searchInput = select.getSearchInput();
			expect(searchInput).toBeTruthy();
			expect(document.activeElement).toBe(searchInput);

			// Verify global event dispatch
			expect(showHandler).toHaveBeenCalledTimes(1);

			// Press Enter
			const enterEvent = new KeyboardEvent('keydown', {
				key: 'Enter',
				bubbles: true,
				cancelable: true,
			});
			searchInput.dispatchEvent(enterEvent);
			await waitFor(200);

			// Verify dropdown closed
			expect(select.isDropdownOpen()).toBe(false);

			// Cleanup
			document.removeEventListener('kt-select:show', showHandler);
		});
	});

	describe('setSelectedOptions sync', () => {
		it('should sync native select, trigger display, and dropdown option when setSelectedOptions([option]) is called (single-select)', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Option 1' },
				{ value: '2', text: 'Option 2' },
				{ value: '3', text: 'Option 3' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, { height: 250 });
			await waitForInit(select);

			const option2 = selectEl.querySelector(
				'option[value="2"]',
			) as HTMLOptionElement;
			expect(option2).toBeTruthy();

			select.setSelectedOptions([option2]);
			await waitFor(50);

			expect(select.getSelectedOptions()).toEqual(['2']);
			expect((selectEl as HTMLSelectElement).value).toBe('2');
			const displayEl = select.getValueDisplayElement();
			expect(displayEl?.textContent?.trim()).toBe('Option 2');

			select.openDropdown();
			await waitFor(100);
			const dropdownOption = select
				.getDropdownElement()
				?.querySelector('[data-kt-select-option][data-value="2"]');
			expect(dropdownOption?.classList.contains('selected')).toBe(true);
			expect(dropdownOption?.getAttribute('aria-selected')).toBe('true');
		});

		it('should sync native options, trigger/tags, and dropdown when setSelectedOptions([optionA, optionB]) is called (multi-select)', async () => {
			const selectEl = createSelectElement([
				{ value: 'a', text: 'A' },
				{ value: 'b', text: 'B' },
				{ value: 'c', text: 'C' },
			]);
			selectEl.setAttribute('multiple', 'multiple');
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				multiple: true,
				height: 250,
			});
			await waitForInit(select);

			const optionA = selectEl.querySelector(
				'option[value="a"]',
			) as HTMLOptionElement;
			const optionB = selectEl.querySelector(
				'option[value="b"]',
			) as HTMLOptionElement;
			expect(optionA).toBeTruthy();
			expect(optionB).toBeTruthy();

			select.setSelectedOptions([optionA, optionB]);
			await waitFor(50);

			expect(select.getSelectedOptions()).toContain('a');
			expect(select.getSelectedOptions()).toContain('b');
			expect(select.getSelectedOptions().length).toBe(2);
			expect(optionA.selected).toBe(true);
			expect(optionB.selected).toBe(true);
			const optionC = selectEl.querySelector(
				'option[value="c"]',
			) as HTMLOptionElement;
			expect(optionC.selected).toBe(false);

			select.openDropdown();
			await waitFor(100);
			const dropdownA = select
				.getDropdownElement()
				?.querySelector('[data-kt-select-option][data-value="a"]');
			const dropdownB = select
				.getDropdownElement()
				?.querySelector('[data-kt-select-option][data-value="b"]');
			expect(dropdownA?.classList.contains('selected')).toBe(true);
			expect(dropdownB?.classList.contains('selected')).toBe(true);
		});

		it('should clear selection, native select, show placeholder, and remove selected state when setSelectedOptions([]) is called', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'Option 1' },
				{ value: '2', text: 'Option 2' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, {
				placeholder: 'Choose...',
				height: 250,
			});
			await waitForInit(select);

			const option1 = selectEl.querySelector(
				'option[value="1"]',
			) as HTMLOptionElement;
			select.setSelectedOptions([option1]);
			await waitFor(50);
			expect(select.getSelectedOptions()).toEqual(['1']);

			select.setSelectedOptions([]);
			await waitFor(50);

			expect(select.getSelectedOptions()).toEqual([]);
			expect((selectEl as HTMLSelectElement).value).toBe('');
			Array.from(selectEl.options).forEach((opt) => {
				expect(opt.selected).toBe(false);
			});
			const displayEl = select.getValueDisplayElement();
			const placeholderEl = displayEl?.querySelector(
				'[data-kt-select-placeholder]',
			);
			expect(placeholderEl).toBeTruthy();

			select.openDropdown();
			await waitFor(100);
			const options = select
				.getDropdownElement()
				?.querySelectorAll('[data-kt-select-option]');
			options?.forEach((opt) => {
				expect(opt.classList.contains('selected')).toBe(false);
				expect(opt.getAttribute('aria-selected')).toBe('false');
			});
		});

		it('should update trigger text immediately when setSelectedOptions([option]) is called with dropdown closed, and show option selected on next open', async () => {
			const selectEl = createSelectElement([
				{ value: '1', text: 'First' },
				{ value: '2', text: 'Second' },
				{ value: '3', text: 'Third' },
			]);
			container.appendChild(selectEl);

			const select = new KTSelect(selectEl, { height: 250 });
			await waitForInit(select);
			expect(select.isDropdownOpen()).toBe(false);

			const option3 = selectEl.querySelector(
				'option[value="3"]',
			) as HTMLOptionElement;
			select.setSelectedOptions([option3]);
			await waitFor(50);

			const displayEl = select.getValueDisplayElement();
			expect(displayEl?.textContent?.trim()).toBe('Third');

			select.openDropdown();
			await waitFor(100);
			const dropdownOption = select
				.getDropdownElement()
				?.querySelector('[data-kt-select-option][data-value="3"]');
			expect(dropdownOption?.classList.contains('selected')).toBe(true);
			expect(dropdownOption?.getAttribute('aria-selected')).toBe('true');
		});
	});
});
