/**
 * Движок шаблонов для обработки промптов с ссылками @{...}.
 * Поддерживает один уровень вложенности и валидацию ссылок.
 */

import { TFile } from 'obsidian';
import LLMAgentPlugin from 'main';
import { ReferenceParser } from './ReferenceParser';

// ----------------------------------------------- Template Engine ---------------------------------------------------------------

export class TemplateEngine {
	private plugin: LLMAgentPlugin;

	constructor(plugin: LLMAgentPlugin) {
		this.plugin = plugin;
	}

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

	/**
	 * Рекурсивно разворачивает промпт, заменяя @{...} ссылки.
	 * Обнаруживает и предотвращает бесконечную рекурсию при циклических ссылках.
	 * @param promptContent - содержимое промпта
	 * @param expandedReferences - внутренний Set для отслеживания уже развернутых ссылок в текущей цепочке рекурсии
	 * @returns развернутый промпт
	 */
	async expandPromptTemplate(promptContent: string, expandedReferences: Set<string> = new Set<string>()): Promise<string> {
		// Находим все шаблонные ссылки @{...}
		const templateReferences = ReferenceParser.extractTemplateReferences(promptContent, ['@', '#']);

		if (templateReferences.length === 0) {
			return promptContent;
		}

		// Получаем все файлы в хранилище Obsidian один раз для всей цепочки рекурсии
		// Это позволяет избежать повторных вызовов vault.getFiles()
		const allFiles = this.plugin.app.vault.getFiles();

		let expandedContent = promptContent;

		// Заменяем каждую ссылку на содержимое соответствующего промпта
		for (const referenceName of templateReferences) {
			if (expandedReferences.has(referenceName)) {
				console.warn(
					`Обнаружена циклическая ссылка на промпт "${referenceName}". Игнорируем для предотвращения бесконечной рекурсии.`
				);
				continue; // Пропускаем циклическую ссылку
			}

			// Добавляем текущую ссылку в Set для отслеживания циклов
			expandedReferences.add(referenceName);

			const referencedContent = await this.loadPromptContent(referenceName, allFiles);
			if (referencedContent !== null) {
				// Рекурсивно разворачиваем содержимое вложенного промпта
				const expandedNestedContent = await this.expandPromptTemplate(referencedContent, expandedReferences);

				// Заменяем все вхождения @{referenceName} или #{referenceName} на содержимое
				const templatePattern = new RegExp(`[#@]?\\{${this.escapeRegExp(referenceName)}\\}`, 'g');
				expandedContent = expandedContent.replace(templatePattern, expandedNestedContent);
			} else {
				// Если промпт не найден, оставляем ссылку как есть
				console.warn(`Промпт "${referenceName}" не найден.`);
			}

			// Удаляем текущую ссылку из Set после её обработки, чтобы не мешать другим веткам рекурсии
			expandedReferences.delete(referenceName);
		}

		return expandedContent;
	}

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

	/**
	 * Загружает содержимое промпта по имени
	 * @param promptName - имя промпта
	 * @returns содержимое промпта или null если не найден
	 */
	private async loadPromptContent(promptName: string, allFiles: TFile[]): Promise<string | null> {
		const promptFile = this.findPromptFile(promptName, allFiles);

		if (!promptFile) {
			return null;
		}

		try {
			const content = await this.plugin.app.vault.read(promptFile);
			return content.trim();
		} catch (error) {
			console.error(`Ошибка чтения промпта "${promptName}":`, error);
			return null;
		}
	}

	/**
	 * Находит файл промпта по имени
	 * @param promptName - имя промпта
	 * @returns файл промпта или null
	 */
	private findPromptFile(promptName: string, allFiles: TFile[]): TFile | null {
		/*const promptsFolder = this.plugin.settings.promptsFolder;
		if (!promptsFolder) {
			return null;
		}

		// Нормализуем путь к папке промптов, убирая начальные/конечные слеши
		const normalizedPromptsPath = promptsFolder.replace(/^\/+|\/+$/g, '');*/

		// Ищем файл, который находится в папке промптов (или ее подпапках), является Markdown-файлом
		// и его имя (без расширения) совпадает с promptName
		for (const file of allFiles) {
			// Проверяем, является ли файл Markdown и находится ли он в папке промптов
			// Сравниваем имя файла (без расширения) с искомым именем промпта
			// Пока без этого: file.path.startsWith(`${normalizedPromptsPath}/`
			if (file.extension === 'md' && file.basename === promptName) {
				return file;
			}
		}

		return null;
	}

	/**
	 * Экранирует специальные символы для регулярного выражения
	 * @param string - строка для экранирования
	 * @returns экранированная строка
	 */
	private escapeRegExp(string: string): string {
		return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
	}
}
