/**
 * Отображает и управляет интерактивным графом LLM агента.
 * Интегрирует историю чатов и визуализацию графа на основе React Flow.
 */

// ----------------------------------------------- Obsidian Imports ----------------------------------------------------------
import { ItemView, WorkspaceLeaf, Notice } from 'obsidian';
// ----------------------------------------------- Plugin Imports ------------------------------------------------------------
import LLMAgentPlugin from 'main'; // Предполагаем, что main.ts экспортирует LLMAgentPlugin по умолчанию
import { HistoryPanel } from '../components/HistoryPanel'; // Предполагаем, что HistoryPanel это JS-класс
import { GraphPanelComponent } from '../components/GraphPanel'; // Импортируем React-компонент и ReactDOM
import * as React from 'react';
import * as ReactDOM from 'react-dom/client'; // <-- ИСПРАВЛЕНО: Импортируем 'react-dom/client' для createRoot
import { Dialog } from 'src/utils/Dialog';

export const GRAPH_VIEW_TYPE = 'llm-agent-graph-view';

// ----------------------------------------------- Graph View ---------------------------------------------------------------

export class GraphView extends ItemView {
	plugin: LLMAgentPlugin;
	graphData: { nodes: any[]; edges: any[] };
	currentNode: string | null;
	graphsList: any[];
	selectedGraphId: string | null;
	selectedNodeIds: Set<string>; // Добавляем Set для хранения ID выделенных узлов
	selectedGraphTitle: string | null;

	// UI Elements
	historyPanel: HistoryPanel | null;
	historyContainer: HTMLDivElement;
	graphContainer: HTMLDivElement;
	reactRoot: ReactDOM.Root | null;
	toggleButton: HTMLButtonElement;
	isHistoryCollapsed: boolean;

	// Глобальные кнопки действий
	copySelectedButton: HTMLButtonElement | null = null;
	deleteSelectedButton: HTMLButtonElement | null = null;

	constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
		super(leaf);
		this.plugin = plugin;
		this.graphData = { nodes: [], edges: [] };
		this.currentNode = null;
		this.graphsList = [];
		this.selectedGraphId = null;
		this.historyPanel = null;
		this.reactRoot = null;
		this.isHistoryCollapsed = true;
		this.selectedNodeIds = new Set<string>(); // Инициализируем Set
		this.selectedGraphTitle = null;
	}

	/**
	 * @returns {string} Тип представления.
	 */
	getViewType(): string {
		return GRAPH_VIEW_TYPE;
	}

	/**
	 * @returns {string} Отображаемое имя представления.
	 */
	getDisplayText(): string {
		return this.selectedGraphTitle || 'LLM Agent Graph';
	}

	/**
	 * @returns {string} Иконка представления.
	 */
	getIcon(): string {
		return 'brain-circuit';
	}

	/**
	 * Вызывается при открытии представления. Инициализирует UI и загружает данные.
	 * @returns {Promise<void>}
	 */
	async onOpen(): Promise<void> {
		const container = this.containerEl.children[1] as HTMLElement;
		container.empty();

		// Создаем основной контейнер с flexbox layout
		const mainContainer = container.createDiv('llm-agent-graph-main');
		mainContainer.style.cssText = `
            display: flex;
            height: 100%;
            width: 100%;
            background-color: var(--background-primary);
        `;

		// Левая панель для истории
		this.historyContainer = mainContainer.createDiv('llm-agent-history-sidebar') as HTMLDivElement;
		this.historyContainer.classList.add(this.isHistoryCollapsed ? 'collapsed' : 'expanded');

		// Правая панель для графа
		this.graphContainer = mainContainer.createDiv('llm-agent-graph-container') as HTMLDivElement;
		this.graphContainer.style.cssText = `
            flex: 1;
            position: relative;
            background-color: var(--background-primary);
        `;

		// Инициализируем панель истории
		this.historyPanel = new HistoryPanel(
			this.historyContainer,
			{
				graphs: this.graphsList,
				eventBus: this.plugin.eventBus
			},
			this.plugin
		);

		// Создаем кнопки переключения и глобальные кнопки действий
		this.addToggleButton();
		this.addGlobalActionButtons(); // Добавляем вызов новой функции

		// Инициализируем React Flow
		this.initializeGraphPanel();

		// Подписываемся на события
		this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
		this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
		this.plugin.eventBus.on('delete-node', this.handleDeleteNodeFromChat.bind(this)); // Слушаем событие из ChatPanel

		// WebSocket Event Handlers
		this.plugin.eventBus.on('ws-graph-title-updated', this.handleGraphTitleUpdated.bind(this));
		this.plugin.eventBus.on('ws-node-title-updated', this.handleNodeTitleUpdated.bind(this));
		this.plugin.eventBus.on('ws-node-type-changed', this.handleNodeTypeChanged.bind(this));

		// Загружаем список графов
		await this.fetchGraphsList();
	}

	/**
	 * Вызывается при закрытии представления. Очищает ресурсы.
	 * @returns {Promise<void>}
	 */
	async onClose(): Promise<void> {
		if (this.historyPanel) {
			this.historyPanel.destroy();
			this.historyPanel = null;
		}

		// Отмонтируем React-компонент
		if (this.reactRoot) {
			this.reactRoot.unmount();
			this.reactRoot = null;
		}

		this.plugin.eventBus.off('new-chat', this.handleNewChat);
		this.plugin.eventBus.off('graph-selected', this.handleGraphSelected);
		this.plugin.eventBus.off('delete-node', this.handleDeleteNodeFromChat);

		// WebSocket Event Cleanup
		this.plugin.eventBus.off('ws-graph-title-updated', this.handleGraphTitleUpdated);
		this.plugin.eventBus.off('ws-node-title-updated', this.handleNodeTitleUpdated);
		this.plugin.eventBus.off('ws-node-type-changed', this.handleNodeTypeChanged);
	}

	// ----------------------------------------------- Custom Title Update ---------------------------------------------------

	/**
	 * Обновляет текстовое содержимое заголовка представления.
	 * Этот метод должен быть вызван для динамического изменения заголовка
	 * после первоначальной отрисовки представления.
	 */
	updateTitle(): void {
		const titleElement = this.containerEl.querySelector('.view-header-title');
		if (titleElement) {
			titleElement.textContent = this.selectedGraphTitle || 'LLM Agent Graph';
		}
	}

	// ----------------------------------------------- History Panel Toggle --------------------------------------------------

	/**
	 * Переключает состояние панели истории между свернутым и развернутым.
	 */
	toggleHistoryPanel(): void {
		this.isHistoryCollapsed = !this.isHistoryCollapsed;

		if (this.isHistoryCollapsed) {
			this.historyContainer.classList.remove('expanded');
			this.historyContainer.classList.add('collapsed');
			this.toggleButton.textContent = '📋';
		} else {
			this.historyContainer.classList.remove('collapsed');
			this.historyContainer.classList.add('expanded');
			this.toggleButton.textContent = '❌';
		}
	}

	// ----------------------------------------------- Graph Panel Integration --------------------------------------------------

	addToggleButton(): void {
		const buttonWrapper = this.graphContainer.createDiv({ cls: 'graph-view-toolbar' }); // Создаем контейнер для кнопок
		this.toggleButton = buttonWrapper.createEl('button', { cls: 'global-action-button' }); // history-toggle-button
		this.toggleButton.textContent = this.isHistoryCollapsed ? '📋' : '❌';
		this.toggleButton.addEventListener('click', this.toggleHistoryPanel.bind(this));
		console.log('Toggle button created and appended:', this.toggleButton);
	}

	/**
	 * Добавляет глобальные кнопки действий для выделенных узлов.
	 */
	addGlobalActionButtons(): void {
		const toolbar = this.graphContainer.querySelector('.graph-view-toolbar');
		if (!toolbar) return;

		// Кнопка "Копировать выделенные"
		this.copySelectedButton = toolbar.createEl('button', {
			cls: 'global-action-button',
			attr: { 'aria-label': 'Копировать выделенные узлы', 'data-tooltip': 'Копировать выделенные узлы' },
			text: '📄'
		});
		this.copySelectedButton.addEventListener('click', this.handleCopySelectedNodes.bind(this));

		// Кнопка "Удалить выделенные"
		this.deleteSelectedButton = toolbar.createEl('button', {
			cls: 'global-action-button',
			attr: { 'aria-label': 'Удалить выделенные узлы', 'data-tooltip': 'Удалить выделенные узлы' },
			text: '🗑️'
		});
		this.deleteSelectedButton.addEventListener('click', this.handleDeleteSelectedNodes.bind(this));
	}

	// ----------------------------------------------- Graph Panel Integration --------------------------------------------------

	/**
	 * Инициализирует GraphPanelComponent (React компонент) в graphContainer.
	 * Использует ReactDOM.createRoot для React 18.
	 */
	initializeGraphPanel(): void {
		this.graphContainer.empty();
		// Перемещаем кнопки обратно после очистки, если они были частью graphContainer
		this.addToggleButton();
		this.addGlobalActionButtons();

		const wrapper = this.graphContainer.createDiv();
		wrapper.style.cssText = 'width: 100%; height: 100%;';

		if (!wrapper) {
			console.error('Не удалось создать wrapper для монтирования React Flow.');
			return;
		}

		if (ReactDOM && typeof ReactDOM.createRoot === 'function') {
			this.reactRoot = ReactDOM.createRoot(wrapper);
			this.renderGraphPanel();
		} else {
			console.error('ReactDOM.createRoot не определен. Убедитесь, что "react-dom/client" правильно импортирован.');
		}
	}

	/**
	 * Рендерит или обновляет GraphPanelComponent в DOM.
	 */
	renderGraphPanel(): void {
		if (this.reactRoot) {
			this.reactRoot.render(
				React.createElement(GraphPanelComponent, {
					graphData: {
						nodes: this.graphData.nodes,
						edges: this.graphData.edges
					},
					onNodeClick: this.handleNodeClick.bind(this),
					onNodeSelectionChange: this.handleNodeSelectionChange.bind(this), // Передаем обработчик изменения выделения
					onNodeDelete: this.handleDeleteNode.bind(this), // Передаем колбэк для удаления одиночного узла
					selectedNodeIds: this.selectedNodeIds, // Передаем Set выделенных узлов
					app: this.plugin.app
				})
			);
		} else {
			console.warn('React root не инициализирован для рендера GraphPanelComponent.');
		}
	}

	// ----------------------------------------------- API Methods ---------------------------------------------------------------

	/**
	 * Загружает список всех графов с бэкенда.
	 * @returns {Promise<void>}
	 */
	async fetchGraphsList(): Promise<void> {
		try {
			const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs`);
			if (!response.ok) {
				const errorText = await response.text();
				throw new Error(`Ошибка загрузки списка графов: ${response.status} - ${errorText}`);
			}
			const data = await response.json();
			this.graphsList = data;
			if (this.historyPanel) {
				this.historyPanel.updateGraphs(this.graphsList);
			}
		} catch (error) {
			console.error('Ошибка загрузки списка графов:', error);
			// Optionally: show a notice to the user
			// new Notice(`Ошибка загрузки списка графов: ${error.message}`);
		}
	}

	/**
	 * Загружает данные конкретного графа по его ID.
	 * @param {string} graphId - ID графа для загрузки.
	 * @returns {Promise<void>}
	 */
	async fetchGraphData(graphId: string): Promise<void> {
		try {
			if (!graphId) {
				this.graphData = { nodes: [], edges: [] };
				this.currentNode = null;
				this.selectedNodeIds.clear(); // Очищаем выделения
				this.selectedGraphTitle = null; // Сброс заголовка
				this.updateTitle(); // Обновляем заголовок
				this.renderGraphPanel();
				this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null });
				return;
			}

			const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${graphId}`);
			if (!response.ok) {
				const errorText = await response.text();
				throw new Error(`Ошибка загрузки данных графа: ${response.status} - ${errorText}`);
			}
			const data = await response.json();

			// Чтобы не раздувать отправляемые эндпоинтом данные
			const nodesWithGraphId = data.graph_nodes ? data.graph_nodes.map((node: any) => ({ ...node, graph_id: graphId })) : [];

			this.graphData = {
				nodes: nodesWithGraphId,
				edges: data.graph_edges || []
			};
			this.currentNode = data.current_node_id;

			// Устанавливаем заголовок графа
			this.selectedGraphTitle = data.title || data.first_message || `Граф ${graphId.substring(0, 8)}...`;
			this.updateTitle(); // Обновляем заголовок
			// Перерендериваем граф
			this.renderGraphPanel();

			// Также обновляем ChatView с полученными сообщениями и текущим узлом
			this.plugin.eventBus.emit('chat-history-updated', {
				graphId: graphId,
				messages: data.messages || [],
				current_node_id: data.current_node_id // Передаем актуальный current_node_id
			});
		} catch (error) {
			console.error('Ошибка загрузки данных графа:', error);
			this.selectedGraphTitle = null;
			this.updateTitle(); // Обновляем заголовок
			// Optionally: show a notice to the user
			// new Notice(`Ошибка загрузки данных графа: ${error.message}`);
		}
	}

	/**
	 * Удаляет узел графа по его ID.
	 * @param {string} graphId - ID графа.
	 * @param {string} nodeId - ID узла для удаления.
	 * @returns {Promise<void>}
	 */
	async handleDeleteNode(nodeId: string): Promise<void> {
		if (!this.selectedGraphId) {
			new Notice('Не выбран граф для удаления узла.', 3000);
			return;
		}

		try {
			const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${this.selectedGraphId}/nodes/${nodeId}`, {
				method: 'DELETE'
			});

			if (!response.ok) {
				const errorText = await response.text();
				throw new Error(`Ошибка удаления узла: ${response.status} - ${errorText}`);
			}

			new Notice('Узел успешно удален.', 2000);
			// После удаления узла, нужно обновить данные графа и перерисовать его.
			// Это также обновит ChatPanel, если удаленное сообщение было частью текущей ветки.
			await this.fetchGraphData(this.selectedGraphId);

			// Сбрасываем выделение, если удаленный узел был выделен
			this.selectedNodeIds.delete(nodeId);
		} catch (error) {
			console.error(`Ошибка при удалении узла ${nodeId}:`, error);
			new Notice(`Ошибка при удалении узла: ${error.message}`, 5000);
		}
	}

	// ----------------------------------------------- Event Handlers ------------------------------------------------------------

	/**
	 * Обработчик клика по узлу графа.
	 * Устанавливает текущий узел и генерирует событие 'node-selected'.
	 * @param {string} nodeId - ID выбранного узла.
	 */
	handleNodeClick(nodeId: string): void {
		this.currentNode = nodeId;
		// Очищаем предыдущие выделения при обычном клике
		if (this.selectedNodeIds.size > 0) {
			this.selectedNodeIds.clear();
		}
		this.plugin.eventBus.emit('node-selected', { nodeId, graphId: this.selectedGraphId });
	}

	/**
	 * Обработчик изменения состояния выделения узла.
	 * @param {string} nodeId - ID узла.
	 * @param {boolean} isSelected - Новое состояние выделения.
	 */
	handleNodeSelectionChange(nodeId: string, isSelected: boolean): void {
		if (isSelected) {
			this.selectedNodeIds.add(nodeId);
		} else {
			this.selectedNodeIds.delete(nodeId);
		}
		this.renderGraphPanel(); // Перерендериваем для обновления визуального состояния выделения
	}

	/**
	 * Обработчик начала нового чата.
	 * Очищает текущие данные графа и перерендеривает его.
	 */
	handleNewChat(): void {
		this.graphData = { nodes: [], edges: [] };
		this.currentNode = null;
		this.selectedGraphId = null;
		this.selectedNodeIds.clear(); // Очищаем выделения
		this.selectedGraphTitle = null; // Сброс заголовка при новом чате
		this.updateTitle(); // Обновляем заголовок
		this.renderGraphPanel();
		this.fetchGraphsList(); // Обновить список графов, чтобы показать новый пустой чат
		this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null });
	}

	/**
	 * Обработчик выбора графа из панели истории.
	 * Устанавливает выбранный граф и загружает его данные.
	 * @param {{ graphId: string; currentNode: string | null }} data - Объект, содержащий ID выбранного графа и текущий узел.
	 * @returns {Promise<void>}
	 */
	async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
		this.selectedGraphId = data.graphId;
		this.selectedNodeIds.clear(); // Очищаем выделения при смене графа
		await this.fetchGraphData(data.graphId);
	}

	/**
	 * Обработчик события удаления узла из ChatPanel.
	 * @param {{ graphId: string, nodeId: string }} data - Данные об удаляемом узле.
	 */
	async handleDeleteNodeFromChat(data: { graphId: string; nodeId: string }): Promise<void> {
		if (data.graphId === this.selectedGraphId) {
			await this.handleDeleteNode(data.nodeId);
		} else {
			new Notice('Сообщение относится к другому графу. Удаление невозможно из текущего контекста.', 3000);
		}
	}

	/**
	 * Обработчик для кнопки "Копировать выделенные узлы".
	 */
	async handleCopySelectedNodes(): Promise<void> {
		if (this.selectedNodeIds.size === 0) {
			new Notice('Выберите узлы для копирования.', 2000);
			return;
		}

		const messagesToCopy: string[] = [];
		// Собираем сообщения в порядке выделения
		// Необходимо пройтись по nodes в graphData, чтобы получить актуальные данные сообщений
		const orderedNodeIds = Array.from(this.selectedNodeIds); // Сохраняем порядок
		for (const nodeId of orderedNodeIds) {
			const node = this.graphData.nodes.find((n) => n.id === nodeId);
			if (node && node.data && node.data.message && node.data.message.content) {
				messagesToCopy.push(node.data.message.content);
			}
		}

		if (messagesToCopy.length > 0) {
			try {
				await navigator.clipboard.writeText(messagesToCopy.join('\n\n--- Разделитель --- \n\n'));
				new Notice(`Скопировано ${messagesToCopy.length} сообщений.`, 2000);
			} catch (err) {
				console.error('Не удалось скопировать сообщения:', err);
				new Notice('Ошибка при копировании сообщений.', 3000);
			}
		} else {
			new Notice('Нет содержимого для копирования в выделенных узлах.', 2000);
		}
	}

	/**
	 * Обработчик для кнопки "Удалить выделенные узлы".
	 */
	async handleDeleteSelectedNodes(): Promise<void> {
		if (this.selectedNodeIds.size === 0) {
			new Notice('Выберите узлы для удаления.', 2000);
			return;
		}

		if (!this.selectedGraphId) {
			new Notice('Не выбран граф для удаления узлов.', 3000);
			return;
		}

		const confirmed = await Dialog.confirm(
			this.plugin.app,
			`Вы уверены, что хотите удалить ${this.selectedNodeIds.size} выделенных узлов?`
		);
		if (!confirmed) return;

		new Notice(`Начинаю удаление ${this.selectedNodeIds.size} узлов...`, 2000);
		const nodesToDelete = Array.from(this.selectedNodeIds); // Сохраняем порядок выделения

		for (const nodeId of nodesToDelete) {
			try {
				const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${this.selectedGraphId}/nodes/${nodeId}`, {
					method: 'DELETE'
				});

				if (!response.ok) {
					const errorText = await response.text();
					throw new Error(`Ошибка удаления узла ${nodeId}: ${response.status} - ${errorText}`);
				}
				new Notice(`Узел ${nodeId.substring(0, 8)}... успешно удален.`, 1000);
			} catch (error) {
				console.error(`Ошибка при удалении узла ${nodeId}:`, error);
				new Notice(`Ошибка при удалении узла ${nodeId.substring(0, 8)}...: ${error.message}`, 5000);
				// Продолжаем попытки удаления остальных узлов, но фиксируем ошибку
			}
		}

		// После всех удалений очищаем выделение и обновляем граф полностью
		this.selectedNodeIds.clear();
		await this.fetchGraphData(this.selectedGraphId); // Полностью обновляем граф и чат-панель
		new Notice('Операция удаления завершена.', 2000);
	}

	// ----------------------------------------------- WebSocket Event Handlers ---------------------------------------------------------------

	/**
	 * Обработчик изменения типа узла через WebSocket
	 */
	handleNodeTypeChanged(data: { graphId: string; nodeId: string; nodeType: string }): void {
		// Обновляем только если это узел текущего графа
		if (this.selectedGraphId !== data.graphId) {
			return;
		}

		// Находим и обновляем тип узла в graphData
		const nodeIndex = this.graphData.nodes.findIndex((n) => n.id === data.nodeId);
		if (nodeIndex !== -1) {
			this.graphData.nodes[nodeIndex].type = data.nodeType;

			// Перерисовываем граф с обновленным типом узла
			this.renderGraphPanel();

			console.log(`✅ Обновлен тип узла ${data.nodeId}: "${data.nodeType}"`);
		}
	}

	/**
	 * Обработчик обновления заголовка графа через WebSocket
	 */
	handleGraphTitleUpdated(data: { graph_id: string; title: string }): void {
		// Обновляем только если это текущий граф
		if (this.selectedGraphId === data.graph_id) {
			this.selectedGraphTitle = data.title;
			this.updateTitle();

			// Также обновляем в списке истории
			if (this.historyPanel) {
				this.fetchGraphsList(); // Перезагружаем список графов
			}
		}
	}

	/**
	 * Обработчик обновления заголовка узла через WebSocket
	 */
	handleNodeTitleUpdated(data: { graph_id: string; node_id: string; title: string }): void {
		// Обновляем только если это узел текущего графа
		if (this.selectedGraphId !== data.graph_id) {
			return;
		}

		// Находим и обновляем узел в graphData
		const nodeIndex = this.graphData.nodes.findIndex((n) => n.id === data.node_id);
		if (nodeIndex !== -1) {
			// Обновляем заголовок и флаг генерации
			this.graphData.nodes[nodeIndex].data.title = data.title;
			this.graphData.nodes[nodeIndex].data.title_generated = true;

			// Перерисовываем граф с обновленными данными
			// React Flow автоматически обновит только измененный узел
			this.renderGraphPanel();

			console.log(`✅ Обновлен заголовок узла ${data.node_id}: "${data.title}"`);
		}
	}
}
