// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\IvSpleef.java

package me.intvk.ivspleef;

import lombok.Getter;
import me.intvk.ivspleef.commands.LanguageCommand;
import me.intvk.ivspleef.commands.SpleefCommand;
import me.intvk.ivspleef.commands.TestCommand;
import me.intvk.ivspleef.database.DatabaseManager;
import me.intvk.ivspleef.listeners.JoinListener;
import me.intvk.ivspleef.listeners.QuitListener;
import me.intvk.ivspleef.utils.l18n.LocalizationManager;  // Импортируйте LocalizationManager
import me.intvk.ivspleef.utils.player.SPlayerManager;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.IOException;

public final class IvSpleef extends JavaPlugin {

    @Getter
    private DatabaseManager databaseManager;

    private SPlayerManager sPlayerManager;

    @Getter
    private LocalizationManager localizationManager;

    @Override
    public void onEnable() {
        initializeDatabase();
        initializeManagers();        // Инициализация менеджеров
        registerListeners();         // Регистрация слушателей
        registerCommands();
        getLogger().info("IvSpleef enabled!");
    }

    @Override
    public void onDisable() {
        if (databaseManager != null) {
            databaseManager.close(); // Закрытие подключения к базе данных
        }
        getLogger().info("IvSpleef disabled!");
    }

    /**
     * Инициализирует базу данных и подключение через DatabaseManager.
     */
    private void initializeDatabase() {
        try {
            databaseManager = new DatabaseManager(this);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Инициализирует все необходимые менеджеры.
     */
    private void initializeManagers() {
        sPlayerManager = new SPlayerManager(this, databaseManager);
        localizationManager = new LocalizationManager(this);  // Инициализируем LocalizationManager
    }

    /**
     * Регистрирует все слушатели событий.
     */
    private void registerListeners() {
        getServer().getPluginManager().registerEvents(new JoinListener(sPlayerManager), this);
        getServer().getPluginManager().registerEvents(new QuitListener(sPlayerManager), this);
    }

    private void registerCommands() {
        getCommand("spleef").setExecutor(new SpleefCommand(this, sPlayerManager, localizationManager));
        getCommand("test").setExecutor(new TestCommand(sPlayerManager, localizationManager));
        getCommand("language").setExecutor(new LanguageCommand(sPlayerManager, localizationManager));
    }

    public SPlayerManager getSPlayerManager() {
        return sPlayerManager;
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\commands\LanguageCommand.java

package me.intvk.ivspleef.commands;

import lombok.AllArgsConstructor;
import me.intvk.ivspleef.utils.l18n.LocalizationManager;
import me.intvk.ivspleef.utils.player.SPlayer;
import me.intvk.ivspleef.utils.player.SPlayerManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

import java.util.UUID;

@AllArgsConstructor
public class LanguageCommand implements CommandExecutor {
    private final SPlayerManager sPlayerManager;
    private final LocalizationManager localizationManager;

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        // Проверяем, является ли отправитель игроком
        if (!(sender instanceof Player player)) {
            sender.sendMessage("Эту команду может выполнять только игрок.");
            return true;
        }

        UUID uuid = player.getUniqueId();

        // Получаем игрока из системы
        SPlayer sPlayer = sPlayerManager.getPlayer(player.getUniqueId()).orElse(null);
        if (sPlayer == null) {
            player.sendMessage("Игрок не найден в системе.");
            return true;
        }

        // Проверяем количество аргументов
        if (args.length != 1) {
            player.sendMessage("Использование: /language <язык>");
            return true;
        }

        String newLanguage = args[0]; // Получаем новый язык из аргументов

        // Проверяем, доступен ли указанный язык
        if (!localizationManager.getAvailableLocales().contains(newLanguage)) {
            player.sendMessage("Недоступный язык. Доступные языки: " + String.join(", ", localizationManager.getAvailableLocales()));
            return true;
        }

        // Изменяем язык игрока
        sPlayerManager.updatePlayerLanguage(uuid, newLanguage);
        player.sendMessage("Ваш язык был изменён на: " + newLanguage);

        return true;
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\commands\SpleefCommand.java

package me.intvk.ivspleef.commands;

import lombok.AllArgsConstructor;
import me.intvk.ivspleef.utils.l18n.LocalizationManager;
import me.intvk.ivspleef.utils.player.SPlayerManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin;

@AllArgsConstructor
public class SpleefCommand implements CommandExecutor {
    private final JavaPlugin plugin;
    private final SPlayerManager sPlayerManager;
    private final LocalizationManager localizationManager;

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        // Проверяем, что аргументы переданы
        if (args.length == 0) {
            sender.sendMessage("Использование: /spleef <reload>");
            return false; // Завершаем команду, если не указаны аргументы
        }

        switch (args[0].toLowerCase()) {
            case "reload":
                reloadConfiguration(sender);
                break;

            default:
                sender.sendMessage("Неизвестная команда. Используйте: /spleef <reload>");
                return false; // Завершаем команду, если аргумент не распознан
        }

        return true; // Команда выполнена успешно
    }

    private void reloadConfiguration(CommandSender sender) {
        // Логика перезагрузки конфигурации
        try {
            // Предполагается, что у вас есть методы для перезагрузки конфигурации
            localizationManager.reloadLocales();
            // Можно добавить другие методы для перезагрузки конфигурации других компонентов

            sender.sendMessage("Конфигурация успешно перезагружена!");
            plugin.getLogger().info("Конфигурация была перезагружена командой: " + sender.getName());
        } catch (Exception e) {
            sender.sendMessage("Ошибка при перезагрузке конфигурации: " + e.getMessage());
            plugin.getLogger().severe("Ошибка при перезагрузке конфигурации: " + e.getMessage());
        }
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\commands\TestCommand.java

package me.intvk.ivspleef.commands;

import lombok.AllArgsConstructor;
import me.intvk.ivspleef.utils.l18n.LocalizationManager;
import me.intvk.ivspleef.utils.player.SPlayer;
import me.intvk.ivspleef.utils.player.SPlayerManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

@AllArgsConstructor
public class TestCommand implements CommandExecutor {
    private final SPlayerManager sPlayerManager;
    private final LocalizationManager localizationManager;

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        // Проверяем, является ли отправитель игроком
        if (!(sender instanceof Player player)) {
            sender.sendMessage("Эту команду может выполнять только игрок.");
            return false; // Завершить команду, если отправитель не игрок
        }

        // Получаем игрока из системы
        SPlayer sPlayer = sPlayerManager.getPlayer(player.getUniqueId()).orElse(null);
        if (sPlayer == null) {
            player.sendMessage("Игрок не найден в системе.");
            return true;
        }

        // Проверяем наличие аргументов
        if (args.length == 0) {
            player.sendMessage("Пожалуйста, введи ключ.");
            return true;
        }

        String key = args[0]; // Получаем ключ из аргументов
        String language = sPlayer.getLanguage();

        // Получаем сообщение по ключу
        String value = localizationManager.getMessage(key, language);

        player.sendMessage("Ключ: " + key + ". Ответ: " + value);
        player.sendMessage("Доступные языки: " + String.join(", ", localizationManager.getAvailableLocales()));
        player.sendMessage("Твой язык: " + language);

        return true;
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\config\GameConfig.java

package me.intvk.ivspleef.config;

import lombok.Builder;
import lombok.Getter;
import org.bukkit.Location;
import org.bukkit.Material;

import java.awt.*;

@Getter
@Builder
public class GameConfig {
    private final Location center;
    private final int radius;
    private final Shape shape;
    private final int minPlayers;
    private final int maxPlayers;
    private final int gameTime;
    private final double deathY;
    private final Material arenaBlockType;
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\database\AsyncResult.java

// AsyncResult.java
package me.intvk.ivspleef.database;

import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class AsyncResult<T> {
    @Getter
    private final CompletableFuture<T> future;
    private final JavaPlugin plugin;

    public AsyncResult(CompletableFuture<T> future) {
        this.future = future;
        this.plugin = JavaPlugin.getProvidingPlugin(AsyncResult.class);
    }

    public AsyncResult<T> thenSync(BiConsumer<T, Exception> action) {
        future.whenComplete((result, exception) ->
                Bukkit.getScheduler().runTask(plugin, () ->
                        action.accept(result, exception instanceof Exception ? (Exception) exception : null)
                )
        );
        return this;
    }

    public AsyncResult<T> thenAsync(BiConsumer<T, Exception> action) {
        future.whenComplete((result, exception) ->
                Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
                        action.accept(result, exception instanceof Exception ? (Exception) exception : null)
                )
        );
        return this;
    }

    public AsyncResult<T> onSuccess(Consumer<T> action) {
        future.thenAccept(result ->
                Bukkit.getScheduler().runTask(plugin, () -> action.accept(result))
        );
        return this;
    }

    public AsyncResult<T> onError(Consumer<Exception> action) {
        future.exceptionally(throwable -> {
            Bukkit.getScheduler().runTask(plugin, () ->
                    action.accept(throwable instanceof Exception ? (Exception) throwable : new RuntimeException(throwable))
            );
            return null;
        });
        return this;
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\database\DatabaseException.java

package me.intvk.ivspleef.database;

public class DatabaseException extends RuntimeException {
    public DatabaseException(String message) {
        super(message);
    }

    public DatabaseException(String message, Throwable cause) {
        super(message, cause);
    }
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\database\DatabaseManager.java

// DatabaseManager.java
package me.intvk.ivspleef.database;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import me.intvk.ivspleef.database.models.PlayerDTO;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.IOException;
import java.sql.*;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

public class DatabaseManager implements AutoCloseable {
    private final JavaPlugin plugin;
    private HikariDataSource dataSource;

    public DatabaseManager(JavaPlugin plugin) throws IOException {
        this.plugin = plugin;
        initializeHikari();
    }

    private void initializeHikari() throws IOException {
        File dbFile = new File(plugin.getDataFolder(), "database.db");
        if (!dbFile.exists()) {
            dbFile.getParentFile().mkdirs(); // Создаёт необходимые директории
            if (!dbFile.createNewFile()) {
                throw new IOException("Не удалось создать файл базы данных.");
            }
        }

        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:sqlite:" + dbFile.getPath());
        config.setPoolName("IVSpleef-Pool");
        config.setMaximumPoolSize(10);
        config.setMinimumIdle(5);
        config.setIdleTimeout(300_000); // 5 минут
        config.setMaxLifetime(600_000); // 10 минут
        config.setConnectionTimeout(5_000); // 5 секунд

        try {
            dataSource = new HikariDataSource(config);
            plugin.getLogger().info("Пул соединений базы данных успешно инициализирован.");
            createTables();
        } catch (Exception e) {
            plugin.getLogger().severe("Не удалось инициализировать пул соединений базы данных: " + e.getMessage());
            throw e; // Пробрасываем исключение дальше для обработки
        }
    }

    private void createTables() {
        String playerTableSql = """
            CREATE TABLE IF NOT EXISTS players (
                uuid TEXT PRIMARY KEY,
                language TEXT NOT NULL DEFAULT 'en'
            )""";

        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(playerTableSql)) {
            stmt.executeUpdate();
            plugin.getLogger().info("Таблицы базы данных успешно инициализированы.");
        } catch (SQLException e) {
            plugin.getLogger().severe("Не удалось создать таблицы базы данных: " + e.getMessage());
        }
    }

    public AsyncResult<Void> putPlayer(PlayerDTO playerDTO) {
        return executeAsync(() -> {
            String sql = """
            INSERT INTO players (uuid, language)
            VALUES (?, ?)
            ON CONFLICT (uuid) DO UPDATE SET language = excluded.language
            """;

            try (Connection conn = dataSource.getConnection();
                 PreparedStatement stmt = conn.prepareStatement(sql)) {
                stmt.setString(1, playerDTO.getUuid().toString());
                stmt.setString(2, playerDTO.getLanguage());
                stmt.executeUpdate();
            }
            return null;
        });
    }

    public AsyncResult<Optional<PlayerDTO>> getPlayer(UUID uuid) {
        return executeAsync(() -> {
            String sql = "SELECT * FROM players WHERE uuid = ?";
            try (Connection conn = dataSource.getConnection();
                 PreparedStatement stmt = conn.prepareStatement(sql)) {
                stmt.setString(1, uuid.toString());
                ResultSet rs = stmt.executeQuery();

                if (rs.next()) {
                    return Optional.of(new PlayerDTO(
                            UUID.fromString(rs.getString("uuid")),
                            rs.getString("language")
                    ));
                }
                return Optional.empty();
            }
        });
    }

    private <T> AsyncResult<T> executeAsync(SqlSupplier<T> action) {
        CompletableFuture<T> future = CompletableFuture.supplyAsync(() -> {
            try {
                return action.get();
            } catch (SQLException e) {
                throw new DatabaseException("Ошибка выполнения операции с базой данных", e);
            }
        });
        return new AsyncResult<>(future);
    }

    @Override
    public void close() {
        if (dataSource != null && !dataSource.isClosed()) {
            dataSource.close();
            plugin.getLogger().info("Пул соединений базы данных закрыт.");
        }
    }

    @FunctionalInterface
    private interface SqlSupplier<T> {
        T get() throws SQLException;
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\database\models\PlayerDTO.java

package me.intvk.ivspleef.database.models;

import lombok.Getter;
import lombok.Setter;
import java.util.UUID;

@Getter
@Setter
public class PlayerDTO {
    private UUID uuid;
    private String language;

    public PlayerDTO(UUID uuid, String language) {
        this.uuid = uuid;
        this.language = language;
    }

    @Override
    public String toString() {
        return "PlayerDTO{" +
                "uuid=" + uuid +
                ", language='" + language + '\'' +
                '}';
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\game\Game.java

package me.intvk.ivspleef.game;

import lombok.Getter;
import me.intvk.ivspleef.config.GameConfig;
import me.intvk.ivspleef.modules.ArenaModule;
import me.intvk.ivspleef.modules.GameLogicModule;
import me.intvk.ivspleef.modules.GameLogicStrategy;
import me.intvk.ivspleef.modules.StandardGameLogicStrategy;
import me.intvk.ivspleef.modules.core.ModuleRegistry;
import me.intvk.ivspleef.modules.core.StateModuleManager;
import me.intvk.ivspleef.modules.states.DuelGameLogicStrategy;
import me.intvk.ivspleef.modules.states.FFAGameLogicStrategy;
import me.intvk.ivspleef.modules.states.SoloGameLogicStrategy;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.HashMap;
import java.util.Map;

public class Game {
    @Getter
    private final JavaPlugin plugin;
    private final GameConfig config;
    @Getter
    private final ModuleRegistry moduleRegistry;
    private final StateModuleManager stateModuleManager;
    private GameState currentState;
    private final GameLogicModule logicModule;
    private final ArenaModule arenaModule;

    private final Map<GameState, GameLogicStrategy> strategyMap;

    public Game(JavaPlugin plugin, GameConfig config) {
        this.plugin = plugin;
        this.config = config;
        this.moduleRegistry = new ModuleRegistry(this);
        this.stateModuleManager = new StateModuleManager(this);

        // Извлекаем параметры арены из GameConfig
        Location arenaCenter = config.getCenter();
        int arenaRadius = config.getRadius();
        Material arenaBlockType = config.getArenaBlockType(); // Предполагаем, что вы добавили этот метод в GameConfig

        // Создаем ArenaModule и регистрируем его
        this.arenaModule = new ArenaModule(this, arenaCenter, arenaRadius, arenaBlockType);
        this.moduleRegistry.register(arenaModule);

        // Создаем модуль логики с дефолтной стратегией
        this.logicModule = new GameLogicModule(new StandardGameLogicStrategy());
        this.moduleRegistry.register(logicModule);

        // Изначальное состояние
        this.currentState = GameState.DEACTIVATE;

        // Инициализация карты стратегий
        strategyMap = new HashMap<>();
        strategyMap.put(GameState.SOLO, new SoloGameLogicStrategy());
        strategyMap.put(GameState.DUEL, new DuelGameLogicStrategy());
        strategyMap.put(GameState.FFA, new FFAGameLogicStrategy());
        strategyMap.put(GameState.DEACTIVATE, new StandardGameLogicStrategy());
    }

    // Метод для смены состояния и логики в зависимости от нового состояния
    public void setState(GameState newState) {
        if (this.currentState != newState) {
            this.currentState = newState;
            stateModuleManager.setState(newState);

            // Обновляем стратегию логики в зависимости от состояния через лямбда-выражение
            logicModule.setStrategy(strategyMap.getOrDefault(newState, new StandardGameLogicStrategy()));
        }
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\game\GameManager.java

package me.intvk.ivspleef.game;

import me.intvk.ivspleef.modules.ArenaModule;
import me.intvk.ivspleef.config.GameConfig;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.plugin.java.JavaPlugin;

import java.awt.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class GameManager {
    private final JavaPlugin plugin;
    private final Map<Integer, Game> games;
    private final Random random;

    public GameManager(JavaPlugin plugin) {
        this.plugin = plugin;
        this.games = new HashMap<>();
        this.random = new Random();
    }

    // Генерация уникального шестизначного кода
    private int generateUniqueGameId() {
        int gameId;
        do {
            // Генерируем случайное число от 100000 до 999999
            gameId = 100000 + random.nextInt(900000);
        } while (games.containsKey(gameId)); // Проверяем, что ID уникален
        return gameId;
    }

    // Создание новой игры и арены
    public int createGame(Location arenaCenter, int arenaRadius, Material arenaBlockType) {
        // Используем builder для создания GameConfig с необходимыми параметрами
        GameConfig config = GameConfig.builder()
                .center(arenaCenter)
                .radius(arenaRadius)
                .shape(new Rectangle(arenaRadius * 2, arenaRadius * 2)) // Пример: создаем квадратную арену
                .minPlayers(2) // Минимальное количество игроков
                .maxPlayers(10) // Максимальное количество игроков
                .deathY(arenaCenter.getY() - 1) // Минимальная высота, например, для арены
                .arenaBlockType(arenaBlockType) // Указываем тип блока арены
                .build();

        int gameId = generateUniqueGameId();
        Game newGame = new Game(plugin, config);

        games.put(gameId, newGame);
        return gameId;
    }

    // Получение игры по ID
    public Game getGame(int gameId) {
        return games.get(gameId);
    }

    // Остановка игры по ID
    public void stopGame(int gameId) {
        Game game = games.get(gameId);
        if (game != null) {
            game.setState(GameState.DEACTIVATE); // Деактивируем игру
            game.getModuleRegistry().getModule(ArenaModule.class).onDisable(); // Деактивируем арену
            games.remove(gameId); // Удаляем игру из менеджера
        }
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\game\GameState.java

package me.intvk.ivspleef.game;

public enum GameState {
    DEACTIVATE, // = 0
    SOLO, // = 1
    DUEL, // = 2
    FFA // >2
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\ArenaModule.java

package me.intvk.ivspleef.modules;

import lombok.Getter;
import me.intvk.ivspleef.game.Game;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;

import java.util.HashSet;
import java.util.Set;

@Getter
public class ArenaModule implements GameModule, UpdatableModule {
    private final Game game;
    private final Location center; // центральный блок арены
    private final int radius; // радиус арены
    private final Set<Location> brokenBlocks;
    private final Material arenaBlockType;

    public ArenaModule(Game game, Location center, int radius, Material arenaBlockType) {
        this.game = game;
        this.center = center;
        this.radius = radius;
        this.brokenBlocks = new HashSet<>();
        this.arenaBlockType = arenaBlockType;

        // Запускаем повторное восстановление арены каждые 200 тактов (10 секунд)
        Bukkit.getScheduler().runTaskTimer(game.getPlugin(), this::resetArena, 200L, 200L);
    }

    @Override
    public void onEnable() {
        resetArena();
    }

    @Override
    public void onDisable() {
        brokenBlocks.clear();
    }

    @Override
    public void update() {
        // Обновление не нужно, если checkPlayersBelow переместили в GameLogicModule
    }

    @Override
    public void tick() {
        update();
    }

    @Override
    public Set<Integer> getTaskIds() {
        return Set.of(); // Можно реализовать, если потребуется
    }

    @Override
    public void addTaskId(int taskId) {
        // Реализуйте при необходимости
    }

    @Override
    public void removeTaskId(int taskId) {
        // Реализуйте при необходимости
    }

    public void resetArena() {
        World world = center.getWorld();
        int minX = center.getBlockX() - radius;
        int maxX = center.getBlockX() + radius;
        int y = center.getBlockY() - 1;
        int minZ = center.getBlockZ() - radius;
        int maxZ = center.getBlockZ() + radius;

        for (int x = minX; x <= maxX; x++) {
            for (int z = minZ; z <= maxZ; z++) {
                Location location = new Location(world, x, y, z);
                if (isInArena(location)) {
                    Block block = world.getBlockAt(x, y, z);
                    block.setType(arenaBlockType);
                }
            }
        }
        brokenBlocks.clear();
    }

    public void breakBlock(Location location) {
        if (isInArena(location)) {
            location.getBlock().setType(Material.AIR);
            brokenBlocks.add(location);
        }
    }

    public boolean isInArena(Location location) {
        return location.getWorld() != null && location.getWorld().equals(center.getWorld()) &&
                location.distance(center) <= radius; // проверка по радиусу
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\GameLogicModule.java

package me.intvk.ivspleef.modules;

import lombok.Getter;
import lombok.Setter;
import org.bukkit.entity.Player;

import java.util.HashSet;
import java.util.Set;

@Getter
public class GameLogicModule implements GameModule, UpdatableModule, PlayerAwareModule {
    private final Set<Player> players = new HashSet<>(); // Список игроков
    @Setter
    private GameLogicStrategy strategy;

    public GameLogicModule(GameLogicStrategy strategy) {
        this.strategy = strategy;
    }

    @Override
    public void onEnable() {
        // Логика, которая выполняется при включении модуля
    }

    @Override
    public void onDisable() {
        // Логика, которая выполняется при отключении модуля
        players.clear();
    }

    @Override
    public void update() {
        strategy.update();
    }

    public void onPlayerFall(Player player) {
        strategy.onPlayerFall(player);
    }

    public void addPlayer(Player player) {
        players.add(player);
    }

    public void removePlayer(Player player) {
        players.remove(player);
    }

    public void checkPlayersBelow() {
        // Замените минимальное значение Y на то, которое вам нужно
        int minY = 0;
        for (Player player : players) {
            if (player.getLocation().getY() < minY) {
                // Игрок упал - вызываем соответствующую логику
                onPlayerFall(player);
            }
        }
    }

    @Override
    public void onPlayerJoin(Player player) {
        addPlayer(player);
    }

    @Override
    public void onPlayerLeave(Player player) {
        removePlayer(player);
    }

    @Override
    public void onPlayerMove(Player player) {
        // Логика при перемещении игрока, если требуется
    }

    @Override
    public void onPlayerTick(Player player) {
        // Логика для обработки tick'ов игрока
    }

    @Override
    public Set<Player> getPlayers() {
        return players;
    }

    @Override
    public void tick() {
        update();
        checkPlayersBelow();
        for (Player player : players) {
            onPlayerTick(player); // Вызываем tick для каждого игрока
        }
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\GameLogicStrategy.java

package me.intvk.ivspleef.modules;

import org.bukkit.entity.Player;

public interface GameLogicStrategy {
    void onPlayerFall(Player player);
    void update();
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\GameModule.java

package me.intvk.ivspleef.modules;

public interface GameModule {
    void onEnable();
    void onDisable();
    void update();
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\ListenerModule.java

package me.intvk.ivspleef.modules;

import org.bukkit.event.Listener;

public interface ListenerModule extends Listener {
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\PlayerAwareModule.java

package me.intvk.ivspleef.modules;

import org.bukkit.entity.Player;
import java.util.Set;

public interface PlayerAwareModule {
    void onPlayerJoin(Player player);
    void onPlayerLeave(Player player);
    void onPlayerMove(Player player);
    void onPlayerTick(Player player);
    Set<Player> getPlayers();
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\StandardGameLogicStrategy.java

package me.intvk.ivspleef.modules;

import org.bukkit.entity.Player;

public class StandardGameLogicStrategy implements GameLogicStrategy {
    @Override
    public void onPlayerFall(Player player) {
        // Стандартная логика при падении игрока
    }

    @Override
    public void update() {
        // Стандартное обновление логики
    }
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\UpdatableModule.java

package me.intvk.ivspleef.modules;

import java.util.Set;

public interface UpdatableModule {
    void tick();

    // Метод для получения списка taskId
    Set<Integer> getTaskIds();

    // Метод для добавления нового taskId
    void addTaskId(int taskId);

    // Метод для удаления taskId
    void removeTaskId(int taskId);
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\core\ModuleRegistry.java

package me.intvk.ivspleef.modules.core;

import me.intvk.ivspleef.game.Game;
import me.intvk.ivspleef.modules.GameModule;

import java.util.HashMap;
import java.util.Map;

public class ModuleRegistry {
    private final Game game;
    private final Map<Class<? extends GameModule>, GameModule> modules;

    public ModuleRegistry(Game game) {
        this.game = game;
        this.modules = new HashMap<>();
    }

    public void register(GameModule module) {
        modules.put(module.getClass(), module);
    }

    @SuppressWarnings("unchecked")
    public <T extends GameModule> T getModule(Class<T> moduleClass) {
        return (T) modules.get(moduleClass);
    }

    public void updateAll() {
        modules.values().forEach(GameModule::update);
    }

    public void disableAll() {
        modules.values().forEach(GameModule::onDisable);
    }
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\core\StateModuleManager.java

package me.intvk.ivspleef.modules.core;

import me.intvk.ivspleef.game.Game;
import me.intvk.ivspleef.game.GameState;
import me.intvk.ivspleef.modules.GameModule;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class StateModuleManager {
    private final Game game;
    private final Map<GameState, Set<Class<? extends GameModule>>> stateModules;
    private GameState currentState;

    public StateModuleManager(Game game) {
        this.game = game;
        this.stateModules = new HashMap<>();
        this.currentState = GameState.DEACTIVATE;

        // Initialize sets for each state
        for (GameState state : GameState.values()) {
            stateModules.put(state, new HashSet<>());
        }
    }

    public void registerModuleForState(GameState state, Class<? extends GameModule> moduleClass) {
        stateModules.get(state).add(moduleClass);
    }

    public void setState(GameState newState) {
        if (currentState == newState) return;

        // Disable current state modules
        disableCurrentStateModules();

        currentState = newState;

        // Enable new state modules
        enableNewStateModules();
    }

    private void disableCurrentStateModules() {
        ModuleRegistry registry = game.getModuleRegistry();
        Set<Class<? extends GameModule>> currentModules = stateModules.get(currentState);

        for (Class<? extends GameModule> moduleClass : currentModules) {
            GameModule module = registry.getModule(moduleClass);
            if (module != null) {
                module.onDisable();
            }
        }
    }

    private void enableNewStateModules() {
        ModuleRegistry registry = game.getModuleRegistry();
        Set<Class<? extends GameModule>> newModules = stateModules.get(currentState);

        for (Class<? extends GameModule> moduleClass : newModules) {
            GameModule module = registry.getModule(moduleClass);
            if (module != null) {
                module.onEnable();
            }
        }
    }
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\states\DuelGameLogicStrategy.java

package me.intvk.ivspleef.modules.states;

import me.intvk.ivspleef.modules.GameLogicStrategy;
import org.bukkit.entity.Player;

public class DuelGameLogicStrategy implements GameLogicStrategy {
    @Override
    public void onPlayerFall(Player player) {
        // Логика для дуэлей при падении игрока
    }

    @Override
    public void update() {
        // Логика для обновления дуэлей
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\states\FFAGameLogicStrategy.java

package me.intvk.ivspleef.modules.states;

import me.intvk.ivspleef.modules.GameLogicStrategy;
import org.bukkit.entity.Player;

public class FFAGameLogicStrategy implements GameLogicStrategy {
    @Override
    public void onPlayerFall(Player player) {

    }

    @Override
    public void update() {

    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\modules\states\SoloGameLogicStrategy.java

package me.intvk.ivspleef.modules.states;

import me.intvk.ivspleef.modules.GameLogicStrategy;
import org.bukkit.entity.Player;

public class SoloGameLogicStrategy implements GameLogicStrategy {
    @Override
    public void onPlayerFall(Player player) {

    }

    @Override
    public void update() {

    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\FillUtils.java

package me.intvk.ivspleef.utils;

import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.scheduler.BukkitRunnable;

import java.util.HashSet;
import java.util.Set;

@UtilityClass
public class FillUtils {

    // Метод для заполнения квадратной области
    public Set<Location> createSquareFill(Location center, int radius) {
        Set<Location> filledLocations = new HashSet<>();
        World world = center.getWorld();
        int centerX = center.getBlockX();
        int centerZ = center.getBlockZ();
        int y = center.getBlockY() - 1; // Уменьшаем высоту на 1

        for (int x = centerX - radius; x <= centerX + radius; x++) {
            for (int z = centerZ - radius; z <= centerZ + radius; z++) {
                filledLocations.add(new Location(world, x, y, z));
            }
        }
        return filledLocations;
    }

    // Метод для заполнения круглой области
    public Set<Location> createCircleFill(Location center, int radius) {
        Set<Location> filledLocations = new HashSet<>();
        World world = center.getWorld();
        int centerX = center.getBlockX();
        int centerZ = center.getBlockZ();
        int y = center.getBlockY() - 1; // Уменьшаем высоту на 1

        for (int x = -radius; x <= radius; x++) {
            for (int z = -radius; z <= radius; z++) {
                if (x * x + z * z <= radius * radius) { // Проверка на круг
                    filledLocations.add(new Location(world, centerX + x, y, centerZ + z));
                }
            }
        }
        return filledLocations;
    }

    // Метод для заполнения области вокруг игрока с учетом радиуса арены
    public Set<Location> fillAreaAroundPlayerWithMaterial(Location arenaCenter, int arenaRadius, Location playerLocation, int fillRadius, Material material) {
        Set<Location> fillLocations = new HashSet<>();
        World world = arenaCenter.getWorld();
        int y = playerLocation.getBlockY() - 1; // Уменьшаем высоту на 1

        // Определяем границы для заполнения
        int minX = playerLocation.getBlockX() - fillRadius;
        int maxX = playerLocation.getBlockX() + fillRadius;
        int minZ = playerLocation.getBlockZ() - fillRadius;
        int maxZ = playerLocation.getBlockZ() + fillRadius;

        // Заполняем локации в пределах радиуса заполнения
        for (int x = minX; x <= maxX; x++) {
            for (int z = minZ; z <= maxZ; z++) {
                Location currentLocation = new Location(world, x, y, z);
                // Проверяем, находится ли текущая локация в пределах арены
                if (isLocationWithinArena(arenaCenter, arenaRadius, currentLocation)) {
                    fillLocations.add(currentLocation);
                }
            }
        }

        return fillLocations; // Возвращаем заполненные локации
    }

    // Проверка, находится ли локация внутри арены
    private boolean isLocationWithinArena(Location arenaCenter, int arenaRadius, Location location) {
        int deltaX = arenaCenter.getBlockX() - location.getBlockX();
        int deltaZ = arenaCenter.getBlockZ() - location.getBlockZ();
        return (deltaX * deltaX + deltaZ * deltaZ) <= (arenaRadius * arenaRadius);
    }

    // Метод для заполнения материалом на последнем тике
    public void fillBlocksWithMaterialAfterDelay(Set<Location> locations, Material material) {
        new BukkitRunnable() {
            @Override
            public void run() {
                fillBlocksWithMaterial(locations, material);
            }
        }.runTaskLater(Bukkit.getPluginManager().getPlugin("ivSpleef"), 19);
    }

    // Метод для заполнения блоков материалом
    public void fillBlocksWithMaterial(Set<Location> locations, Material material) {
        for (Location location : locations) {
            Block block = location.getBlock();
            block.setType(material); // Устанавливаем материал блока
        }
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\PlacehoderAPI.java

package me.intvk.ivspleef.utils;

public class PlacehoderAPI {
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\TeleportUtils.java

package me.intvk.ivspleef.utils;

import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;

import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

@UtilityClass
public class TeleportUtils {

    private static final Random RANDOM = new Random();
    private static final int MAX_ATTEMPTS = 25;
    private static final int MIN_DISTANCE = 6;
    private static final int Y_LEVEL = 5;

    public Location getPossibleTeleport(Location center, int radius, Set<UUID> playerUUIDs) {
        Set<Location> locations = getLocations(playerUUIDs);

        for (int i = 0; i < MAX_ATTEMPTS; i++) {
            Location randomLocation = getRandomLocation(center, radius);
            if (isLocationFarEnough(randomLocation, locations, MIN_DISTANCE)) {
                return randomLocation;
            }
        }
        return null; // Возвращаем null, если подходящая точка не найдена за 25 попыток
    }

    private Location getRandomLocation(Location center, int radius) {
        double xOffset = RANDOM.nextInt(2 * radius + 1) - radius;
        double zOffset = RANDOM.nextInt(2 * radius + 1) - radius;
        return new Location(center.getWorld(), center.getX() + xOffset, Y_LEVEL, center.getZ() + zOffset);
    }

    private boolean isLocationFarEnough(Location location, Set<Location> locations, int minDistance) {
        return locations.stream().allMatch(otherLocation -> otherLocation.distance(location) >= minDistance);
    }

    public Set<Location> getLocations(Set<UUID> playerUUIDs) {
        return playerUUIDs.stream()
                .map(Bukkit::getPlayer)                        // Получаем Player по UUID
                .filter(Objects::nonNull)                      // Фильтруем, чтобы исключить null значений
                .map(Player::getLocation)                      // Получаем Location у Player
                .peek(location -> location.setY(Y_LEVEL))      // Устанавливаем y = 5
                .collect(Collectors.toSet());                 // Собираем в Set<Location>
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\config\CustomConfig.java

package me.intvk.ivspleef.utils.config;

import lombok.Getter;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CustomConfig {

    private final JavaPlugin plugin;

    @Getter
    private File file;
    private File filepath;
    private final String name;
    private final String path;
    private FileConfiguration config;

    public CustomConfig(JavaPlugin plugin, String pathname, String filename) {
        this.plugin = plugin;

        this.path = pathname;
        this.name = filename;
        this.filepath = new File(plugin.getDataFolder(), path);
        this.file = new File(filepath, name);
    }

    public void saveDefaultConfig() {
        if (!file.exists())
            this.plugin.saveResource(path + name, false);
    }

    public FileConfiguration getConfig() {
        if (!filepath.exists())
            this.saveDefaultConfig();
        if (config == null)
            this.reloadConfig();
        return config;
    }

    public void reloadConfig() {
        if (filepath == null)
            filepath = new File(plugin.getDataFolder(), path);
        if (file == null)
            file = new File(filepath, name);
        config = YamlConfiguration.loadConfiguration(file);
        InputStream stream = plugin.getResource(name);
        if (stream != null) {
            YamlConfiguration YmlFile = YamlConfiguration.loadConfiguration(new InputStreamReader(stream));
            config.setDefaults(YmlFile);
        }
    }

    public void saveConfig() {
        try {
            config.save(file);
        } catch (Throwable t) {
            plugin.getLogger().severe("Error saving configuration file: " + file.getName() + ". Please try again.");
        }
    }

    public void backupConfig() {
        SimpleDateFormat sdf = new SimpleDateFormat("dd_MM_yyyy_HH_mm_ss");
        String backupFileName = name.replace(".yml", "") + "_" + sdf.format(new Date()) + ".yml";
        File backupFile = new File(filepath, backupFileName);
        try {
            Files.copy(file.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            plugin.getLogger().info("Backup created: " + backupFileName);
        } catch (IOException e) {
            plugin.getLogger().severe("Error creating backup for configuration file: " + e.getMessage());
        }
    }

    public static void save(CustomConfig config) {
        config.saveConfig();
        config.reloadConfig();
    }

}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\l18n\LocaleContainer.java

package me.intvk.ivspleef.utils.l18n;

import me.intvk.ivspleef.utils.config.CustomConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.util.*;

public class LocaleContainer {
    private final JavaPlugin plugin;
    private Map<String, Object> messages;
    private final CustomConfig config;

    public LocaleContainer(JavaPlugin plugin, File file) throws LocalizationException {
        this.plugin = plugin;

        this.config = new CustomConfig(plugin, "messages/", file.getName());
        this.messages = new HashMap<>();
        loadMessages();
    }

    private void loadMessages() throws LocalizationException {
        try {
            FileConfiguration configData = config.getConfig();
            messages.clear(); // Очищаем старые сообщения

            for (String key : configData.getKeys(true)) {
                Object value = configData.get(key);
                if (value instanceof String || value instanceof List<?>) {
                    messages.put(key, value);
                }
            }
        } catch (Exception e) {
            config.backupConfig(); // Создаем бэкап при ошибке
            throw new LocalizationException("Failed to load localization from " + config.getFile().getName(), e);
        }
    }

    public void reload() throws LocalizationException {
        Map<String, Object> oldMessages = new HashMap<>(messages);
        try {
            loadMessages();
        } catch (Exception e) {
            messages = oldMessages;
            throw e;
        }
    }

    public String getMessage(String key, String locale, Map<String, String> placeholders, MessageCache cache) {
        long startTime = System.currentTimeMillis();

        String cacheKey = PlaceholderUtil.generateCacheKey(locale, key, placeholders);
        String cachedMessage = cache.get(cacheKey);
        if (cachedMessage != null) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            plugin.getLogger().info("[" + locale + "] Message retrieved from cache in " + elapsedTime + " ms: " + cachedMessage);
            return cachedMessage;
        }

        Object value = messages.get(key);
        if (!(value instanceof String)) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            plugin.getLogger().info("[" + locale + "] Message not found. Key: " + key + ". Time taken: " + elapsedTime + " ms");
            return key;
        }

        String message = (String) value;
        if (placeholders != null && !placeholders.isEmpty()) {
            message = PlaceholderUtil.replacePlaceholders(message, placeholders);
        }

        cache.put(cacheKey, message);

        long elapsedTime = System.currentTimeMillis() - startTime;
        plugin.getLogger().info("[" + locale + "] Message generated and cached in " + elapsedTime + " ms: " + message);
        return message;
    }


    public List<String> getMessageList(String key, Map<String, String> placeholders) {
        Object value = messages.get(key);
        if (!(value instanceof List)) {
            return List.of(key);
        }

        List<String> messageList = new ArrayList<>();
        ((List<?>) value).forEach(line -> {
            if (line instanceof String processedLine) {
                if (placeholders != null && !placeholders.isEmpty()) {
                    processedLine = PlaceholderUtil.replacePlaceholders(processedLine, placeholders);
                }
                messageList.add(processedLine);
            }
        });
        return messageList;
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\l18n\LocalizationException.java

package me.intvk.ivspleef.utils.l18n;

import org.bukkit.configuration.file.FileConfiguration;

public class LocalizationException extends Exception {
    public LocalizationException(String message) {
        super(message);
    }

    public LocalizationException(String message, Throwable cause) {
        super(message, cause);
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\l18n\LocalizationManager.java

package me.intvk.ivspleef.utils.l18n;

import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.entity.Player;

public class LocalizationManager {

    private static final String FOLDER = "messages/";

    private final Map<String, LocaleContainer> locales;
    private final JavaPlugin plugin;
    private final MessageCache messageCache;
    private String defaultLocale;

    public LocalizationManager(JavaPlugin plugin) {
        this.plugin = plugin;
        this.locales = new ConcurrentHashMap<>();
        this.messageCache = new MessageCache();
        this.defaultLocale = "en_US";
        loadAllLocales();
    }

    private void loadAllLocales() {
        File localeFolder = new File(plugin.getDataFolder(), FOLDER);
        if (!localeFolder.exists() && !localeFolder.mkdirs()) {
            plugin.getLogger().severe("Could not create localization folder: " + FOLDER);
            return;
        }

        // Проверяем существование файлов перед их сохранением
        File enFile = new File(localeFolder, "en_US.yml");
        File ruFile = new File(localeFolder, "ru_RU.yml");

        if (!enFile.exists()) {
            plugin.saveResource(FOLDER + "en_US.yml", false);
        }

        if (!ruFile.exists()) {
            plugin.saveResource(FOLDER + "ru_RU.yml", false);
        }

        File[] files = localeFolder.listFiles((dir, name) -> name.endsWith(".yml"));
        if (files != null) {
            for (File file : files) {
                loadLocale(file);
            }
        } else {
            plugin.getLogger().warning("No localization files found in: " + FOLDER);
        }
    }


    private void loadLocale(File file) {
        String fileName = file.getName();
        String localeKey = fileName.replace(".yml", "");

        try {
            LocaleContainer container = new LocaleContainer(plugin, file);
            locales.put(localeKey, container);
            messageCache.invalidateAll();
            plugin.getLogger().info("Loaded locale: " + localeKey);
        } catch (Exception e) {
            plugin.getLogger().severe("Error loading locale " + fileName + ": " + e.getMessage());
        }
    }

    public String getMessage(String key, String locale, Player player, Object... args) {
        if (args.length % 2 != 0) {
            throw new IllegalArgumentException("Placeholders must be in pairs (key1, value1, key2, value2, ...)");
        }

        // Use requested locale or fall back to default
        String effectiveLocale = locale != null ? locale : defaultLocale;
        LocaleContainer container = locales.getOrDefault(effectiveLocale, locales.get(defaultLocale));
        if (container == null) {
            return key;
        }

        Map<String, String> placeholders = null;
        if (args.length > 0) {
            placeholders = new HashMap<>();
            for (int i = 0; i < args.length; i += 2) {
                placeholders.put(args[i].toString(), args[i + 1].toString());
            }
        }

        // Pass the effective locale to getMessage for proper cache key generation
        String message = container.getMessage(key, effectiveLocale, placeholders, messageCache);
        return player != null ? PlaceholderAPI.setPlaceholders(player, message) : message;
    }

    public List<String> getMessageList(String key, String locale, Player player, Object... args) {
        if (args.length % 2 != 0) {
            throw new IllegalArgumentException("Placeholders must be in pairs (key1, value1, key2, value2, ...)");
        }

        LocaleContainer container = locales.getOrDefault(locale, locales.get(defaultLocale));
        if (container == null) {
            return List.of(key);
        }

        Map<String, String> placeholders = null;
        if (args.length > 0) {
            placeholders = new HashMap<>();
            for (int i = 0; i < args.length; i += 2) {
                placeholders.put(args[i].toString(), args[i + 1].toString());
            }
        }

        List<String> messages = container.getMessageList(key, placeholders);
        if (player != null) {
            return messages.stream()
                    .map(message -> PlaceholderAPI.setPlaceholders(player, message))
                    .toList();
        }
        return messages;
    }

    public String getMessage(String key) {
        return getMessage(key, defaultLocale, null);
    }

    public String getMessage(String key, String locale) {
        return getMessage(key, locale, null);
    }

    public List<String> getMessageList(String key) {
        return getMessageList(key, defaultLocale, null);
    }

    public void reloadLocales() {
        messageCache.invalidateAll();
        locales.clear();
        loadAllLocales();
    }

    public Set<String> getAvailableLocales() {
        return Collections.unmodifiableSet(locales.keySet());
    }

    public void setDefaultLocale(String locale) {
        if (locales.containsKey(locale)) {
            this.defaultLocale = locale;
            messageCache.invalidateAll();
        } else {
            plugin.getLogger().warning("Locale not found: " + locale);
        }
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\l18n\MessageCache.java

package me.intvk.ivspleef.utils.l18n;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.TimeUnit;

public class MessageCache {
    private final Cache<String, String> cache;

    public MessageCache() {
        this.cache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .build();
    }

    public String get(String key) {
        return cache.getIfPresent(key);
    }

    public void put(String key, String message) {
        cache.put(key, message);
    }

    public void invalidateAll() {
        cache.invalidateAll();
    }
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\l18n\PlaceholderUtil.java

package me.intvk.ivspleef.utils.l18n;

import java.util.Map;
import java.util.regex.Pattern;

public class PlaceholderUtil {
    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("%([^%]+)%");

    public static String replacePlaceholders(String message, Map<String, String> placeholders) {
        if (message == null || placeholders == null || placeholders.isEmpty()) {
            return message;
        }
        return PLACEHOLDER_PATTERN.matcher(message).replaceAll(matchResult -> {
            String placeholder = matchResult.group(1);
            return placeholders.getOrDefault(placeholder, matchResult.group());
        });
    }

    public static String generateCacheKey(String locale, String key, Map<String, String> placeholders) {
        StringBuilder sb = new StringBuilder(locale)
                .append(":")
                .append(key);
        if (placeholders != null) {
            placeholders.entrySet().stream()
                    .sorted(Map.Entry.comparingByKey()) // Ensure consistent order
                    .forEach(entry -> sb.append(":")
                            .append(entry.getKey())
                            .append("=")
                            .append(entry.getValue()));
        }
        return sb.toString();
    }
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\player\SPlayer.java

package me.intvk.ivspleef.utils.player;

import lombok.Getter;
import lombok.Setter;
import me.intvk.ivspleef.database.models.PlayerDTO;
import java.time.LocalDateTime;
import java.util.UUID;

@Getter
public class SPlayer {
    private final UUID uuid;
    @Setter
    private String language;

    public SPlayer(PlayerDTO dto) {
        this.uuid = dto.getUuid();
        this.language = dto.getLanguage();
    }

    public PlayerDTO toDTO() {
        return new PlayerDTO(uuid, language);
    }
}
// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\player\SPlayerCache.java

package me.intvk.ivspleef.utils.player;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class SPlayerCache {
    private final Cache<UUID, SPlayer> playerCache;

    public SPlayerCache() {
        this.playerCache = CacheBuilder.newBuilder()
                .maximumSize(1000) // Максимальный размер кеша
                .expireAfterWrite(30, TimeUnit.MINUTES) // Установка времени жизни после записи
                .expireAfterAccess(15, TimeUnit.MINUTES) // Установка времени жизни после доступа
                .build();
    }

    public void putPlayer(UUID uuid, SPlayer player) {
        playerCache.put(uuid, player);
    }

    public Optional<SPlayer> getPlayer(UUID uuid) {
        SPlayer player = playerCache.getIfPresent(uuid);
        return Optional.ofNullable(player);
    }

    public void invalidatePlayer(UUID uuid) {
        playerCache.invalidate(uuid);
    }
}

// C:/Users/kloch/IdeaProjects/ivSpleef/src/main/java/me/intvk\ivspleef\utils\player\SPlayerManager.java

// SPlayerManager.java
package me.intvk.ivspleef.utils.player;

import lombok.Getter;
import me.intvk.ivspleef.database.DatabaseManager;
import me.intvk.ivspleef.database.models.PlayerDTO;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.Optional;
import java.util.UUID;
import java.util.logging.Level;

public class SPlayerManager {
    private final JavaPlugin plugin;
    private final DatabaseManager databaseManager;

    @Getter
    private final SPlayerCache playerCache;

    public SPlayerManager(JavaPlugin plugin, DatabaseManager databaseManager) {
        this.plugin = plugin;
        this.databaseManager = databaseManager;
        this.playerCache = new SPlayerCache();
    }

    public void loadPlayer(Player player) {
        UUID uuid = player.getUniqueId();
        databaseManager.getPlayer(uuid).thenSync((playerOpt, ex) -> {
            if (ex != null) {
                plugin.getLogger().log(Level.SEVERE, "Failed to load player data", ex);
                return;
            }

            PlayerDTO dto = playerOpt.orElse(new PlayerDTO(uuid, "en_US"));
            SPlayer sPlayer = new SPlayer(dto);
            playerCache.putPlayer(uuid, sPlayer);
            databaseManager.putPlayer(dto);
        });
    }

    public Optional<SPlayer> getPlayer(UUID uuid) {
        long startTime = System.currentTimeMillis(); // Начало отсчета времени

        Optional<SPlayer> cachedPlayer = playerCache.getPlayer(uuid);
        if (cachedPlayer.isPresent()) {
            long elapsedTime = System.currentTimeMillis() - startTime; // Время выполнения
            plugin.getLogger().info("Player retrieved from cache in " + elapsedTime + " ms: " + uuid);
            return cachedPlayer;
        }

        // Если игрока нет в кеше, загружаем его из базы данных
        databaseManager.getPlayer(uuid).thenSync((playerOpt, ex) -> {
            if (ex != null) {
                plugin.getLogger().log(Level.SEVERE, "Error fetching player from database", ex);
                return;
            }

            playerOpt.ifPresent(dto -> {
                SPlayer sPlayer = new SPlayer(dto);
                playerCache.putPlayer(uuid, sPlayer);

                long elapsedTime = System.currentTimeMillis() - startTime; // Время выполнения
                plugin.getLogger().info("Player retrieved from database and cached in " + elapsedTime + " ms: " + uuid);
            });
        });

        return Optional.empty(); // Пока возвращаем пустое значение, если игрок не был найден в кеше
    }

    public void updatePlayerLanguage(UUID uuid, String language) {
        getPlayer(uuid).ifPresent(player -> {
            player.setLanguage(language);
            databaseManager.putPlayer(player.toDTO());
        });
    }
}

Улучши весь код. Реализуй единый стиль и каждый компонент