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

import { KTSelectConfigInterface } from './config';
import { KTSelect } from './select';
import { filterOptions, FocusManager } from './utils';
import { defaultTemplates } from './templates';

/**
 * KTSelectCombobox - Handles combobox-specific functionality for KTSelect
 */
export class KTSelectCombobox {
	private _select: KTSelect;
	private _config: KTSelectConfigInterface;
	private _searchInputElement: HTMLInputElement | null;
	private _clearButtonElement: HTMLElement | null;
	private _boundInputHandler: (event: Event) => void;
	private _boundClearHandler: (event: MouseEvent) => void;
	private _valuesContainerElement: HTMLElement | null; // For tags or displayTemplate output

	constructor(select: KTSelect) {
		this._select = select;
		this._config = select.getConfig();

		const displayElement = select.getDisplayElement(); // KTSelect's main display element for combobox

		this._searchInputElement = displayElement.querySelector(
			'input[data-kt-select-search]',
		);
		this._clearButtonElement = displayElement.querySelector(
			'[data-kt-select-clear-button]',
		);
		this._valuesContainerElement = displayElement.querySelector(
			'[data-kt-select-combobox-values]',
		);

		this._boundInputHandler = this._handleComboboxInput.bind(this);
		this._boundClearHandler = this._handleClearButtonClick.bind(this);

		this._attachEventListeners();

		this._select.getElement()?.addEventListener('dropdown.close', () => {
			// When dropdown closes, if not multi-select and not using displayTemplate,
			// ensure input shows the selected value or placeholder.
			if (!this._config.multiple && !this._config.displayTemplate) {
				this.updateDisplay(this._select.getSelectedOptions());
			} else {
				// For tags or displayTemplate, the input should be clear for typing.
				if (this._searchInputElement) {
					this._searchInputElement.value = '';
				}
			}
			this._toggleClearButtonVisibility(this._searchInputElement?.value ?? '');
			// this._select.showAllOptions(); // showAllOptions might be too broad, filtering is managed by typing.
		});
	}

	/**
	 * Attach event listeners specific to combobox
	 */
	private _attachEventListeners(): void {
		this._removeEventListeners();
		if (this._searchInputElement) {
			// Ensure element exists
			this._searchInputElement.addEventListener(
				'input',
				this._boundInputHandler,
			);
		}
		if (this._clearButtonElement) {
			this._clearButtonElement.addEventListener(
				'click',
				this._boundClearHandler,
			);
		}
	}

	/**
	 * Remove event listeners to prevent memory leaks or duplicates
	 */
	private _removeEventListeners(): void {
		if (this._searchInputElement) {
			this._searchInputElement.removeEventListener(
				'input',
				this._boundInputHandler,
			);
		}
		if (this._clearButtonElement) {
			this._clearButtonElement.removeEventListener(
				'click',
				this._boundClearHandler,
			);
		}
	}

	/**
	 * Handle combobox input events
	 */
	private _handleComboboxInput(event: Event): void {
		const inputElement = event.target as HTMLInputElement;
		const query = inputElement.value;

		this._toggleClearButtonVisibility(query);

		if (!this._select.isDropdownOpen()) {
			// Use public getter
			this._select.openDropdown();
		}
		// For single select without displayTemplate, if user types, they are essentially clearing the previous selection text
		// The actual selection state isn't cleared until they pick a new option or clear explicitly.
		// For multi-select or with displayTemplate, the input is purely for search.
		if (this._config.multiple || this._config.displayTemplate) {
			// Values are in _valuesContainerElement, input is for search
		} else {
			// Single select, no displayTemplate: If user types, it implies they might be changing selection.
			// We don't clear the actual _select state here, just the visual in input.
		}

		this._filterOptionsForCombobox(query);
	}

	/**
	 * Handle clear button click
	 */
	private _handleClearButtonClick(event: MouseEvent): void {
		event.preventDefault();
		event.stopPropagation();
		if (!this._searchInputElement) return;

		this._searchInputElement.value = '';
		this._toggleClearButtonVisibility('');
		if (this._valuesContainerElement) {
			this._valuesContainerElement.innerHTML = '';
		}
		this._select.clearSelection(); // This will also trigger updateSelectedOptionDisplay
		this._select.showAllOptions(); // Show all options after clearing
		this._select.openDropdown();
		this._searchInputElement?.focus();
	}

	/**
	 * Toggle clear button visibility based on input value and selection state.
	 * Clear button should be visible if there's text in input OR if there are selected items (for multi/displayTemplate modes).
	 */
	private _toggleClearButtonVisibility(inputValue: string): void {
		if (!this._clearButtonElement) return;
		const hasSelectedItems = this._select.getSelectedOptions().length > 0;

		if (
			inputValue.length > 0 ||
			(hasSelectedItems &&
				(this._config.multiple || this._config.displayTemplate))
		) {
			this._clearButtonElement.classList.remove('hidden');
		} else {
			this._clearButtonElement.classList.add('hidden');
		}
	}

	/**
	 * Filter options for combobox based on input query
	 */
	private _filterOptionsForCombobox(query: string): void {
		const options = Array.from(
			this._select.getOptionsElement(),
		) as HTMLElement[];
		const config = this._select.getConfig();
		const dropdownElement = this._select.getDropdownElement();
		filterOptions(options, query, config, dropdownElement);
		// After filtering, focusManager in KTSelectSearch (if search is also enabled there)
		// or the main FocusManager should adjust focus if needed.
		// For combobox, this filtering is the primary search mechanism.
		// We might need to tell select's focus manager to focus first option.
		const focusManager = (
			this._select as unknown as { _focusManager?: FocusManager }
		)._focusManager;
		focusManager?.focusFirst();
	}

	/**
	 * Updates the combobox display (input field or values container) based on selection.
	 */
	public updateDisplay(selectedOptions: string[]): void {
		if (!this._searchInputElement) return;

		// Always clear the values container first if it exists
		if (this._valuesContainerElement) {
			this._valuesContainerElement.innerHTML = '';
		}

		if (this._config.tags && this._valuesContainerElement) {
			const valuesContainer = this._valuesContainerElement;
			const selectElement = this._select.getElement();
			if (!selectElement) return;
			// Combobox + Tags
			selectedOptions.forEach((value) => {
				// Ensure value is properly escaped for querySelector
				const optionElement = selectElement.querySelector(
					`option[value="${CSS.escape(value)}"]`,
				) as HTMLOptionElement;
				if (optionElement) {
					const tagElement = defaultTemplates.tag(optionElement, this._config);
					valuesContainer.appendChild(tagElement);
				}
			});
			this._searchInputElement.value = ''; // Input field is for typing new searches
			this._searchInputElement.placeholder =
				selectedOptions.length > 0
					? ''
					: this._config.placeholder || 'Select...';
		} else if (this._config.displayTemplate && this._valuesContainerElement) {
			// Combobox + DisplayTemplate (no tags)
			this._valuesContainerElement.innerHTML =
				this._select.renderDisplayTemplateForSelected(selectedOptions);
			this._searchInputElement.value = ''; // Input field is for typing new searches
			this._searchInputElement.placeholder =
				selectedOptions.length > 0
					? ''
					: this._config.placeholder || 'Select...';
		} else if (this._config.multiple && this._valuesContainerElement) {
			// Combobox + Multiple (no tags, no display template)
			// For simplicity, join text. A proper tag implementation would be more complex here.
			this._valuesContainerElement.innerHTML = selectedOptions
				.map((value) => {
					const optionEl = this._select
						.getElement()
						?.querySelector(`option[value="${CSS.escape(value)}"]`);
					return optionEl ? optionEl.textContent : '';
				})
				.join(', '); // Basic comma separation
			this._searchInputElement.value = '';
			this._searchInputElement.placeholder =
				selectedOptions.length > 0
					? ''
					: this._config.placeholder || 'Select...';
		} else if (!this._config.multiple && selectedOptions.length > 0) {
			// Single select combobox: display selected option's text in the input
			const selectedValue = selectedOptions[0];
			const optionElement = this._select
				.getElement()
				?.querySelector(`option[value="${CSS.escape(selectedValue)}"]`);
			this._searchInputElement.value = optionElement
				? optionElement.textContent || ''
				: '';
			// placeholder is implicitly handled by input value for single select
		} else {
			// No selection or not fitting above categories (e.g. single select, no items)
			this._searchInputElement.value = '';
			this._searchInputElement.placeholder =
				this._config.placeholder || 'Select...';
			// _valuesContainerElement is already cleared if it exists
		}
		this._toggleClearButtonVisibility(this._searchInputElement.value);
	}

	/**
	 * Destroy the combobox component and clean up event listeners
	 */
	public destroy(): void {
		this._removeEventListeners();
	}
}
