/*
 * ChatView управляет пользовательским интерфейсом чата, отображает историю сообщений,
 * отправляет запросы LLM-агенту и обрабатывает ответы, обновляя состояние чата и графа.
 */

import { ItemView, Notice, WorkspaceLeaf } from 'obsidian';
import { ChatPanel } from '../components/ChatPanel';
import LLMAgentPlugin from 'main';

export const CHAT_VIEW_TYPE = 'llm-agent-chat-view';

// ----------------------------------------------- Chat View ---------------------------------------------------------------

export class ChatView extends ItemView {
	plugin: LLMAgentPlugin;
	chatHistory: any[];
	selectedGraphId: string | null;
	currentNode: string | null;
	chatContainer: HTMLDivElement;
	chatPanel: ChatPanel | null;
	availableModels: string[];

	constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
		super(leaf);
		this.plugin = plugin;
		this.chatHistory = [];
		this.selectedGraphId = null;
		this.currentNode = null;
		this.chatPanel = null;
		this.availableModels = [];
	}

	getViewType(): string {
		return CHAT_VIEW_TYPE;
	}

	getDisplayText(): string {
		return 'LLM Agent Chat';
	}

	getIcon(): string {
		return 'message-circle';
	}

	async onOpen(): Promise<void> {
		const container = this.containerEl.children[1] as HTMLElement;
		container.empty();

		// Создаем только контейнер для чата (без истории)
		this.chatContainer = container.createDiv() as HTMLDivElement;
		this.chatContainer.addClass('llm-agent-chat-container');

		// Загружаем доступные модели
		await this.fetchAvailableModels();

		// Инициализируем только чат панель с передачей plugin
		this.chatPanel = new ChatPanel(this.chatContainer, {
			chatHistory: this.chatHistory,
			onSendMessage: this.handleSendMessage.bind(this),
			availableModels: this.availableModels,
			selectedModel: this.plugin.settings.defaultModel || 'gemini-2.5-flash',
			plugin: this.plugin // Добавляем передачу plugin
		});

		// Остальной код без изменений...
		this.plugin.eventBus.on('node-selected', this.handleNodeSelected.bind(this));
		this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
		this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
		this.plugin.eventBus.on('regenerate-message', this.handleRegenerateMessage.bind(this)); // Добавлено
		this.plugin.eventBus.on('ws-node-type-changed', this.handleNodeTypeChanged.bind(this));
	}

	async onClose(): Promise<void> {
		if (this.chatPanel) {
			this.chatPanel.destroy();
			this.chatPanel = null;
		}
		this.plugin.eventBus.off('node-selected', this.handleNodeSelected);
		this.plugin.eventBus.off('new-chat', this.handleNewChat);
		this.plugin.eventBus.off('graph-selected', this.handleGraphSelected);
		this.plugin.eventBus.off('regenerate-message', this.handleRegenerateMessage);
		this.plugin.eventBus.off('ws-node-type-changed', this.handleNodeTypeChanged);
	}

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

	// TODO: проблемное место
	/**
	 * Загружает историю сообщений от корня до указанного узла графа.
	 * Обновляет `chatHistory` и отображение в `chatPanel`.
	 * @param {string} nodeId - ID конечного узла для загрузки истории.
	 * @returns {Promise<void>}
	 */
	async fetchMessagesFromRootToNode(nodeId: string | null): Promise<void> {
		try {
			// Убедимся, что selectedGraphId установлен
			if (!this.selectedGraphId) {
				console.warn('selectedGraphId не установлен. Невозможно загрузить сообщения.');
				return;
			}

			const url = nodeId
				? `${this.plugin.settings.apiBaseUrl}/messages/${this.selectedGraphId}/${nodeId}`
				: `${this.plugin.settings.apiBaseUrl}/messages/${this.selectedGraphId}/last`;
			const response = await fetch(url);

			if (!response.ok) {
				const errorText = await response.text();
				throw new Error(`Ошибка загрузки сообщений: ${response.status} - ${errorText}`);
			}
			const data = await response.json();
			// ИСПРАВЛЕНО: Убедиться, что type передается, если это возможно
			this.chatHistory =
				data.map((msg: any) => ({
					...msg,
					type: msg.type || msg.role // Используем type, если есть, иначе role как fallback
				})) || [];
			if (this.chatPanel) {
				await this.chatPanel.updateHistory(this.chatHistory);
			}
		} catch (error) {
			console.error('Ошибка загрузки сообщений:', error);
			// Optionally: show a notice to the user
			// new Notice(`Ошибка загрузки сообщений: ${error.message}`);
		}
	}

	/**
	 * Загружает список доступных моделей с бэкенда.
	 * @returns {Promise<void>}
	 */
	async fetchAvailableModels(): Promise<void> {
		try {
			const response = await fetch(`${this.plugin.settings.apiBaseUrl}/models`);
			if (!response.ok) {
				throw new Error(`Ошибка загрузки моделей: ${response.status}`);
			}
			this.availableModels = await response.json();
		} catch (error) {
			console.error('Ошибка загрузки доступных моделей:', error);
			// Fallback к базовым моделям
			this.availableModels = ['gemini-2.5-flash', 'gpt-4o-mini', 'claude-3-5-sonnet-20241022'];
		}
	}

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

	async handleSendMessage(message: string, selectedModel?: string): Promise<void> {
		try {
			// Шаг 1: Создаём узел пользователя
			const sendResponse = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/send`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					message: message,
					graph_id: this.selectedGraphId,
					parent_node_id: this.currentNode
				})
			});

			if (!sendResponse.ok) {
				throw new Error(`Ошибка при отправке сообщения: ${sendResponse.status}`);
			}

			const sendResult = await sendResponse.json();
			this.selectedGraphId = sendResult.graph_id;
			const userNodeId = sendResult.node_id;

			// Обновляем граф с новым узлом пользователя
			this.plugin.eventBus.emit('graph-selected', {
				graphId: this.selectedGraphId,
				currentNode: userNodeId
			});

			// Шаг 2: Запускаем стриминг ответа
			if (this.selectedGraphId) {
				// Проверка на null
				// Передаем null, так как это не регенерация, а новое сообщение
				await this.streamLLMResponse(this.selectedGraphId, userNodeId, selectedModel, null);
			} else {
				throw new Error('Graph ID не определён после отправки сообщения');
			}
		} catch (error: any) {
			// Указываем тип any для error
			console.error('Ошибка отправки сообщения:', error);
			new Notice(`Ошибка: ${error?.message || error}`, 5000);
		}
	}

	/**
	 * Обрабатывает выбор узла на графе. Загружает историю сообщений до выбранного узла
	 * и обновляет панель чата.
	 * @param {{ nodeId: string }} data - Объект, содержащий ID выбранного узла.
	 */
	handleNodeSelected(data: { nodeId: string }): void {
		this.currentNode = data.nodeId;
		if (this.selectedGraphId) {
			this.fetchMessagesFromRootToNode(data.nodeId);
		}
	}

	/**
	 * Обрабатывает событие начала нового чата. Сбрасывает текущее состояние чата
	 * и очищает отображение истории.
	 */
	async handleNewChat(): Promise<void> {
		this.chatHistory = [];
		this.currentNode = null;
		this.selectedGraphId = null;
		if (this.chatPanel) {
			await this.chatPanel.updateHistory(this.chatHistory);
		}
	}

	/**
	 * Обрабатывает выбор графа из `GraphView`. Устанавливает выбранный граф и обновляет
	 * историю чата, загружая сообщения, относящиеся к этому графу.
	 * @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.currentNode = data.currentNode; // Может быть пустым, но это не проблема
		await this.fetchMessagesFromRootToNode(data.currentNode);
	}

	/**
	 * Стримит ответ LLM.
	 * @param {string} graphId - ID графа.
	 * @param {string} userNodeId - ID узла пользователя.
	 * @param {string} selectedModel - Выбранная модель.
	 * @param {string | null} existingAssistantNodeId - Существующий ID узла ассистента для повторного использования (для регенерации).
	 */
	async streamLLMResponse(
		graphId: string,
		userNodeId: string,
		selectedModel?: string,
		existingAssistantNodeId: string | null = null
	): Promise<void> {
		const body = {
			graph_id: graphId,
			user_node_id: userNodeId,
			system_prompt: this.plugin.settings.systemPrompt,
			model: selectedModel || this.plugin.settings.defaultModel
		};

		// Если есть существующий ID узла ассистента, добавляем его в тело запроса
		if (existingAssistantNodeId) {
			Object.assign(body, { existing_assistant_node_id: existingAssistantNodeId });
		}

		const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/stream`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify(body) // Используем модифицированное тело запроса
		});

		if (!response.ok) {
			throw new Error(`Ошибка стриминга: ${response.status}`);
		}

		const reader = response.body?.getReader();
		if (!reader) {
			throw new Error('Response body is null');
		}

		const decoder = new TextDecoder();
		let assistantNodeId: string | null = null;
		let accumulatedContent = '';

		try {
			while (true) {
				const { done, value } = await reader.read();
				if (done) break;

				const chunk = decoder.decode(value, { stream: true });
				const lines = chunk.split('\n');

				for (const line of lines) {
					if (line.startsWith('data: ')) {
						const data = JSON.parse(line.slice(6));

						if (data.type === 'node_created') {
							assistantNodeId = data.node_id;
							// Если узел переиспользуется, нужно очистить его содержимое в UI
							if (data.reused) {
								this.clearStreamingMessage(assistantNodeId);
							}
							this.plugin.eventBus.emit('graph-selected', {
								graphId: graphId,
								currentNode: assistantNodeId
							});
						} else if (data.type === 'chunk' && assistantNodeId) {
							accumulatedContent += data.content;
							this.updateStreamingMessage(assistantNodeId, accumulatedContent);
						} else if (data.type === 'error') {
							console.error('Ошибка:', data.error);
							if (assistantNodeId) {
								this.markNodeAsError(assistantNodeId, data.error);
								// Эмиттируем событие, чтобы GraphView обновил отображение узла с ошибкой
								this.plugin.eventBus.emit('graph-selected', {
									graphId: graphId,
									currentNode: assistantNodeId
								});
							}
							break; // Прекращаем обработку при ошибке
						} else if (data.type === 'done') {
							this.currentNode = assistantNodeId;
							this.plugin.eventBus.emit('graph-selected', {
								graphId: graphId,
								currentNode: assistantNodeId
							});
							break;
						}
					}
				}
			}
		} finally {
			reader.releaseLock();
		}
	}

	/**
	 * Очищает содержимое сообщения в истории чата, помечая его как заглушку.
	 * Используется для подготовки к регенерации.
	 * @param {string} nodeId - ID узла, содержимое которого нужно очистить.
	 */
	clearStreamingMessage(nodeId: string): void {
		const messageIndex = this.chatHistory.findIndex((msg) => msg.node_id === nodeId);
		if (messageIndex !== -1) {
			this.chatHistory[messageIndex].content = 'Получение ответа...';
			this.chatHistory[messageIndex].type = 'llm'; // Убедимся, что тип узла LLM
			// Если нужно, сбросьте другие флаги
		}

		if (this.chatPanel) {
			this.chatPanel.updateHistory(this.chatHistory);
		}
	}

	updateStreamingMessage(nodeId: string, content: string): void {
		const lastMessage = this.chatHistory[this.chatHistory.length - 1];
		if (lastMessage && lastMessage.node_id === nodeId) {
			lastMessage.content = content;
		} else {
			this.chatHistory.push({
				role: 'assistant',
				content: content,
				node_id: nodeId,
				graph_id: this.selectedGraphId || ''
			});
		}

		if (this.chatPanel) {
			this.chatPanel.updateHistory(this.chatHistory);
		}
	}

	markNodeAsError(nodeId: string, errorMessage: string): void {
		const lastMessage = this.chatHistory[this.chatHistory.length - 1];
		if (lastMessage && lastMessage.node_id === nodeId) {
			lastMessage.type = 'error';
			lastMessage.content = errorMessage;
		} else {
			// Если узел не последний, попробуйте найти его и обновить
			const messageIndex = this.chatHistory.findIndex((msg) => msg.node_id === nodeId);
			if (messageIndex !== -1) {
				this.chatHistory[messageIndex].type = 'error';
				this.chatHistory[messageIndex].content = errorMessage;
			} else {
				// Если узел не найден, добавьте новое сообщение об ошибке
				this.chatHistory.push({
					role: 'assistant',
					content: errorMessage,
					node_id: nodeId,
					graph_id: this.selectedGraphId || '',
					type: 'error'
				});
			}
		}

		if (this.chatPanel) {
			this.chatPanel.updateHistory(this.chatHistory);
		}
	}

	async handleRegenerateMessage(data: { graphId: string; nodeId: string; model: string }): Promise<void> {
		// НОВОЕ: Добавляем 'model' в тип данных
		try {
			const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/regenerate`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					graph_id: data.graphId,
					node_id: data.nodeId,
					model: data.model // НОВОЕ: Передаем выбранную модель для регенерации
				})
			});

			if (!response.ok) {
				throw new Error(`Ошибка регенерации: ${response.status}`);
			}

			const result = await response.json();
			// НОВОЕ: Получаем ID существующего узла ассистента и ID пользовательского узла
			const existingAssistantNodeId = result.existing_assistant_node_id;
			const userNodeId = result.user_node_id;
			const modelToUse = result.model;

			// Запускаем стриминг, передавая ID существующего узла для повторного использования
			if (data.graphId) {
				await this.streamLLMResponse(data.graphId, userNodeId, modelToUse, existingAssistantNodeId);
			}
		} catch (error: any) {
			console.error('Ошибка регенерации:', error);
			new Notice(`Ошибка регенерации: ${error?.message || error}`, 5000);
		}
	}

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

	handleNodeTypeChanged(data: { graphId: string; nodeId: string; nodeType: string }): void {
		// Обновляем тип узла в истории чата
		const messageIndex = this.chatHistory.findIndex((msg) => msg.node_id === data.nodeId);
		if (messageIndex !== -1) {
			this.chatHistory[messageIndex].type = data.nodeType;
			if (this.chatPanel) {
				this.chatPanel.updateHistory(this.chatHistory);
			}
		}
	}
}
