/**
 * Управляет автокомплитом для ссылок на файлы и промпты.
 * Обеспечивает поиск файлов в хранилище и кеширование результатов.
 */

import { TFile } from 'obsidian';
import LLMAgentPlugin from 'main';

// ----------------------------------------------- Interfaces ---------------------------------------------------------------

export interface AutocompleteItem {
	name: string;
	path: string;
	type: 'file' | 'prompt';
	icon: string; // Obsidian icon name
	file?: TFile; // Ссылка на файл Obsidian
}

// ----------------------------------------------- Autocomplete Manager ---------------------------------------------------------------

export class AutocompleteManager {
	private plugin: LLMAgentPlugin;
	private fileCache: Map<string, AutocompleteItem[]> = new Map();
	private promptCache: Map<string, AutocompleteItem[]> = new Map();
	private lastCacheUpdate = 0;
	private readonly CACHE_TTL = 30000; // 30 секунд

	/**
	 * @property {number} MAX_TOTAL_ITEMS Максимальное количество элементов для поиска и кэширования.
	 */
	static MAX_TOTAL_ITEMS = 20;

	constructor(plugin: LLMAgentPlugin) {
		this.plugin = plugin;
		this.setupFileWatcher();
		// Слушаем изменения настроек для очистки кеша
		this.plugin.eventBus.on('settings-changed', this.clearCache.bind(this));
	}

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

	/**
	 * Получает список файлов для автокомплита
	 * @param query - поисковый запрос
	 * @returns массив файлов для автокомплита
	 */
	async getFileOptions(query: string): Promise<AutocompleteItem[]> {
		if (!this.plugin.settings.enableFileReferences) {
			return [];
		}

		await this.updateCacheIfNeeded();

		const allFiles = this.fileCache.get('all') || [];

		if (!query.trim()) {
			return allFiles.slice(0, AutocompleteManager.MAX_TOTAL_ITEMS); // Показываем первые 10 файлов
		}

		const lowercaseQuery = query.toLowerCase();
		return allFiles
			.filter((item) => item.name.toLowerCase().includes(lowercaseQuery) || item.path.toLowerCase().includes(lowercaseQuery))
			.slice(0, AutocompleteManager.MAX_TOTAL_ITEMS);
	}

	/**
	 * Получает список промптов для автокомплита
	 * @param query - поисковый запрос
	 * @returns массив промптов для автокомплита
	 */
	async getPromptOptions(query: string): Promise<AutocompleteItem[]> {
		if (!this.plugin.settings.enablePromptReferences) {
			return [];
		}

		await this.updateCacheIfNeeded();

		const allPrompts = this.promptCache.get('all') || [];

		if (!query.trim()) {
			return allPrompts.slice(0, AutocompleteManager.MAX_TOTAL_ITEMS);
		}

		const lowercaseQuery = query.toLowerCase();
		return allPrompts
			.filter((item) => item.name.toLowerCase().includes(lowercaseQuery) || item.path.toLowerCase().includes(lowercaseQuery))
			.slice(0, AutocompleteManager.MAX_TOTAL_ITEMS);
	}

	/**
	 * Очищает кеш (используется при изменении настроек)
	 */
	clearCache(): void {
		this.fileCache.clear();
		this.promptCache.clear();
		this.lastCacheUpdate = 0;
	}

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

	/**
	 * Обновляет кеш, если он устарел
	 */
	private async updateCacheIfNeeded(): Promise<void> {
		const now = Date.now();
		if (now - this.lastCacheUpdate > this.CACHE_TTL) {
			await this.updateCache();
			this.lastCacheUpdate = now;
		}
	}

	/**
	 * Обновляет кеш файлов и промптов
	 */
	private async updateCache(): Promise<void> {
		const files = this.plugin.app.vault.getMarkdownFiles();
		const fileItems: AutocompleteItem[] = [];
		const promptItems: AutocompleteItem[] = [];

		// Нормализуем пути исключенных папок
		const normalizedExcludedFolders = this.plugin.settings.excludedFolders
			.filter((folder) => folder.trim() !== '')
			.map((folder) => (folder.endsWith('/') ? folder : folder + '/')) // Убедимся, что заканчивается на слэш
			.map((folder) => (folder.startsWith('/') ? folder.substring(1) : folder)); // Удаляем начальный слэш

		for (const file of files) {
			// Пропускаем файлы из исключенных папок
			if (this.isFileInExcludedFolder(file, normalizedExcludedFolders)) {
				continue;
			}

			const item: AutocompleteItem = {
				name: file.basename,
				path: file.path,
				type: this.isPromptFile(file) ? 'prompt' : 'file',
				icon: this.getFileIcon(file),
				file: file
			};

			if (item.type === 'prompt') {
				promptItems.push(item);
			} else {
				fileItems.push(item);
			}
		}

		// Сортируем по имени
		fileItems.sort((a, b) => a.name.localeCompare(b.name));
		promptItems.sort((a, b) => a.name.localeCompare(b.name));

		this.fileCache.set('all', fileItems);
		this.promptCache.set('all', promptItems);
	}

	/**
	 * Проверяет, является ли файл промптом
	 */
	private isPromptFile(file: TFile): boolean {
		const promptsFolder = this.plugin.settings.promptsFolder;
		if (!promptsFolder) return false;

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

		return file.path.startsWith(normalizedPromptsPath + '/') || file.path === normalizedPromptsPath;
	}

	/**
	 * Возвращает иконку для файла на основе Obsidian иконок
	 */
	private getFileIcon(file: TFile): string {
		if (this.isPromptFile(file)) {
			return 'zap'; // Иконка промпта
		}

		// Определяем иконку по расширению
		const extension = file.extension.toLowerCase();
		switch (extension) {
			case 'md':
				return 'file-text';
			case 'js':
			case 'ts':
				return 'code';
			case 'json':
				return 'brackets';
			case 'css':
				return 'palette';
			case 'png':
			case 'jpg':
			case 'jpeg':
			case 'gif':
				return 'image';
			default:
				return 'file';
		}
	}

	/**
	 * Настраивает отслеживание изменений файлов для обновления кеша
	 */
	private setupFileWatcher(): void {
		this.plugin.registerEvent(
			this.plugin.app.vault.on('create', () => {
				this.clearCache();
			})
		);

		this.plugin.registerEvent(
			this.plugin.app.vault.on('delete', () => {
				this.clearCache();
			})
		);

		this.plugin.registerEvent(
			this.plugin.app.vault.on('rename', () => {
				this.clearCache();
			})
		);
	}

	/**
	 * Проверяет, находится ли файл в одной из исключенных папок.
	 * @param file - файл Obsidian.
	 * @param excludedFolders - массив нормализованных путей исключенных папок.
	 * @returns true, если файл находится в исключенной папке.
	 */
	private isFileInExcludedFolder(file: TFile, excludedFolders: string[]): boolean {
		const filePath = file.path.toLowerCase(); // Для регистронезависимого сравнения

		for (const folder of excludedFolders) {
			const normalizedFolder = folder.toLowerCase();
			// Проверяем, начинается ли путь файла с пути исключенной папки
			// Или если файл находится прямо в корне исключенной папки (н.п. "folder/file.md")
			if (filePath.startsWith(normalizedFolder)) {
				return true;
			}
		}
		return false;
	}
}
