import requests
import os
import json
import sqlite3
import tkinter as tk
from tkinter import messagebox, Toplevel, filedialog
from threading import Thread
from tkinter.scrolledtext import ScrolledText
from ttkbootstrap import Style
from ttkbootstrap.constants import *
from ttkbootstrap import ttk
from PIL import ImageTk, Image

CONFIG_FILE = 'config.json'

# === Загрузка и сохранение конфигурации ===
def load_config():
    if not os.path.exists(CONFIG_FILE):
        default_config = {
            "mc_version": "",
            "mod_loader": "Forge",
            "default_keywords": [],
            "download_folder": "downloads",
            "user_agent": "ModPackBuilder/1.0 (+contact@yourdomain.com)",
        }
        with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
            json.dump(default_config, f, indent=4, ensure_ascii=False)
    with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
        return json.load(f)

def save_config(config):
    with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=4, ensure_ascii=False)

# === Modrinth API запросы и обработка ===
def search_projects_modrinth(description, project_type, mc_version=None, modloader=None, user_agent="ModPackBuilder"):
    url = 'https://api.modrinth.com/v2/search'
    headers = {'User-Agent': user_agent}
    facets = [
        [f"project_type:{project_type}"],
    ]
    if mc_version:
        facets.append([f"versions:{mc_version}"])
    if modloader and project_type == "mod":
        facets.append([f"categories:{modloader.lower()}"])
    params = {'query': description, 'facets': json.dumps(facets), 'limit': 100}
    response = requests.get(url, params=params, headers=headers)
    if response.status_code == 200:
        return response.json().get('hits', [])
    else:
        messagebox.showerror("Ошибка", f"Modrinth API Ошибка: {response.status_code}")
        return []

def get_latest_compatible_version_modrinth(project_id, mc_version=None, modloader=None, user_agent="ModPackBuilder"):
    url = f'https://api.modrinth.com/v2/project/{project_id}/version'
    headers = {'User-Agent': user_agent}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        versions = response.json()
        if mc_version and modloader:
            compatible_versions = [v for v in versions if mc_version in v['game_versions'] and modloader.lower() in [loader.lower() for loader in v.get('loaders', [])]]
        elif mc_version:
            compatible_versions = [v for v in versions if mc_version in v['game_versions']]
        else:
            compatible_versions = versions

        if compatible_versions:
            for version in compatible_versions:
                if version['version_type'] == 'release':
                    return version
            return compatible_versions[0]
    return None

def get_mod_dependencies(version):
    dependencies = version.get('dependencies', [])
    required = []
    for dep in dependencies:
        if dep['dependency_type'] == 'required':
            required.append(dep['project_id'])
    return required

def get_mod_by_id(mod_id, user_agent="ModPackBuilder"):
    url = f'https://api.modrinth.com/v2/project/{mod_id}'
    headers = {'User-Agent': user_agent}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.json()
    return None

# === Spiget API для плагинов ===
def search_plugins_spiget(query, user_agent="ModPackBuilder"):
    url = f'https://api.spiget.org/v2/search/resources/{query}'
    headers = {'User-Agent': user_agent}
    params = {'size': 100}
    response = requests.get(url, params=params, headers=headers)
    if response.status_code == 200:
        return response.json()
    else:
        messagebox.showerror("Ошибка", f"Spiget API Ошибка: {response.status_code}")
        return []

def get_plugin_info_spiget(plugin_id, user_agent="ModPackBuilder"):
    url = f'https://api.spiget.org/v2/resources/{plugin_id}'
    headers = {'User-Agent': user_agent}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.json()
    return None

# === Функция загрузки файла ===
def download_file(url, dest_folder, filename, progress_bar=None, user_agent="ModPackBuilder"):
    local_filename = os.path.join(dest_folder, filename)
    headers = {'User-Agent': user_agent}
    with requests.get(url, stream=True, headers=headers) as r:
        r.raise_for_status()
        total_size = int(r.headers.get('content-length', 0))
        with open(local_filename, 'wb') as f:
            downloaded = 0
            for chunk in r.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
                    downloaded += len(chunk)
                    if progress_bar and total_size > 0:
                        progress = (downloaded / total_size) * 100
                        progress_bar['value'] = progress
                        progress_bar.update()
    return local_filename

# === GUI ===
class ModPackBuilder:
    def __init__(self, root, config):
        self.root = root
        self.config = config
        self.root.title("ModPack Builder")

        # Применяем стиль ttkbootstrap
        self.style = Style(theme="darkly")  # Попробуйте разные темы: 'darkly', 'flatly', 'cyborg', и т.д.

        # Установка иконки приложения (замените 'path_to_icon.ico' на путь к вашей иконке, если есть)
        # self.root.iconbitmap('path_to_icon.ico')

        # Подключение к базе данных
        self.db_connection = sqlite3.connect('user_data.db')
        self.create_tables()

        # Создаем главный фрейм
        self.main_frame = ttk.Frame(root)
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # === Переменные ===
        self.mc_version_var = tk.StringVar(value=config["mc_version"])
        self.modloader_var = tk.StringVar(value=config.get("mod_loader", "Forge"))
        self.description_var = tk.StringVar(value=", ".join(config["default_keywords"]))
        self.build_type_var = tk.StringVar(value="client")

        # Добавляем переменные для категорий модов
        self.mod_categories = {
            "Магия": tk.BooleanVar(),
            "Технические": tk.BooleanVar(),
            "Хардкор": tk.BooleanVar(),
            "Приключения": tk.BooleanVar(),
            "Декоративные": tk.BooleanVar(),
            "Оптимизация": tk.BooleanVar(),
        }

        # Добавляем переменные для исключаемых категорий
        self.exclude_categories = {
            "Читерные": tk.BooleanVar(),
            "Экспериментальные": tk.BooleanVar(),
            "Несовместимые": tk.BooleanVar(),
        }

        # === Создаем вкладки ===
        self.tab_control = ttk.Notebook(self.main_frame)
        self.tab_control.pack(expand=1, fill="both")

        # Вкладки
        self.mods_tab = ttk.Frame(self.tab_control)
        self.resourcepacks_tab = ttk.Frame(self.tab_control)
        self.shaders_tab = ttk.Frame(self.tab_control)
        self.plugins_tab = ttk.Frame(self.tab_control)

        self.tab_control.add(self.mods_tab, text='Моды')
        self.tab_control.add(self.resourcepacks_tab, text='Ресурс-паки')
        self.tab_control.add(self.shaders_tab, text='Шейдеры')
        self.tab_control.add(self.plugins_tab, text='Плагины')

        # === Моды ===
        self.setup_mods_tab()

        # === Ресурс-паки ===
        self.setup_resourcepacks_tab()

        # === Шейдеры ===
        self.setup_shaders_tab()

        # === Плагины ===
        self.setup_plugins_tab()

        # === Настройки сборки ===
        self.setup_build_settings()

        # === Кнопка скачивания ===
        download_button = ttk.Button(self.main_frame, text="Скачать сборку", command=self.download_build)
        download_button.pack(pady=5)

        # Добавляем кнопку для отображения рекомендаций
        recommendations_button = ttk.Button(self.main_frame, text="Рекомендуемые моды", command=self.show_recommendations)
        recommendations_button.pack(pady=5)

        # При изменении размера окна обновляем размеры виджетов
        self.root.bind('<Configure>', self.on_resize)

        # Словари для хранения выбранных элементов
        self.selected_mods_info = {}
        self.selected_resourcepacks_info = {}
        self.selected_shaders_info = {}
        self.selected_plugins_info = {}

        # Обработка закрытия окна
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def create_tables(self):
        cursor = self.db_connection.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS mod_ratings (
                mod_id TEXT PRIMARY KEY,
                rating INTEGER
            )
        ''')
        self.db_connection.commit()

    def setup_mods_tab(self):
        ttk.Label(self.mods_tab, text="Версия Minecraft:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        ttk.Entry(self.mods_tab, textvariable=self.mc_version_var).grid(row=0, column=1, sticky="ew", padx=5, pady=5)

        ttk.Label(self.mods_tab, text="Mod Loader:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        modloader_menu = ttk.OptionMenu(self.mods_tab, self.modloader_var, self.modloader_var.get(), "Forge", "Fabric", "Quilt", "Liteloader", "Cauldron")
        modloader_menu.grid(row=1, column=1, sticky="ew", padx=5, pady=5)

        # Добавляем чекбоксы для категорий модов
        ttk.Label(self.mods_tab, text="Выберите категории модов:").grid(row=2, column=0, sticky="w", padx=5, pady=5)
        categories_frame = ttk.Frame(self.mods_tab)
        categories_frame.grid(row=3, column=0, columnspan=2, sticky="w", padx=5)

        col = 0
        for category, var in self.mod_categories.items():
            chk = ttk.Checkbutton(categories_frame, text=category, variable=var, style="TCheckbutton")
            chk.grid(row=0, column=col, sticky="w", padx=5)
            col += 1

        # Поле для дополнительных ключевых слов
        ttk.Label(self.mods_tab, text="Дополнительные ключевые слова:").grid(row=4, column=0, sticky="w", padx=5, pady=5)
        self.additional_keywords_var = tk.StringVar()
        ttk.Entry(self.mods_tab, textvariable=self.additional_keywords_var).grid(row=4, column=1, sticky="ew", padx=5, pady=5)

        # Добавляем чекбоксы для исключаемых категорий модов
        ttk.Label(self.mods_tab, text="Исключить категории модов:").grid(row=5, column=0, sticky="w", padx=5, pady=5)
        exclude_frame = ttk.Frame(self.mods_tab)
        exclude_frame.grid(row=6, column=0, columnspan=2, sticky="w", padx=5)

        col = 0
        for category, var in self.exclude_categories.items():
            chk = ttk.Checkbutton(exclude_frame, text=category, variable=var, style="TCheckbutton")
            chk.grid(row=0, column=col, sticky="w", padx=5)
            col += 1

        # Кнопка поиска модов
        search_mods_button = ttk.Button(self.mods_tab, text="Поиск модов", command=self.search_mods)
        search_mods_button.grid(row=7, column=0, columnspan=2, pady=5)

        # Список модов с возможностью выбора
        self.mods_list = tk.Listbox(self.mods_tab, selectmode="extended")
        self.mods_list.grid(row=8, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
        self.mods_tab.rowconfigure(8, weight=1)
        self.mods_tab.columnconfigure(1, weight=1)

        self.mods_scrollbar = ttk.Scrollbar(self.mods_tab, orient="vertical", command=self.mods_list.yview)
        self.mods_scrollbar.grid(row=8, column=2, sticky="ns")
        self.mods_list.config(yscrollcommand=self.mods_scrollbar.set)

        # Дерево категорий и модов
        ttk.Label(self.mods_tab, text="Модпак:").grid(row=0, column=3, sticky="w", padx=5, pady=5)
        self.modpack_tree = ttk.Treeview(self.mods_tab)
        self.modpack_tree.grid(row=1, column=3, rowspan=8, sticky="nsew", padx=5, pady=5)
        self.mods_tab.columnconfigure(3, weight=1)

        # Добавляем поле для отображения деталей мода
        self.mod_details_text = ScrolledText(self.mods_tab, height=10)
        self.mod_details_text.grid(row=9, column=0, columnspan=4, sticky="nsew", padx=5, pady=5)
        self.mods_tab.rowconfigure(9, weight=1)

        # Привязываем событие выбора элемента в списке
        self.mods_list.bind('<<ListboxSelect>>', self.show_mod_details)

        # Перетаскивание модов в модпак
        self.mods_list.bind("<Double-Button-1>", self.add_mod_to_modpack)
        self.modpack_tree.bind("<Double-Button-1>", self.remove_mod_from_modpack)

        # Кнопки сохранения и загрузки конфигурации
        buttons_frame = ttk.Frame(self.mods_tab)
        buttons_frame.grid(row=10, column=0, columnspan=4, pady=5)

        ttk.Button(buttons_frame, text="Сохранить конфигурацию", command=self.save_user_config).grid(row=0, column=0, padx=5)
        ttk.Button(buttons_frame, text="Загрузить конфигурацию", command=self.load_user_config).grid(row=0, column=1, padx=5)

        # Кнопка для оценки модов
        ttk.Button(buttons_frame, text="Оценить выбранные моды", command=self.rate_selected_mods).grid(row=0, column=2, padx=5)
        ttk.Button(buttons_frame, text="Проверить совместимость", command=self.check_compatibility).grid(row=0, column=3, padx=5)

        # Сортировка
        ttk.Label(self.mods_tab, text="Сортировать по:").grid(row=11, column=0, sticky="w", padx=5, pady=5)
        self.sort_by_var = tk.StringVar(value="Релевантности")
        sort_menu = ttk.OptionMenu(self.mods_tab, self.sort_by_var, self.sort_by_var.get(), "Релевантности", "Скачиваниям", "Подписчикам")
        sort_menu.grid(row=11, column=1, sticky="w", padx=5)

    def add_mod_to_modpack(self, event):
        selection = self.mods_list.curselection()
        for idx in selection:
            mod_entry = self.mods[idx]
            mod = mod_entry['data']
            mod_id = mod['project_id']
            mod_title = mod['title']
            # Добавляем мод в дерево модпака
            if not self.modpack_tree.exists(mod_id):
                self.modpack_tree.insert('', 'end', iid=mod_id, text=mod_title)
                # Добавляем в выбранные моды
                self.selected_mods_info[mod_id] = mod
                messagebox.showinfo("Мод добавлен", f"Мод '{mod_title}' добавлен в модпак.")
            else:
                messagebox.showwarning("Внимание", f"Мод '{mod_title}' уже добавлен в модпак.")

    def remove_mod_from_modpack(self, event):
        selection = self.modpack_tree.selection()
        for item_id in selection:
            mod_title = self.modpack_tree.item(item_id, 'text')
            self.modpack_tree.delete(item_id)
            if item_id in self.selected_mods_info:
                del self.selected_mods_info[item_id]
            messagebox.showinfo("Мод удален", f"Мод '{mod_title}' удален из модпака.")

    def setup_resourcepacks_tab(self):
        ttk.Label(self.resourcepacks_tab, text="Описание (ключевые слова):").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        self.resourcepack_description_var = tk.StringVar()
        ttk.Entry(self.resourcepacks_tab, textvariable=self.resourcepack_description_var).grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        ttk.Button(self.resourcepacks_tab, text="Поиск ресурс-паков", command=self.search_resourcepacks).grid(row=1, column=0, columnspan=2, pady=5)

        # Список ресурс-паков
        self.resourcepacks_list = tk.Listbox(self.resourcepacks_tab, selectmode="extended")
        self.resourcepacks_list.grid(row=2, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
        self.resourcepacks_tab.rowconfigure(2, weight=1)
        self.resourcepacks_tab.columnconfigure(1, weight=1)

        self.resourcepacks_scrollbar = ttk.Scrollbar(self.resourcepacks_tab, orient="vertical", command=self.resourcepacks_list.yview)
        self.resourcepacks_scrollbar.grid(row=2, column=2, sticky="ns")
        self.resourcepacks_list.config(yscrollcommand=self.resourcepacks_scrollbar.set)

        # Дерево выбранных ресурс-паков
        ttk.Label(self.resourcepacks_tab, text="Выбранные ресурс-паки:").grid(row=0, column=3, sticky="w", padx=5, pady=5)
        self.resourcepack_tree = ttk.Treeview(self.resourcepacks_tab)
        self.resourcepack_tree.grid(row=1, column=3, rowspan=5, sticky="nsew", padx=5, pady=5)
        self.resourcepacks_tab.columnconfigure(3, weight=1)

        # Перетаскивание
        self.resourcepacks_list.bind("<Double-Button-1>", self.add_resourcepack)
        self.resourcepack_tree.bind("<Double-Button-1>", self.remove_resourcepack)

    def add_resourcepack(self, event):
        selection = self.resourcepacks_list.curselection()
        for idx in selection:
            rp_entry = self.resourcepacks[idx]
            rp = rp_entry['data']
            rp_id = rp['project_id']
            rp_title = rp['title']
            if not self.resourcepack_tree.exists(rp_id):
                self.resourcepack_tree.insert('', 'end', iid=rp_id, text=rp_title)
                self.selected_resourcepacks_info[rp_id] = rp
                messagebox.showinfo("Ресурс-пак добавлен", f"Ресурс-пак '{rp_title}' добавлен.")
            else:
                messagebox.showwarning("Внимание", f"Ресурс-пак '{rp_title}' уже добавлен.")

    def remove_resourcepack(self, event):
        selection = self.resourcepack_tree.selection()
        for item_id in selection:
            rp_title = self.resourcepack_tree.item(item_id, 'text')
            self.resourcepack_tree.delete(item_id)
            if item_id in self.selected_resourcepacks_info:
                del self.selected_resourcepacks_info[item_id]
            messagebox.showinfo("Ресурс-пак удален", f"Ресурс-пак '{rp_title}' удален.")

    def setup_shaders_tab(self):
        ttk.Label(self.shaders_tab, text="Описание (ключевые слова):").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        self.shader_description_var = tk.StringVar()
        ttk.Entry(self.shaders_tab, textvariable=self.shader_description_var).grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        ttk.Button(self.shaders_tab, text="Поиск шейдеров", command=self.search_shaders).grid(row=1, column=0, columnspan=2, pady=5)

        # Список шейдеров
        self.shaders_list = tk.Listbox(self.shaders_tab, selectmode="extended")
        self.shaders_list.grid(row=2, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
        self.shaders_tab.rowconfigure(2, weight=1)
        self.shaders_tab.columnconfigure(1, weight=1)

        self.shaders_scrollbar = ttk.Scrollbar(self.shaders_tab, orient="vertical", command=self.shaders_list.yview)
        self.shaders_scrollbar.grid(row=2, column=2, sticky="ns")
        self.shaders_list.config(yscrollcommand=self.shaders_scrollbar.set)

        # Дерево выбранных шейдеров
        ttk.Label(self.shaders_tab, text="Выбранные шейдеры:").grid(row=0, column=3, sticky="w", padx=5, pady=5)
        self.shader_tree = ttk.Treeview(self.shaders_tab)
        self.shader_tree.grid(row=1, column=3, rowspan=5, sticky="nsew", padx=5, pady=5)
        self.shaders_tab.columnconfigure(3, weight=1)

        # Перетаскивание
        self.shaders_list.bind("<Double-Button-1>", self.add_shader)
        self.shader_tree.bind("<Double-Button-1>", self.remove_shader)

    def add_shader(self, event):
        selection = self.shaders_list.curselection()
        for idx in selection:
            shader_entry = self.shaders[idx]
            shader = shader_entry['data']
            shader_id = shader['project_id']
            shader_title = shader['title']
            if not self.shader_tree.exists(shader_id):
                self.shader_tree.insert('', 'end', iid=shader_id, text=shader_title)
                self.selected_shaders_info[shader_id] = shader
                messagebox.showinfo("Шейдер добавлен", f"Шейдер '{shader_title}' добавлен.")
            else:
                messagebox.showwarning("Внимание", f"Шейдер '{shader_title}' уже добавлен.")

    def remove_shader(self, event):
        selection = self.shader_tree.selection()
        for item_id in selection:
            shader_title = self.shader_tree.item(item_id, 'text')
            self.shader_tree.delete(item_id)
            if item_id in self.selected_shaders_info:
                del self.selected_shaders_info[item_id]
            messagebox.showinfo("Шейдер удален", f"Шейдер '{shader_title}' удален.")

    def setup_plugins_tab(self):
        ttk.Label(self.plugins_tab, text="Поиск плагинов:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        self.plugin_description_var = tk.StringVar()
        ttk.Entry(self.plugins_tab, textvariable=self.plugin_description_var).grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        ttk.Button(self.plugins_tab, text="Поиск плагинов", command=self.search_plugins).grid(row=1, column=0, columnspan=2, pady=5)

        # Список плагинов
        self.plugins_list = tk.Listbox(self.plugins_tab, selectmode="extended")
        self.plugins_list.grid(row=2, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
        self.plugins_tab.rowconfigure(2, weight=1)
        self.plugins_tab.columnconfigure(1, weight=1)

        self.plugins_scrollbar = ttk.Scrollbar(self.plugins_tab, orient="vertical", command=self.plugins_list.yview)
        self.plugins_scrollbar.grid(row=2, column=2, sticky="ns")
        self.plugins_list.config(yscrollcommand=self.plugins_scrollbar.set)

        # Дерево выбранных плагинов
        ttk.Label(self.plugins_tab, text="Выбранные плагины:").grid(row=0, column=3, sticky="w", padx=5, pady=5)
        self.plugin_tree = ttk.Treeview(self.plugins_tab)
        self.plugin_tree.grid(row=1, column=3, rowspan=5, sticky="nsew", padx=5, pady=5)
        self.plugins_tab.columnconfigure(3, weight=1)

        # Перетаскивание
        self.plugins_list.bind("<Double-Button-1>", self.add_plugin)
        self.plugin_tree.bind("<Double-Button-1>", self.remove_plugin)

    def add_plugin(self, event):
        selection = self.plugins_list.curselection()
        for idx in selection:
            plugin = self.plugins[idx]
            plugin_id = str(plugin['id'])
            plugin_title = plugin['name']
            if not self.plugin_tree.exists(plugin_id):
                self.plugin_tree.insert('', 'end', iid=plugin_id, text=plugin_title)
                self.selected_plugins_info[plugin_id] = plugin
                messagebox.showinfo("Плагин добавлен", f"Плагин '{plugin_title}' добавлен.")
            else:
                messagebox.showwarning("Внимание", f"Плагин '{plugin_title}' уже добавлен.")

    def remove_plugin(self, event):
        selection = self.plugin_tree.selection()
        for item_id in selection:
            plugin_title = self.plugin_tree.item(item_id, 'text')
            self.plugin_tree.delete(item_id)
            if item_id in self.selected_plugins_info:
                del self.selected_plugins_info[item_id]
            messagebox.showinfo("Плагин удален", f"Плагин '{plugin_title}' удален.")

    def setup_build_settings(self):
        settings_frame = ttk.Frame(self.main_frame)
        settings_frame.pack(fill=tk.X, padx=5, pady=5)

        ttk.Label(settings_frame, text="Тип сборки:").grid(row=0, column=0, sticky="w", padx=5)
        build_type_menu = ttk.OptionMenu(settings_frame, self.build_type_var, self.build_type_var.get(), "client", "server", "both")
        build_type_menu.grid(row=0, column=1, sticky="w", padx=5)

    def on_resize(self, event):
        # Обновляем размеры виджетов при изменении размера окна
        self.main_frame.update_idletasks()

    def on_closing(self):
        # Закрываем соединение с базой данных
        self.db_connection.close()
        self.root.destroy()

    # === Функции для вкладки модов ===
    def search_mods(self):
        # Получаем выбранные категории
        selected_categories = [category for category, var in self.mod_categories.items() if var.get()]
        categories_query = " ".join(selected_categories)

        # Получаем дополнительные ключевые слова
        additional_keywords = self.additional_keywords_var.get()

        # Формируем общий запрос
        description = f"{categories_query} {additional_keywords}".strip()

        mc_version = self.mc_version_var.get()
        modloader = self.modloader_var.get()

        self.config["mc_version"] = mc_version
        self.config["mod_loader"] = modloader
        self.config["default_keywords"] = description.split(", ")
        save_config(self.config)

        self.mods_list.delete(0, tk.END)
        self.mods = []

        mods = search_projects_modrinth(description, "mod", mc_version, modloader, self.config["user_agent"])

        # Получаем исключаемые категории
        excluded_categories = [category for category, var in self.exclude_categories.items() if var.get()]

        # Если выбраны категории для исключения, получаем их идентификаторы
        excluded_category_ids = []
        if excluded_categories:
            # Получаем список всех категорий с их идентификаторами
            all_categories = self.get_all_categories()
            for name in excluded_categories:
                cat_id = self.get_category_id_by_name(name, all_categories)
                if cat_id:
                    excluded_category_ids.append(cat_id)

        sort_by = self.sort_by_var.get()
        if sort_by == "Скачиваниям":
            mods.sort(key=lambda m: m.get('downloads', 0), reverse=True)
        elif sort_by == "Подписчикам":
            mods.sort(key=lambda m: m.get('follows', 0), reverse=True)
        # Если "Релевантности", оставляем порядок по умолчанию

        for mod in mods:
            # Проверяем, не входит ли мод в исключаемые категории
            if excluded_category_ids:
                if set(mod.get('categories', [])).intersection(excluded_category_ids):
                    continue  # Пропускаем мод
            self.mods.append({"source": "Modrinth", "data": mod})
            # Получаем информацию о популярности
            downloads = mod.get('downloads', 0)
            followers = mod.get('follows', 0)
            # Отображаем название мода, описание и показатели популярности
            self.mods_list.insert(tk.END, f"{mod['title']} ({downloads} скачиваний, {followers} подписчиков) - {mod['description']}")

    def get_all_categories(self):
        url = 'https://api.modrinth.com/v2/tag/category'
        headers = {'User-Agent': self.config["user_agent"]}
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.json()
        else:
            return []

    def get_category_id_by_name(self, name, categories):
        for category in categories:
            if category['name'].lower() == name.lower():
                return category['slug']
        return None

    def show_mod_details(self, event):
        selection = self.mods_list.curselection()
        if selection:
            index = selection[0]
            mod_entry = self.mods[index]
            mod = mod_entry['data']
            description = mod.get('description', 'Нет описания')
            downloads = mod.get('downloads', 0)
            followers = mod.get('follows', 0)
            # Очищаем и заполняем поле с деталями
            self.mod_details_text.delete('1.0', tk.END)
            self.mod_details_text.insert(tk.END, f"Название: {mod['title']}\n")
            self.mod_details_text.insert(tk.END, f"Скачивания: {downloads}\n")
            self.mod_details_text.insert(tk.END, f"Подписчики: {followers}\n")
            self.mod_details_text.insert(tk.END, f"\n{description}")

    def check_compatibility(self):
        # Получаем список выбранных модов из дерева
        mod_ids = self.modpack_tree.get_children()
        if not mod_ids:
            messagebox.showwarning("Предупреждение", "Добавьте хотя бы один мод в модпак для проверки.")
            return

        mc_version = self.config["mc_version"]
        modloader = self.config["mod_loader"].lower()

        incompatible_mods = []
        mod_map = {}

        # Получение информации о выбранных модах и их версиях
        for mod_id in mod_ids:
            mod = self.selected_mods_info.get(mod_id)
            if not mod:
                continue
            mod_title = mod['title']
            version = get_latest_compatible_version_modrinth(mod_id, mc_version, modloader, self.config["user_agent"])
            if version:
                mod_map[mod_id] = {
                    'title': mod_title,
                    'version': version,
                    'dependencies': get_mod_dependencies(version),
                }
            else:
                incompatible_mods.append(mod_title)

        if incompatible_mods:
            messagebox.showerror("Несовместимые моды", f"Следующие моды не совместимы с версией {mc_version} и загрузчиком {modloader.capitalize()}:\n" + "\n".join(incompatible_mods))
            return

        # Проверка зависимостей и автоматическое добавление недостающих
        missing_dependencies = []
        processed_mods = set()
        mods_to_process = list(mod_map.keys())

        while mods_to_process:
            current_mods = mods_to_process
            mods_to_process = []
            for mod_id in current_mods:
                if mod_id in processed_mods:
                    continue
                mod_info = mod_map[mod_id]
                processed_mods.add(mod_id)
                for dep_id in mod_info['dependencies']:
                    if dep_id not in mod_map:
                        dep_project = get_mod_by_id(dep_id, self.config["user_agent"])
                        if dep_project:
                            dep_title = dep_project['title']
                            dep_version = get_latest_compatible_version_modrinth(dep_id, mc_version, modloader, self.config["user_agent"])
                            if dep_version:
                                # Добавляем зависимость в список модов
                                mod_map[dep_id] = {
                                    'title': dep_title,
                                    'version': dep_version,
                                    'dependencies': get_mod_dependencies(dep_version),
                                }
                                self.selected_mods_info[dep_id] = dep_project
                                if not self.modpack_tree.exists(dep_id):
                                    self.modpack_tree.insert('', 'end', iid=dep_id, text=dep_title)
                                # Добавляем в список для дальнейшей обработки зависимостей этой зависимости
                                mods_to_process.append(dep_id)
                            else:
                                missing_dependencies.append(dep_title)
                        else:
                            missing_dependencies.append(dep_id)

        if missing_dependencies:
            messagebox.showwarning("Отсутствующие зависимости", f"Следующие зависимости отсутствуют или несовместимы:\n" + "\n".join(set(missing_dependencies)))
            return

        # Сохраняем информацию о выбранных модах и их зависимостях для дальнейшего использования при скачивании
        self.selected_mods_info = mod_map

        messagebox.showinfo("Совместимость", "Выбранные моды совместимы между собой и с текущей версией Minecraft. Все необходимые зависимости будут скачаны.")

    # Метод для оценки модов
    def rate_selected_mods(self):
        selection = self.mods_list.curselection()
        if not selection:
            messagebox.showwarning("Предупреждение", "Выберите хотя бы один мод для оценки.")
            return

        for idx in selection:
            mod_entry = self.mods[idx]
            mod = mod_entry['data']
            mod_title = mod['title']

            # Окно для ввода оценки
            rating_window = Toplevel(self.root)
            rating_window.title(f"Оценка мода {mod_title}")

            ttk.Label(rating_window, text=f"Оцените мод '{mod_title}' (1-5):").pack(pady=5)
            rating_var = tk.IntVar()
            ttk.Entry(rating_window, textvariable=rating_var).pack(pady=5)
            ttk.Button(rating_window, text="Сохранить оценку", command=lambda mod_id=mod['project_id'], var=rating_var, win=rating_window: self.save_mod_rating(mod_id, var.get(), win)).pack(pady=5)

    # Метод для сохранения оценки мода
    def save_mod_rating(self, mod_id, rating, window):
        if rating < 1 or rating > 5:
            messagebox.showwarning("Предупреждение", "Оценка должна быть от 1 до 5.")
            return

        # Сохраняем оценку в локальной базе данных
        self.save_rating_to_db(mod_id, rating)
        messagebox.showinfo("Спасибо!", "Ваша оценка сохранена.")
        window.destroy()

    def save_rating_to_db(self, mod_id, rating):
        cursor = self.db_connection.cursor()
        cursor.execute('''
            INSERT OR REPLACE INTO mod_ratings (mod_id, rating)
            VALUES (?, ?)
        ''', (mod_id, rating))
        self.db_connection.commit()

    # === Функции для вкладки ресурс-паков ===
    def search_resourcepacks(self):
        description = self.resourcepack_description_var.get()

        self.resourcepacks_list.delete(0, tk.END)
        self.resourcepacks = []

        resourcepacks = search_projects_modrinth(description, "resourcepack", user_agent=self.config["user_agent"])
        for rp in resourcepacks:
            self.resourcepacks.append({"source": "Modrinth", "data": rp})
            self.resourcepacks_list.insert(tk.END, f"{rp['title']} - {rp['description']}")

    # === Функции для вкладки шейдеров ===
    def search_shaders(self):
        description = self.shader_description_var.get()

        self.shaders_list.delete(0, tk.END)
        self.shaders = []

        shaders = search_projects_modrinth(description, "shader", user_agent=self.config["user_agent"])
        for shader in shaders:
            self.shaders.append({"source": "Modrinth", "data": shader})
            self.shaders_list.insert(tk.END, f"{shader['title']} - {shader['description']}")

    # === Функции для вкладки плагинов ===
    def search_plugins(self):
        query = self.plugin_description_var.get()

        self.plugins_list.delete(0, tk.END)
        self.plugins = []

        plugins = search_plugins_spiget(query, self.config["user_agent"])
        for plugin in plugins:
            self.plugins.append(plugin)
            self.plugins_list.insert(tk.END, f"{plugin['name']} - {plugin.get('tag', '')}")

    # === Функция скачивания сборки ===
    def download_build(self):
        build_type = self.build_type_var.get()

        if build_type in ["client", "both"]:
            self.download_client_build()

        if build_type in ["server", "both"]:
            self.download_server_build()

    def download_client_build(self):
        if not os.path.exists(self.config["download_folder"]):
            os.makedirs(self.config["download_folder"])

        self.progress_window = Toplevel(self.root)
        self.progress_window.title("Прогресс скачивания клиента")

        progress_bar = ttk.Progressbar(self.progress_window, orient="horizontal", length=300, mode="determinate")
        progress_bar.pack(pady=20)

        def download():
            # Скачивание модов
            mod_items = list(self.selected_mods_info.items())
            total_mods = len(mod_items)
            for idx, (mod_id, mod_info) in enumerate(mod_items):
                mod_title = mod_info['title']
                version = mod_info['version']
                files = [file for file in version['files'] if file['primary']]
                if files:
                    file_info = files[0]
                    filename = file_info['filename']
                    url = file_info['url']
                    dest_folder = os.path.join(self.config["download_folder"], "mods")
                    if not os.path.exists(dest_folder):
                        os.makedirs(dest_folder)
                    download_file(url, dest_folder, filename, progress_bar, self.config["user_agent"])
                else:
                    messagebox.showinfo("Информация", f"Нет файлов для мода {mod_title}.")

                progress = ((idx + 1) / total_mods) * 100
                progress_bar['value'] = progress
                progress_bar.update()

            # Скачивание ресурс-паков
            resourcepack_items = list(self.selected_resourcepacks_info.items())
            for idx, (rp_id, rp) in enumerate(resourcepack_items):
                version = get_latest_compatible_version_modrinth(rp['project_id'], user_agent=self.config["user_agent"])
                if version:
                    files = version['files']
                    if files:
                        file_info = files[0]
                        filename = file_info['filename']
                        url = file_info['url']
                        dest_folder = os.path.join(self.config["download_folder"], "resourcepacks")
                        if not os.path.exists(dest_folder):
                            os.makedirs(dest_folder)
                        download_file(url, dest_folder, filename, progress_bar, self.config["user_agent"])

            # Скачивание шейдеров
            shader_items = list(self.selected_shaders_info.items())
            for idx, (shader_id, shader) in enumerate(shader_items):
                version = get_latest_compatible_version_modrinth(shader['project_id'], user_agent=self.config["user_agent"])
                if version:
                    files = version['files']
                    if files:
                        file_info = files[0]
                        filename = file_info['filename']
                        url = file_info['url']
                        dest_folder = os.path.join(self.config["download_folder"], "shaderpacks")
                        if not os.path.exists(dest_folder):
                            os.makedirs(dest_folder)
                        download_file(url, dest_folder, filename, progress_bar, self.config["user_agent"])

            messagebox.showinfo("Готово", "Клиентская сборка успешно скачана!")
            self.progress_window.destroy()

        Thread(target=download).start()

    def download_server_build(self):
        if not os.path.exists(self.config["download_folder"]):
            os.makedirs(self.config["download_folder"])

        self.progress_window = Toplevel(self.root)
        self.progress_window.title("Прогресс скачивания сервера")

        progress_bar = ttk.Progressbar(self.progress_window, orient="horizontal", length=300, mode="determinate")
        progress_bar.pack(pady=20)

        def download():
            # Скачивание серверных файлов
            server_folder = os.path.join(self.config["download_folder"], "server")
            if not os.path.exists(server_folder):
                os.makedirs(server_folder)

            mc_version = self.config["mc_version"]
            modloader = self.config["mod_loader"]
            user_agent = self.config["user_agent"]

            if modloader.lower() == "forge":
                # Скачиваем Forge Installer
                forge_version = self.get_forge_version(mc_version)
                if forge_version:
                    url = f"https://maven.minecraftforge.net/net/minecraftforge/forge/{mc_version}-{forge_version}/forge-{mc_version}-{forge_version}-installer.jar"
                    filename = f"forge-{mc_version}-{forge_version}-installer.jar"
                    download_file(url, server_folder, filename, progress_bar, user_agent)
                    # Запускаем установщик сервера
                    installer_path = os.path.join(server_folder, filename)
                    os.system(f'java -jar "{installer_path}" --installServer')
                    # Принимаем EULA
                    self.accept_eula(server_folder)
                else:
                    messagebox.showerror("Ошибка", "Не удалось найти версию Forge для данной версии Minecraft.")
                    return
            else:
                messagebox.showwarning("Внимание", f"Автоматическая установка сервера для {modloader} не поддерживается.")
                return

            # Скачивание модов
            mod_items = list(self.selected_mods_info.items())
            total_mods = len(mod_items)
            for idx, (mod_id, mod_info) in enumerate(mod_items):
                mod_title = mod_info['title']
                version = mod_info['version']
                files = [file for file in version['files'] if file['primary']]
                if files:
                    file_info = files[0]
                    filename = file_info['filename']
                    url = file_info['url']
                    dest_folder = os.path.join(server_folder, "mods")
                    if not os.path.exists(dest_folder):
                        os.makedirs(dest_folder)
                    download_file(url, dest_folder, filename, progress_bar, user_agent)
                else:
                    messagebox.showinfo("Информация", f"Нет файлов для мода {mod_title}.")

                progress = ((idx + 1) / total_mods) * 100
                progress_bar['value'] = progress
                progress_bar.update()

            # Скачивание плагинов
            plugin_items = list(self.selected_plugins_info.items())
            for idx, (plugin_id, plugin) in enumerate(plugin_items):
                url = f'https://api.spiget.org/v2/resources/{plugin_id}/download'
                dest_folder = os.path.join(server_folder, "plugins")
                if not os.path.exists(dest_folder):
                    os.makedirs(dest_folder)
                filename = f"{plugin['name']}.jar"
                download_file(url, dest_folder, filename, progress_bar, user_agent)

            messagebox.showinfo("Готово", "Серверная сборка успешно скачана!")
            self.progress_window.destroy()

        Thread(target=download).start()

    def get_forge_version(self, mc_version):
        # Получение последней версии Forge для заданной версии Minecraft
        url = f"https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json"
        response = requests.get(url)
        if response.status_code == 200:
            data = response.json()
            promotions = data.get('promos', {})
            key = f"{mc_version}-latest"
            forge_version = promotions.get(key)
            return forge_version
        return None

    def accept_eula(self, server_folder):
        eula_path = os.path.join(server_folder, 'eula.txt')
        with open(eula_path, 'w') as file:
            file.write('eula=true\n')

    # Метод для отображения рекомендаций
    def show_recommendations(self):
        trending_mods = self.get_trending_mods()
        recommendations_window = Toplevel(self.root)
        recommendations_window.title("Рекомендуемые моды")

        mods_listbox = tk.Listbox(recommendations_window)
        mods_listbox.pack(fill=tk.BOTH, expand=True)

        for mod in trending_mods:
            mods_listbox.insert(tk.END, f"{mod['title']} - {mod['description']}")

    def get_trending_mods(self):
        url = 'https://api.modrinth.com/v2/search'
        headers = {'User-Agent': self.config["user_agent"]}
        params = {
            'facets': json.dumps([[f"project_type:mod"]]),
            'sort': 'downloads',
            'limit': 10
        }
        response = requests.get(url, params=params, headers=headers)
        if response.status_code == 200:
            return response.json().get('hits', [])
        else:
            messagebox.showerror("Ошибка", f"Modrinth API Ошибка: {response.status_code}")
            return []

    def save_user_config(self):
        # Сохраняем все выбранные моды из дерева
        selected_mods = [self.modpack_tree.item(mod_id, 'text') for mod_id in self.modpack_tree.get_children()]
        user_config = {
            "selected_mods": selected_mods,
            "mod_categories": {k: v.get() for k, v in self.mod_categories.items()},
            "exclude_categories": {k: v.get() for k, v in self.exclude_categories.items()},
            "additional_keywords": self.additional_keywords_var.get(),
            "mc_version": self.mc_version_var.get(),
            "mod_loader": self.modloader_var.get(),
        }
        with open('user_config.json', 'w', encoding='utf-8') as f:
            json.dump(user_config, f, ensure_ascii=False, indent=4)
        messagebox.showinfo("Сохранение", "Конфигурация сохранена.")

    def load_user_config(self):
        if not os.path.exists('user_config.json'):
            messagebox.showwarning("Загрузка", "Конфигурация не найдена.")
            return
        with open('user_config.json', 'r', encoding='utf-8') as f:
            user_config = json.load(f)
        # Восстанавливаем версию Minecraft и модлоадер
        self.mc_version_var.set(user_config.get("mc_version", ""))
        self.modloader_var.set(user_config.get("mod_loader", "Forge"))
        # Восстанавливаем выбранные категории
        for k, v in user_config.get("mod_categories", {}).items():
            if k in self.mod_categories:
                self.mod_categories[k].set(v)
        for k, v in user_config.get("exclude_categories", {}).items():
            if k in self.exclude_categories:
                self.exclude_categories[k].set(v)
        # Восстанавливаем ключевые слова
        self.additional_keywords_var.set(user_config.get("additional_keywords", ""))
        # Обновляем список модов
        self.search_mods()
        # Восстанавливаем выбранные моды в дереве модпака
        selected_mods = user_config.get("selected_mods", [])
        for idx, mod_entry in enumerate(self.mods):
            mod_str = self.mods_list.get(idx)
            for mod_name in selected_mods:
                if mod_name in mod_str:
                    self.mods_list.selection_set(idx)
                    self.add_mod_to_modpack(None)
        messagebox.showinfo("Загрузка", "Конфигурация загружена.")

# === Запуск приложения ===
if __name__ == '__main__':
    config = load_config()
    root = tk.Tk()
    app = ModPackBuilder(root, config)
    root.mainloop()

можно разделить на части пожалуста и написать структуру