/*
 * Управляет пользовательским интерфейсом чата, включая ввод сообщений,
 * отображение истории и подсказки команд для взаимодействия с пользователем.
 */

import { ChatHistoryItem } from './models/ChatHistoryItem';
import { AutocompleteManager } from '../references/AutocompleteManager';
import { ReferenceAutocomplete } from '../references/ReferenceAutocomplete';
import { ReferenceExpander } from '../references/ReferenceExpander';
import { TemplateEngine } from '../references/TemplateEngine';
import { ReferenceParser, FileReference, ReferenceMatch } from '../references/ReferenceParser';
import { ReferenceBox } from '../references/ReferenceBox';
import { AutocompleteItem } from '../references/AutocompleteManager';
import LLMAgentPlugin from 'main';
import { MarkdownRenderer, setIcon } from 'obsidian'; // Добавляем импорт MarkdownRenderer

export class ChatPanel {
	container: HTMLElement;
	props: {
		chatHistory: ChatHistoryItem[];
		onSendMessage: (message: string, selectedModel?: string) => void;
		availableModels?: string[];
		selectedModel?: string;
		plugin: LLMAgentPlugin;
	};
	chatHistory: ChatHistoryItem[];
	messageInput: HTMLDivElement;
	sendButton: HTMLButtonElement;
	suggestionsContainer: HTMLDivElement;
	modelSelectButton: HTMLButtonElement;
	modelDropdown: HTMLDivElement;
	selectedModel: string;

	// ----------------------------------------------- Reference System Fields ---------------------------------------------------------------
	private autocompleteManager: AutocompleteManager;
	private referenceAutocomplete: ReferenceAutocomplete;
	private referenceExpander: ReferenceExpander;
	private templateEngine: TemplateEngine;
	private currentReferences: FileReference[] = [];
	private isProcessingReference = false;

	constructor(
		container: HTMLElement,
		props: {
			chatHistory: ChatHistoryItem[];
			onSendMessage: (message: string, selectedModel?: string) => void;
			availableModels?: string[];
			selectedModel?: string;
			plugin: LLMAgentPlugin; // Обновленный тип
		}
	) {
		this.container = container;
		this.props = props;
		this.chatHistory = props.chatHistory || [];
		this.selectedModel = props.selectedModel || 'gemini-2.5-flash';

		// Инициализация системы ссылок
		this.initializeReferenceSystem();

		this.init();
	}

	updatePlaceholder(): void {
		const isEmpty = this.messageInput.textContent?.trim() === '';
		const isFocused = document.activeElement === this.messageInput;

		if (isEmpty && !isFocused) {
			this.messageInput.classList.add('is-empty');
		} else {
			this.messageInput.classList.remove('is-empty');
		}
	}

	toggleModelDropdown(): void {
		const isVisible = this.modelDropdown.style.display === 'block';
		if (isVisible) {
			this.modelDropdown.style.display = 'none';
			return;
		}

		// Показываем дропдаун, чтобы получить его размеры и положение
		this.modelDropdown.style.display = 'block';

		const container = this.modelSelectButton.closest('.chat-input-container') as HTMLElement;

		const modelSelectButtonRect = this.modelSelectButton.getBoundingClientRect();
		const containerRect = container.getBoundingClientRect(); // Общие границы чат-панели
		const dropdownRect = this.modelDropdown.getBoundingClientRect();
		const viewportHeight = window.innerHeight;

		// Позиция относительно контейнера chatPanel
		// Левый край дропдауна совпадает с левым краем кнопки выбора модели
		let left = modelSelectButtonRect.left - containerRect.left - 5;

		// Дефолтное положение - под кнопкой
		let top = modelSelectButtonRect.height;

		// Проверяем, помещается ли dropdown снизу
		const spaceBelow = viewportHeight - modelSelectButtonRect.bottom;
		const spaceAbove = modelSelectButtonRect.top;

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

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

		// Убедимся, что дропдаун не выходит за границы контейнера слева
		if (left < 0) {
			left = 0;
		}

		this.modelDropdown.style.top = `${top}px`;
		this.modelDropdown.style.left = `${left}px`;
		this.modelDropdown.style.right = 'auto'; // Сброс right, чтобы left работал корректно

		// Устанавливаем минимальную ширину дропдауна, равную ширине кнопки
		let minWidth = Math.min(containerRect.width / 2, dropdownRect.width);
		this.modelDropdown.style.minWidth = `${minWidth}px`;
		this.modelDropdown.style.width = 'auto'; // Позволяем контенту определить ширину, но не меньше minWidth
	}

	updateModelDropdown(): void {
		if (!this.props.availableModels) return;

		this.modelDropdown.innerHTML = this.props.availableModels
			.map(
				(model) => `
				<div class="model-option" data-model="${model}">
					${model}
				</div>
			`
			)
			.join('');

		// Add event listeners to model options
		this.modelDropdown.querySelectorAll('.model-option').forEach((option) => {
			option.addEventListener('click', () => {
				const model = (option as HTMLElement).dataset.model;
				if (model) {
					this.setSelectedModel(model);
				}
			});
		});
	}

	setSelectedModel(model: string): void {
		this.selectedModel = model;
		const modelNameElement = this.container.querySelector('#selected-model-name');
		if (modelNameElement) {
			modelNameElement.textContent = model;
		}
		this.modelDropdown.style.display = 'none';
	}

	handleInput(): void {
		const value = this.messageInput.textContent || '';
		const commands = ['/help', '/imagine', '/analyze', '/subtitles_meet', '/subtitles_teams', '/summarize', '/chat'];

		if (value.startsWith('/')) {
			const filteredCommands = commands.filter((cmd) => cmd.startsWith(value.toLowerCase()));
			this.showSuggestions(filteredCommands);
		} else {
			this.hideSuggestions();
		}
	}

	showSuggestions(suggestions: string[]): void {
		if (suggestions.length === 0) {
			this.hideSuggestions();
			return;
		}

		this.suggestionsContainer.innerHTML = suggestions.map((suggestion) => `<div class="suggestion-item">${suggestion}</div>`).join('');

		this.suggestionsContainer.style.display = 'block';

		// Добавляем обработчики кликов на предложения
		this.suggestionsContainer.querySelectorAll('.suggestion-item').forEach((item) => {
			item.addEventListener('click', () => {
				this.messageInput.textContent = item.textContent || '';
				this.hideSuggestions();
				this.messageInput.focus();
			});
		});
	}

	hideSuggestions(): void {
		this.suggestionsContainer.style.display = 'none';
	}

	/**
	 * Рендерит историю сообщений в контейнер чата.
	 * @returns {Promise<void>}
	 */
	async renderHistory(): Promise<void> {
		const historyContainer = this.container.querySelector('#chat-history') as HTMLElement;
		historyContainer.empty(); // Очищаем контейнер перед добавлением новых сообщений

		for (const msg of this.chatHistory) {
			const messageEl = historyContainer.createDiv({ cls: `message ${msg.role} message-type-${msg.type || 'default'} group/turn-messages` });

			// Информация об отправителе
			const metaDiv = messageEl.createDiv({ cls: 'chat-meta' });
			const senderInfoDiv = metaDiv.createDiv({ cls: 'chat-sender-info' });
			const iconDiv = senderInfoDiv.createDiv({ cls: 'chat-message-icon' });
			iconDiv.innerHTML =
				msg.role === 'user'
					? `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>`
					: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>`;
			senderInfoDiv.createSpan({ cls: 'chat-role-label', text: this.getRoleLabel(msg.role) });

			// Контент сообщения, рендерится Markdown
			const contentDiv = messageEl.createDiv({ cls: 'chat-message-content' });
            await MarkdownRenderer.renderMarkdown(
                msg.content,
                contentDiv,
                '', // Путь к источнику, может быть пустым для сообщений чата
                this.props.plugin // Компонент, необходимый для управления жизненным циклом рендера
            );
            this.createMessageToolset(messageEl, msg.content);
		}

		// Прокручиваем к самому низу родительского контейнера (view-content)
		if (this.chatHistory.length > 0) {
			setTimeout(() => {
				const scrollContainer = this.container.closest('.view-content') as HTMLElement;
				if (scrollContainer) {
					scrollContainer.scrollTop = scrollContainer.scrollHeight;
				}
			}, 0);
		}
	}

	/**
	 * Возвращает удобочитаемую метку для роли сообщения.
	 * @param {string} role - Роль отправителя сообщения (например, 'user', 'assistant').
	 * @returns {string} Локализованная метка роли.
	 */
	getRoleLabel(role: string): string {
		switch (role) {
			case 'user':
				return 'Вы:';
			case 'assistant':
				return 'LLM Агент:';
			default:
				return `${role}:`;
		}
	}

	processMessageContent(content: string): string {
		// Простейшая обработка Markdown для изображений
		return content.replace(/<img([^>]*?)>/g, '<img$1 style="max-width: 100%; height: auto;">').replace(/\n/g, '<br>');
	}

	/**
	 * Обновляет историю чата и перерисовывает её.
	 * @param {ChatHistoryItem[]} newHistory - Новый массив элементов истории чата.
	 * @returns {Promise<void>}
	 */
	async updateHistory(newHistory: ChatHistoryItem[]): Promise<void> {
		this.chatHistory = newHistory;
		await this.renderHistory();
	}

	updateAvailableModels(models: string[]): void {
		this.props.availableModels = models;
		this.updateModelDropdown();
	}

	destroy(): void {
		if (this.referenceAutocomplete) {
			this.referenceAutocomplete.destroy();
		}
		this.container.innerHTML = '';
	}

	// ----------------------------------------------- Reference System Initialization ---------------------------------------------------------------

	/**
	 * Инициализирует систему ссылок
	 */
	private initializeReferenceSystem(): void {
		if (!this.props.plugin) {
			console.warn('Plugin не передан в ChatPanel, система ссылок отключена');
			return;
		}

		this.autocompleteManager = new AutocompleteManager(this.props.plugin);
		this.templateEngine = new TemplateEngine(this.props.plugin);
		this.referenceExpander = new ReferenceExpander(this.props.plugin, this.templateEngine);
	}

	// Обновить метод init():
	init(): void {
		this.container.innerHTML = `
            <div class="llm-agent-chat-panel">
                <div class="chat-history" id="chat-history"></div>
                <div class="chat-input-container">
                    <div class="scroll-container">
                        <div 
                            contenteditable="true" 
                            data-testid="editor-input-main" 
                            class="message-input-editor" 
                            tabindex="0"
                            data-placeholder="Введите сообщение, @файл, #промпт или команду (/)"
                        ></div>
                    </div>
                    <div class="chat-controls">
                        <div class="control-buttons-left">
                            <div class="model-select-container">
                                <button class="model-select-button" id="model-select-button">
                                    <span class="model-name" id="selected-model-name">${this.selectedModel}</span>
                                    <svg class="dropdown-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                                        <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"></path>
                                    </svg>
                                </button>
                                <div class="model-dropdown" id="model-dropdown" style="display: none;"></div>
                            </div>
                        </div>
                        <div class="control-buttons-right">
                            <button class="submit-button" id="send-button" data-tooltip="Enter">
                                <span class="submit-text">⏎ Enter</span>
                            </button>
                        </div>
                    </div>
                    <div class="suggestions" id="suggestions" style="display: none;"></div>
                </div>
            </div>
        `;

		this.messageInput = this.container.querySelector('.message-input-editor') as HTMLDivElement;
		this.sendButton = this.container.querySelector('#send-button') as HTMLButtonElement;
		this.suggestionsContainer = this.container.querySelector('#suggestions') as HTMLDivElement;
		this.modelSelectButton = this.container.querySelector('#model-select-button') as HTMLButtonElement;
		this.modelDropdown = this.container.querySelector('#model-dropdown') as HTMLDivElement;

		this.setupEventListeners();
		this.setupReferenceSystem();
		this.renderHistory();
		this.updateModelDropdown();
		this.updatePlaceholder();
	}

	// ----------------------------------------------- Reference System Setup ---------------------------------------------------------------

	/**
	 * Настраивает систему ссылок
	 */
	private setupReferenceSystem(): void {
		if (!this.autocompleteManager) return;

		// Инициализируем автокомплит
		this.referenceAutocomplete = new ReferenceAutocomplete(this.messageInput, this.autocompleteManager);

		// Настраиваем обработчик выбора элемента
		this.referenceAutocomplete.onItemSelected((item: AutocompleteItem, match: ReferenceMatch) => {
			this.replaceReferenceWithBox(match, item);
		});
	}

	// Обновить метод setupEventListeners():
	setupEventListeners(): void {
		this.sendButton.addEventListener('click', () => this.handleSendWithReferences());

		this.messageInput.addEventListener('keydown', (e) => {
			// Сначала проверяем автокомплит ссылок
			if (this.referenceAutocomplete && this.referenceAutocomplete.handleKeydown(e)) {
				return; // Событие обработано автокомплитом
			}

			if (e.key === 'Enter' && !e.shiftKey) {
				if (this.suggestionsContainer.style.display === 'block') {
					const firstSuggestion = this.suggestionsContainer.querySelector('.suggestion-item');
					if (firstSuggestion) {
						this.messageInput.textContent = firstSuggestion.textContent || '';
						this.hideSuggestions();
						e.preventDefault();
						return;
					}
				}
				this.handleSendWithReferences();
				e.preventDefault();
			}
		});

		this.messageInput.addEventListener('input', () => {
			this.handleInput();
			this.handleReferenceInput();
			this.updatePlaceholder();
		});

		this.messageInput.addEventListener('focus', () => {
			this.updatePlaceholder();
		});

		this.messageInput.addEventListener('blur', () => {
			this.updatePlaceholder();
		});

		// Model selection
		this.modelSelectButton.addEventListener('click', (e) => {
			e.stopPropagation();
			this.toggleModelDropdown();
		});

		// Close dropdown when clicking outside
		document.addEventListener('click', (e) => {
			if (!this.modelSelectButton.contains(e.target as Node) && !this.modelDropdown.contains(e.target as Node)) {
				this.modelDropdown.style.display = 'none';
			}
			if (!this.suggestionsContainer.contains(e.target as Node)) {
				this.hideSuggestions();
			}
		});
	}

	// ----------------------------------------------- Reference Processing ---------------------------------------------------------------

	/**
	 * Обрабатывает ввод для автокомплита ссылок
	 */
	private handleReferenceInput(): void {
		if (!this.referenceAutocomplete || this.isProcessingReference) return;

		const selection = window.getSelection();
		if (!selection || selection.rangeCount === 0) {
			this.referenceAutocomplete.hideAutocomplete();
			return;
		}

		const range = selection.getRangeAt(0);
		const cursorPosition = this.getCursorPositionInText();

		if (cursorPosition === -1) {
			this.referenceAutocomplete.hideAutocomplete();
			return;
		}

		const text = this.getTextContent();
		const currentRef = ReferenceParser.getCurrentReference(text, cursorPosition);

		if (currentRef) {
			// Получаем позицию курсора на экране
			const cursorRect = range.getBoundingClientRect();
			this.referenceAutocomplete.showAutocomplete(currentRef, cursorRect);
		} else {
			this.referenceAutocomplete.hideAutocomplete();
		}
	}

	/**
	 * Заменяет текст ссылки на визуальный бокс
	 */
	private replaceReferenceWithBox(match: ReferenceMatch, item: AutocompleteItem): void {
		this.isProcessingReference = true;

		const reference: FileReference = {
			type: item.type,
			name: item.name,
			path: item.path,
			permanent: match.prefix.length === 2 // @@ или ##
		};

		// Создаем бокс
		const box = ReferenceBox.createBox(reference);

		// Используем текущий Range для замены
		const selection = window.getSelection();
		if (!selection || selection.rangeCount === 0) {
			this.isProcessingReference = false;
			return;
		}

		const currentRange = selection.getRangeAt(0);

		// Создаем новый диапазон для замены, основываясь на match.startIndex и match.endIndex
		// Необходимо получить текстовые узлы, соответствующие этим индексам
		const { startNode, startOffset, endNode, endOffset } = this.findNodesAndOffsetsForMatch(match);

		if (!startNode || !endNode) {
			console.error('Не удалось найти узлы для замены ссылки', match);
			this.isProcessingReference = false;
			return;
		}

		const replacementRange = document.createRange();
		replacementRange.setStart(startNode, startOffset);
		replacementRange.setEnd(endNode, endOffset);

		replacementRange.deleteContents(); // Удаляем текст ссылки
		replacementRange.insertNode(box); // Вставляем бокс

		// Очищаем выделение
		selection.removeAllRanges();

		// Добавляем ссылку в список текущих ссылок
		this.currentReferences.push(reference);

		this.isProcessingReference = false;

		// Вставляем пробел после бокса и перемещаем курсор за него
		this.insertSpaceAndSetCursorAfterElement(box);
	}

	/**
	 * Находит узлы и смещения для заданного match в contenteditable элементе.
	 * @param match - объект ReferenceMatch.
	 * @returns Объект с startNode, startOffset, endNode, endOffset.
	 */
	private findNodesAndOffsetsForMatch(match: ReferenceMatch): {
		startNode: Node | null;
		startOffset: number;
		endNode: Node | null;
		endOffset: number;
	} {
		let startNode: Node | null = null;
		let startOffset = 0;
		let endNode: Node | null = null;
		let endOffset = 0;
		let currentLength = 0;

		const walker = document.createTreeWalker(this.messageInput, NodeFilter.SHOW_ALL); // SHOW_ALL, чтобы учитывать и элементы
		let node;

		while ((node = walker.nextNode())) {
			const nodeTextLength = this.getNodeEquivalentLengthForReferenceParsing(node);

			// Проверяем, начинается ли ссылка в этом узле
			if (currentLength <= match.startIndex && match.startIndex < currentLength + nodeTextLength) {
				startNode = node;
				startOffset = match.startIndex - currentLength;
			}

			// Проверяем, заканчивается ли ссылка в этом узле
			if (currentLength <= match.endIndex && match.endIndex <= currentLength + nodeTextLength) {
				endNode = node;
				endOffset = match.endIndex - currentLength;
				break; // Нашли оба, выходим
			}

			// Если ссылка охватывает несколько узлов, может быть, endNode еще не найден,
			// но мы уже прошли startNode.
			if (startNode && !endNode && match.endIndex <= currentLength + nodeTextLength) {
				// Это случай, когда endMatch.endIndex может быть на границе или внутри следующего узла
				endNode = node;
				endOffset = match.endIndex - currentLength;
				break;
			}

			currentLength += nodeTextLength;
		}

		// Если endNode не найден, возможно, match.endIndex указывает на конец inputElement
		if (!endNode && startNode && currentLength <= match.endIndex) {
			if (this.messageInput.lastChild) {
				endNode = this.messageInput.lastChild;
				endOffset = this.messageInput.lastChild.textContent?.length || 0; // Или конец всего контента
			}
		}

		return { startNode, startOffset, endNode, endOffset };
	}

	/**
	 * Вычисляет эквивалентную текстовую длину узла для целей парсинга ссылок.
	 * Это отличается от getNodeEquivalentLength, так как здесь reference-box уже считается как его текстовое представление,
	 * а не 0, так как мы ищем где находится match.
	 * @param node - узел DOM.
	 * @returns эквивалентная текстовая длина узла.
	 */
	private getNodeEquivalentLengthForReferenceParsing(node: Node): number {
		if (node.nodeType === Node.TEXT_NODE) {
			return node.textContent?.length || 0;
		} else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).classList.contains('reference-box')) {
			const reference = ReferenceBox.extractReference(node as HTMLElement);
			if (reference) {
				// Если это reference-box, его "текстовая длина" для парсинга - это его исходная @/@@#name
				return (reference.permanent ? 2 : 1) + reference.name.length;
			}
		}
		return 0; // Другие элементы не влияют на текстовую длину для этого контекста
	}

	/**
	 * Вставляет пробел после указанного элемента и устанавливает курсор.
	 * @param element - HTML элемент, после которого нужно вставить пробел и установить курсор.
	 */
	private insertSpaceAndSetCursorAfterElement(element: HTMLElement): void {
		const selection = window.getSelection();
		if (!selection) return;

		const range = document.createRange();
		range.setStartAfter(element);
		range.collapse(true); // Схлопываем диапазон до одной точки

		// Вставляем неразрывный пробел
		const spaceNode = document.createTextNode('\u00A0');
		range.insertNode(spaceNode);

		// Перемещаем курсор после вставленного пробела
		range.setStartAfter(spaceNode);
		range.collapse(true);

		selection.removeAllRanges();
		selection.addRange(range);
	}

	/**
	 * Заменяет текст на HTML элемент
	 */
	private replaceTextWithElement(match: ReferenceMatch, element: HTMLElement): void {
		const selection = window.getSelection();
		if (!selection || selection.rangeCount === 0) return;

		// Находим текстовый узел с совпадением
		const range = document.createRange();
		range.selectNodeContents(this.messageInput);
		range.setEnd(selection.focusNode!, selection.focusOffset); // Устанавливаем конец диапазона на текущую позицию курсора

		let textBeforeMatch = '';
		let nodeBeforeMatch: Node | null = null;
		let offsetBeforeMatch = 0;

		// Проходим по дочерним элементам до match.startIndex
		const walker = document.createTreeWalker(this.messageInput, NodeFilter.SHOW_TEXT);
		let node: Node | null;
		let currentLength = 0;

		while ((node = walker.nextNode())) {
			const nodeText = node.textContent || '';
			if (currentLength + nodeText.length >= match.startIndex) {
				nodeBeforeMatch = node;
				offsetBeforeMatch = match.startIndex - currentLength;
				break;
			}
			currentLength += nodeText.length;
		}

		if (!nodeBeforeMatch) {
			console.warn('Не удалось найти текстовый узел для начала замены.');
			return;
		}

		// Удаляем диапазон, соответствующий тексту ссылки
		const deleteRange = document.createRange();
		deleteRange.setStart(nodeBeforeMatch, offsetBeforeMatch);

		// Расширяем диапазон удаления до конца 'fullMatch'
		let remainingLengthToDelete = match.fullMatch.length;
		let currentNode = nodeBeforeMatch;
		let currentOffset = offsetBeforeMatch;

		while (currentNode && remainingLengthToDelete > 0) {
			const nodeText = currentNode.textContent || '';
			const canDelete = Math.min(remainingLengthToDelete, nodeText.length - currentOffset);
			remainingLengthToDelete -= canDelete;
			currentOffset += canDelete;

			if (remainingLengthToDelete > 0) {
				// Если не всё удалено в текущем узле, переходим к следующему
				currentNode = walker.nextNode();
				currentOffset = 0; // Начинаем с начала следующего узла
			}
		}
		deleteRange.setEnd(currentNode || nodeBeforeMatch, currentOffset);
		deleteRange.deleteContents();

		// Вставляем новый элемент на место удаленного текста
		deleteRange.insertNode(element);

		// Очищаем выделение
		selection.removeAllRanges();
	}

	/**
	 * Получает позицию курсора в тексте
	 * (теперь используем findNodesAndOffsetsForMatch для более точного определения текстового смещения)
	 */
	private getCursorPositionInText(): number {
		const selection = window.getSelection();
		if (!selection || selection.rangeCount === 0) return -1;

		const range = selection.getRangeAt(0);
		// Получаем "абсолютный" индекс курсора в контексте всего текстового содержимого `messageInput`
		// с учетом того, что reference-box'ы представлены их текстовыми аналогами в getTextContent()
		let currentTextOffset = 0;
		const walker = document.createTreeWalker(this.messageInput, NodeFilter.SHOW_ALL);
		let node;

		while ((node = walker.nextNode())) {
			if (node === range.endContainer) {
				if (node.nodeType === Node.TEXT_NODE) {
					currentTextOffset += range.endOffset;
				} else if (node === this.messageInput) {
					// Cлучай, когда курсор в корневом элементе, но не в текстовом узле.
					// Может быть, курсор находится между элементами или в конце.
					// Нам нужно определить, сколько узлов до курсора.
					let nodeBeforeCursorElementCount = 0;
					for (let i = 0; i < range.endOffset; i++) {
						const childNode = this.messageInput.childNodes[i];
						currentTextOffset += this.getNodeEquivalentLengthForReferenceParsing(childNode);
					}
				} else {
					// Если endContainer - это другой элемент (например, <br>),
					// то курсор находится до него.
					// Мы не должны добавлять его длину, но должны учесть, сколько текста было до него.
					// В данном случае `range.endOffset` будет 0, если курсор перед ним.
					// Если курсор внутри элемента, нужно по-другому считать.
					// В контексте `contenteditable` это обычно означает, что мы либо в текстовом узле,
					// либо `endContainer` является родителем, а `endOffset` - индексом дочернего элемента.
					// Для `reference-box` курсор внутри быть не должен (т.к. он `<span contenteditable="false">`).
					// Для других элементов, если курсор внутри них, `range.endOffset` будет смещением.
				}
				break;
			} else {
				currentTextOffset += this.getNodeEquivalentLengthForReferenceParsing(node);
			}
		}

		return currentTextOffset;
	}

	/**
	 * Получает текстовое содержимое input элемента (без HTML),
	 * заменяя reference-box их текстовым представлением.
	 */
	private getTextContent(): string {
		let result = '';
		const walker = document.createTreeWalker(this.messageInput, NodeFilter.SHOW_ALL); // SHOW_ALL для корректного обхода
		let node;

		while ((node = walker.nextNode())) {
			if (node.nodeType === Node.TEXT_NODE) {
				result += node.textContent || '';
			} else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).classList.contains('reference-box')) {
				const reference = ReferenceBox.extractReference(node as HTMLElement);
				if (reference) {
					const prefix = reference.permanent ? (reference.type === 'file' ? '@@' : '##') : reference.type === 'file' ? '@' : '#';
					result += prefix + reference.name;
				}
			} else if (node.nodeType === Node.ELEMENT_NODE) {
				// Для других элементов, которые могут быть в contenteditable (например, <br>),
				// нужно решить, как их представлять в текстовом содержимом.
				// В данном случае, если это <br>, его можно игнорировать или заменить на \n.
				// Для простоты, пока игнорируем, если это не бокс.
				// Если внутри inputElement есть другие элементы, они также будут частью getTextContent,
				// поэтому walker.nextNode() будет их учитывать.
				// Возможно, здесь нужно быть более точным, основываясь на ожидаемой структуре.
			}
		}
		return result;
	}

	/**
	 * Извлекает текст с заменой боксов на исходные ссылки
	 */
	private extractTextWithReferences(): string {
		let result = '';

		const childNodes = Array.from(this.messageInput.childNodes);

		for (const childNode of childNodes) {
			if (childNode.nodeType === Node.TEXT_NODE) {
				result += childNode.textContent;
			} else if (childNode.nodeType === Node.ELEMENT_NODE) {
				const element = childNode as HTMLElement;

				if (element.classList.contains('reference-box')) {
					// Это бокс ссылки - заменяем на исходную ссылку
					const reference = ReferenceBox.extractReference(element);
					if (reference) {
						const prefix = reference.permanent
							? reference.type === 'file'
								? '@@'
								: '##'
							: reference.type === 'file'
							? '@'
							: '#';
						result += `${prefix}${reference.name}`;
					}
				} else {
					// Обычный элемент - берем текстовое содержимое
					result += element.textContent;
				}
			}
		}

		return result;
	}

	/**
	 * Обработка отправки сообщения с раскрытием ссылок
	 */
	private async handleSendWithReferences(): Promise<void> {
		const rawText = this.extractTextWithReferences();
		const message = rawText.trim();

		if (!message) return;

		if (!this.referenceExpander) {
			// Если система ссылок не инициализирована, отправляем как обычно
			this.handleSend();
			return;
		}

		try {
			// Раскрываем ссылки перед отправкой
			const expandedText = await this.referenceExpander.expandReferences(rawText, this.currentReferences);

			// Отправляем expandedText вместо rawText
			if (this.props.onSendMessage) {
				this.props.onSendMessage(expandedText, this.selectedModel);
			}

			// Очищаем состояние
			this.clearInput();
		} catch (error) {
			console.error('Ошибка при раскрытии ссылок:', error);
			// В случае ошибки отправляем исходный текст
			if (this.props.onSendMessage) {
				this.props.onSendMessage(rawText, this.selectedModel);
			}
			this.clearInput();
		}
	}

	/**
	 * Очищает поле ввода и сбрасывает состояние ссылок
	 */
	private clearInput(): void {
		this.messageInput.innerHTML = '';
		this.currentReferences = [];
		this.updatePlaceholder();
		this.hideSuggestions();
		if (this.referenceAutocomplete) {
			this.referenceAutocomplete.hideAutocomplete();
		}
	}

	// Обновить метод handleSend() для совместимости:
	handleSend(): void {
		const message = this.messageInput.textContent?.trim() || '';
		if (message && this.props.onSendMessage) {
			this.props.onSendMessage(message, this.selectedModel);
			this.clearInput();
		}
	}

    /**
     * Создает панель инструментов под сообщением с кнопками для взаимодействия.
     * @param messageElement - Элемент сообщения, к которому добавляется панель инструментов.
     * @param messageContent - Содержимое сообщения для функции копирования.
     */
    private createMessageToolset(messageElement: HTMLElement, messageContent: string): void {
        const toolsetWrapper = messageElement.createDiv({
            cls: 'z-0 flex min-h-[46px] justify-start'
        });
        const toolsetContainer = toolsetWrapper.createDiv({
            cls: 'touch:-me-2 touch:-ms-3.5 -ms-2.5 -me-1 flex flex-wrap items-center gap-y-4 p-1 select-none touch:w-[calc(100%+--spacing(3.5))] -mt-1 w-[calc(100%+--spacing(2.5))] duration-[1.5s] focus-within:transition-none hover:transition-none pointer-events-none [mask-image:linear-gradient(to_right,black_33%,transparent_66%)] [mask-size:300%_100%] [mask-position:100%_0%] motion-safe:transition-[mask-position] group-hover/turn-messages:pointer-events-auto group-hover/turn-messages:[mask-position:0_0] group-focus-within/turn-messages:pointer-events-auto group-focus-within/turn-messages:[mask-position:0_0] has-data-[state=open]:pointer-events-auto has-data-[state=open]:[mask-position:0_0]',
        });

        // Кнопка "Копировать"
        const copyButton = toolsetContainer.createEl('button', {
            cls: 'text-token-text-secondary hover:bg-token-bg-secondary rounded-lg',
            attr: {
                'aria-label': 'Копировать',
                'aria-pressed': 'false',
                'data-testid': 'copy-turn-action-button',
                'data-state': 'closed',
            },
        });
        const copyIconContainer = copyButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' });
        setIcon(copyIconContainer, 'copy'); // Используем импортированную функцию setIcon
        copyButton.addEventListener('click', () => {
            this.copyMessageContent(messageContent);
        });

        // Кнопка "Попробовать снова"
        /*const retryButton = toolsetContainer.createEl('button', {
            cls: 'text-token-text-secondary hover:bg-token-bg-secondary rounded-lg',
            attr: {
                'aria-label': 'Попробовать снова',
                'aria-pressed': 'false',
                'data-testid': 'refresh-turn-action-button',
                'data-state': 'closed',
            },
        });
        const retryIconContainer = retryButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' });
        setIcon(retryIconContainer, 'refresh-cw'); // Используем импортированную функцию setIcon
        retryButton.addEventListener('click', () => {
            this.copyMessageContent(messageContent);
        });*/ // TODO: реализовать переотправку сообщения
    }

    private copyMessageContent(content: string): void {
        navigator.clipboard.writeText(content)
            .then(() => {
                console.log('Message content copied to clipboard.');
                // Optionally: show a notice to the user
                // new Notice('Сообщение скопировано!');
            })
            .catch(err => {
                console.error('Failed to copy message content:', err);
                // Optionally: show an error notice
                // new Notice('Не удалось скопировать сообщение.');
            });
    }
}
