/**
 * Компонент автокомплита для ссылок. Показывает выпадающий список файлов и промптов
 * при вводе @/@@ и #/## с умным позиционированием относительно курсора.
 */

import { AutocompleteItem, AutocompleteManager } from './AutocompleteManager';
import { ReferenceMatch } from './ReferenceParser';
import { setIcon } from 'obsidian';

// ----------------------------------------------- Reference Autocomplete Component ---------------------------------------------------------------

export class ReferenceAutocomplete {
	private container: HTMLElement;
	private dropdown: HTMLElement;
	private inputElement: HTMLElement;
	private autocompleteManager: AutocompleteManager;
	private currentReference: ReferenceMatch | null = null;
	private selectedIndex = 0;
	private currentOptions: AutocompleteItem[] = [];
	private onItemSelectedCallback?: (item: AutocompleteItem, match: ReferenceMatch) => void;
	private mouseIsOverDropdown = false; // Флаг для отслеживания наведения мыши

	constructor(inputElement: HTMLElement, autocompleteManager: AutocompleteManager) {
		this.inputElement = inputElement;
		this.autocompleteManager = autocompleteManager;
		this.container = this.inputElement.closest('.chat-input-container') as HTMLElement;

		this.createDropdown();
		this.setupEventListeners();
	}

	// ----------------------------------------------- Public Methods ---------------------------------------------------------------

	/**
	 * Показывает автокомплит с опциями.
	 * @param reference - объект ReferenceMatch, определяющий текущий вводимый референс.
	 * @param cursorRect - DOMRect объекта курсора, для определения позиции.
	 */
	async showAutocomplete(reference: ReferenceMatch, cursorRect: DOMRect): Promise<void> {
		this.currentReference = reference;

		// Получаем опции в зависимости от типа ссылки
		const options =
			reference.type === 'file'
				? await this.autocompleteManager.getFileOptions(reference.name)
				: await this.autocompleteManager.getPromptOptions(reference.name);

		if (options.length === 0) {
			this.hideAutocomplete();
			return;
		}

		this.currentOptions = options;
		// Сбрасываем selection, если новые опции не содержат предыдущий выбранный элемент
		if (this.selectedIndex >= this.currentOptions.length) {
			this.selectedIndex = 0;
		}
		this.renderOptions();
		this.positionDropdown(cursorRect);
		this.dropdown.style.display = 'block';
	}

	/**
	 * Скрывает автокомплит
	 */
	hideAutocomplete(): void {
		this.dropdown.style.display = 'none';
		this.currentReference = null;
		this.currentOptions = [];
		this.selectedIndex = 0;
	}

	/**
	 * Обрабатывает нажатия клавиш для навигации
	 * @param event - событие клавиатуры
	 * @returns true если событие было обработано
	 */
	handleKeydown(event: KeyboardEvent): boolean {
		// Мы хотим, чтобы стрелки и Enter обрабатывались только если автокомплит видим
		if (!this.isVisible()) {
			return false;
		}

		switch (event.key) {
			case 'ArrowDown':
				event.preventDefault(); // Предотвращаем прокрутку страницы
				this.moveSelection(1);
				return true;

			case 'ArrowUp':
				event.preventDefault(); // Предотвращаем прокрутку страницы
				this.moveSelection(-1);
				return true;

			case 'Enter':
			case 'Tab':
				// Разрешаем Tab завершать автокомплит
				event.preventDefault();
				this.selectCurrentItem();
				return true;

			case 'Escape':
				event.preventDefault();
				this.hideAutocomplete();
				return true;

			default:
				return false;
		}
	}

	/**
	 * Устанавливает коллбек для выбора элемента
	 * @param callback - функция обратного вызова
	 */
	onItemSelected(callback: (item: AutocompleteItem, match: ReferenceMatch) => void): void {
		this.onItemSelectedCallback = callback;
	}

	/**
	 * Проверяет, виден ли автокомплит
	 * @returns true если виден
	 */
	isVisible(): boolean {
		return this.dropdown.style.display === 'block';
	}

	// ----------------------------------------------- Private Methods ---------------------------------------------------------------

	/**
	 * Создает элемент выпадающего списка
	 */
	private createDropdown(): void {
		this.dropdown = document.createElement('div');
		this.dropdown.className = 'reference-autocomplete';
		this.dropdown.style.display = 'none';

		// Добавляем dropdown в контейнер
		this.container.appendChild(this.dropdown);
	}

	/**
	 * Настраивает обработчики событий
	 */
	private setupEventListeners(): void {
		// Закрытие при клике вне dropdown
		document.addEventListener('click', (e) => {
			if (!this.dropdown.contains(e.target as Node) && !this.inputElement.contains(e.target as Node)) {
				this.hideAutocomplete();
			}
		});

		// Отслеживание наведения мыши для предотвращения скрытия по blur
		this.dropdown.addEventListener('mouseenter', () => {
			this.mouseIsOverDropdown = true;
		});
		this.dropdown.addEventListener('mouseleave', () => {
			this.mouseIsOverDropdown = false;
		});

		// Скрытие при потере фокуса input элементом, если мышь не над дропдауном
		this.inputElement.addEventListener('blur', () => {
			// Задержка позволяет клику на элемент дропдауна сработать
			if (!this.mouseIsOverDropdown) {
				this.hideAutocomplete();
			}
		});
	}

	/**
	 * Отрисовывает опции в dropdown
	 */
	private renderOptions(): void {
		this.dropdown.innerHTML = '';

		this.currentOptions.forEach((option, index) => {
			const optionElement = this.createOptionElement(option, index === this.selectedIndex);
			this.dropdown.appendChild(optionElement);
		});
		this.scrollToSelected(); // Убеждаемся, что выбранный элемент виден
	}

	/**
	 * Создает элемент опции
	 * @param option - опция для отображения
	 * @param isSelected - выбрана ли опция
	 * @returns HTML элемент опции
	 */
	private createOptionElement(option: AutocompleteItem, isSelected: boolean): HTMLElement {
		const optionDiv = document.createElement('div');
		optionDiv.className = `reference-option ${isSelected ? 'selected' : ''}`;

		// Иконка
		const iconSpan = document.createElement('span');
		iconSpan.className = 'reference-option-icon';
		setIcon(iconSpan, option.icon);

		// Имя файла
		const nameSpan = document.createElement('span');
		nameSpan.className = 'reference-option-name';
		nameSpan.textContent = option.name;

		// Путь к файлу
		const pathSpan = document.createElement('span');
		pathSpan.className = 'reference-option-path';
		pathSpan.textContent = option.path;

		optionDiv.appendChild(iconSpan);
		optionDiv.appendChild(nameSpan);
		optionDiv.appendChild(pathSpan);

		// Обработчик клика
		optionDiv.addEventListener('click', (e) => {
			e.preventDefault();
			e.stopPropagation();
			this.selectItem(option);
		});

		return optionDiv;
	}

	/**
	 * Позиционирует dropdown относительно курсора
	 * @param cursorRect - DOMRect объекта курсора, для определения позиции.
	 */
	private positionDropdown(cursorRect: DOMRect): void {
		// Чтобы получить корректные размеры, устанавливаем display: 'block'
		this.dropdown.style.display = 'block';

		const inputRect = this.inputElement.getBoundingClientRect();
		const containerRect = this.container.getBoundingClientRect();
		const dropdownRect = this.dropdown.getBoundingClientRect(); // Получаем размеры дропдауна
		const viewportHeight = window.innerHeight;

		// Позиционируем относительно inputElement, т.к. он уже в потоке документа
		// Левый край дропдауна совпадает с левым краем курсора
		let left = cursorRect.left - containerRect.left;

		// Дефолтное положение - под курсором
		let top = cursorRect.bottom - containerRect.top + 5; // +5px отступ

		// Проверяем, есть ли место снизу
		const spaceBelow = viewportHeight - cursorRect.bottom;
		const spaceAbove = cursorRect.top;

		if (spaceBelow < dropdownRect.height + 10 && spaceAbove > dropdownRect.height + 10) {
			// Если снизу мало места и сверху достаточно, размещаем сверху
			top = cursorRect.top - containerRect.top - dropdownRect.height - 5; // -5px отступ
		}

		// Убедимся, что дропдаун не выходит за границы контейнера справа
		const maxRight = inputRect.right - containerRect.left;
		if (left + dropdownRect.width > maxRight) {
			left = maxRight - dropdownRect.width;
			if (left < 0) left = 0; // Если дропдаун шире контейнера, прижимаем к левому краю
		}
		// Убедимся, что дропдаун не выходит за границы контейнера слева
		if (left < 0) {
			left = 0;
		}

		this.dropdown.style.top = `${top}px`;
		this.dropdown.style.left = `${left}px`;
		// Устанавливаем ширину дропдауна равной ширине inputElement, если он больше
		// Это предотвратит слишком узкий дропдаун, если имя файла короткое
		this.dropdown.style.minWidth = `${inputRect.width / 2}px`; // Минимум половина ширины инпута
		this.dropdown.style.width = 'auto'; // Позволяем контенту определить ширину, но не меньше minWidth
	}

	/**
	 * Перемещает выделение в списке
	 * @param direction - направление (1 = вниз, -1 = вверх)
	 */
	private moveSelection(direction: number): void {
		this.selectedIndex += direction;

		if (this.selectedIndex < 0) {
			this.selectedIndex = this.currentOptions.length - 1;
		} else if (this.selectedIndex >= this.currentOptions.length) {
			this.selectedIndex = 0;
		}

		this.renderOptions();
		this.scrollToSelected();
	}

	/**
	 * Прокручивает список к выбранному элементу
	 */
	private scrollToSelected(): void {
		const selectedElement = this.dropdown.querySelector('.reference-option.selected') as HTMLElement;
		if (selectedElement) {
			selectedElement.scrollIntoView({ block: 'nearest' });
		}
	}

	/**
	 * Выбирает текущий элемент
	 */
	private selectCurrentItem(): void {
		if (this.selectedIndex >= 0 && this.selectedIndex < this.currentOptions.length) {
			this.selectItem(this.currentOptions[this.selectedIndex]);
		}
	}

	/**
	 * Выбирает указанный элемент
	 * @param item - выбранный элемент
	 */
	private selectItem(item: AutocompleteItem): void {
		if (this.onItemSelectedCallback && this.currentReference) {
			this.onItemSelectedCallback(item, this.currentReference);
		}
		this.hideAutocomplete();
	}

	/**
	 * Очищает ресурсы
	 */
	destroy(): void {
		if (this.dropdown.parentNode) {
			this.dropdown.parentNode.removeChild(this.dropdown);
		}
	}
}
