// /server_modules/gameInstance.js
const { v4: uuidv4 } = require('uuid'); // Убедитесь, что uuidv4 установлен: npm install uuid
// Removed the self-import: const GameInstance = require('./gameInstance'); // Не нужно импортировать себя
const gameData = require('./data'); // Нужен для getAvailablePvPGamesListForClient и данных персонажей
const GAME_CONFIG = require('./config'); // Нужен для GAME_CONFIG.PLAYER_ID и других констант
const serverGameLogic = require('./gameLogic'); // Подключаем игровую логику

class GameInstance {
    // ДОБАВЛЕН АРГУМЕНТ gameManager В КОНСТРУКТОР
    constructor(gameId, io, mode = 'ai', gameManager) {
        this.id = gameId;
        this.io = io; // Ссылка на Socket.IO сервер для широковещательных рассылок
        this.mode = mode; // 'ai' или 'pvp'
        // players теперь { socket.id: { id: 'player'/'opponent', socket: socketObject, chosenCharacterKey?: 'elena'/'almagest', identifier: userId|socketId } }
        this.players = {};
        // playerSockets { 'player': socketObject, 'opponent': socketObject } - для быстрого доступа к сокету по роли
        // Этот маппинг хранит *текущие* сокеты. При переподключении ссылка обновляется в GameManager.
        this.playerSockets = {};
        this.playerCount = 0; // Количество подключенных сокетов (активных игроков) в игре

        this.gameState = null; // Хранит текущее состояние игры (HP, ресурсы, эффекты, чей ход и т.д.)
        this.aiOpponent = (mode === 'ai'); // Флаг, является ли оппонент AI

        this.logBuffer = []; // Буфер для сообщений лога боя (ожидающих отправки клиентам)

        // Ключи персонажей для слотов 'player' и 'opponent' в текущей игре
        // Определяются один раз при инициализации gameState
        this.playerCharacterKey = null;    // Ключ персонажа в слоте 'player' (Елена или Альмагест)
        this.opponentCharacterKey = null; // Ключ персонажа в слоте 'opponent' (Балард, Елена или Альмагест)

        this.ownerIdentifier = null; // Идентификатор создателя игры (userId или socketId)

        // СОХРАНЯЕМ ССЫЛКУ НА GAMEMANAGER
        this.gameManager = gameManager;
        if (!this.gameManager || typeof this.gameManager._cleanupGame !== 'function') {
            console.error(`[Game ${this.id}] CRITICAL ERROR: GameInstance created without valid GameManager reference! Cleanup will fail.`);
            // Если GameManager не передан или не имеет метода _cleanupGame, логика очистки не будет работать.
            // В продакшене, возможно, стоит выбрасывать ошибку или завершать процесс.
        }
    }

    /**
     * Добавляет игрока в игру.
     * Вызывается GameManager при создании новой игры или присоединении второго игрока.
     * @param {object} socket - Объект сокета игрока.
     * @param {string} [chosenCharacterKey='elena'] - Выбранный игроком персонаж (только для первого игрока в PvP).
     * @param {string|number} identifier - Идентификатор пользователя (userId или socketId).
     * @returns {boolean} true, если игрок успешно добавлен, иначе false.
     */
    addPlayer(socket, chosenCharacterKey = 'elena', identifier) { // <--- ДОБАВЛЕН identifier
        // Проверка, не пытается ли сокет, который уже есть в этой игре, добавиться снова
        if (this.players[socket.id]) {
            console.warn(`[Game ${this.id}] Игрок ${identifier} (сокет ${socket.id}) попытался присоединиться к игре, в которой его сокет уже зарегистрирован.`);
            // Возможно, это быстрый повторный запрос.
            // Отправляем gameError и возвращаем false.
            socket.emit('gameError', { message: 'Ваш сокет уже зарегистрирован в этой игре.' });
            return false;
        }
        // Проверка, не пытается ли пользователь, который уже есть в этой игре (но с другим сокетом), добавиться через addPlayer
        // Этого не должно случаться, если GameManager.handleRequestGameState корректно обновляет сокет для существующего identifier.
        // AddPlayer вызывается только для *нового* игрока в GameInstance.
        const existingPlayerByIdentifier = Object.values(this.players).find(p => p.identifier === identifier);
        if (existingPlayerByIdentifier) {
            console.warn(`[Game ${this.id}] Игрок с идентификатором ${identifier} (${socket.id}) уже находится в игре под сокетом ${existingPlayerByIdentifier.socket.id}. Игнорируем добавление.`);
            socket.emit('gameError', { message: 'Вы уже находитесь в этой игре под другим подключением.' });
            return false;
        }


        if (this.playerCount >= 2) {
            socket.emit('gameError', { message: 'Эта игра уже заполнена.' });
            return false;
        }

        let assignedPlayerId; // 'player' или 'opponent' (технический ID слота в рамках этой игры)
        let actualCharacterKey; // 'elena', 'almagest' (balard только для AI-опонента)

        if (this.mode === 'ai') {
            if (this.playerCount > 0) { // AI игра только для одного игрока
                socket.emit('gameError', { message: 'Нельзя присоединиться к AI игре как второй игрок.' });
                return false;
            }
            assignedPlayerId = GAME_CONFIG.PLAYER_ID;
            actualCharacterKey = 'elena'; // В AI режиме игрок всегда Елена
            this.ownerIdentifier = identifier; // Сохраняем идентификатор создателя AI игры

        } else { // PvP режим
            if (this.playerCount === 0) { // Первый игрок в PvP
                assignedPlayerId = GAME_CONFIG.PLAYER_ID;
                actualCharacterKey = (chosenCharacterKey === 'almagest') ? 'almagest' : 'elena';
                this.ownerIdentifier = identifier; // Сохраняем идентификатор создателя PvP игры
            } else { // Segundo jugador en PvP
                assignedPlayerId = GAME_CONFIG.OPPONENT_ID;
                const firstPlayerInfo = Object.values(this.players).find(p => p.id === GAME_CONFIG.PLAYER_ID); // Находим первого игрока по его роли
                // Segundo jugador recibe automáticamente un personaje "espejo" del primero.
                actualCharacterKey = (firstPlayerInfo?.chosenCharacterKey === 'elena') ? 'almagest' : 'elena';
                // El identificador de ownerIdentifier sigue siendo el del creador de la partida (el primer jugador).
            }
        }

        // Добавляем информацию об игроке в map players, ключом является socket.id
        this.players[socket.id] = {
            id: assignedPlayerId, // Технический ID слота ('player' или 'opponent')
            socket: socket, // Объект сокета
            chosenCharacterKey: actualCharacterKey, // Выбранный/назначенный персонаж
            identifier: identifier // <--- ДОБАВЛЕНО: Идентификатор пользователя
        };
        this.playerSockets[assignedPlayerId] = socket; // Связываем роль в игре с объектом сокета
        this.playerCount++; // Увеличиваем количество активных сокетов

        socket.join(this.id); // Присоединяем сокет к комнате игры Socket.IO

        const characterData = this._getCharacterBaseData(actualCharacterKey);
        console.log(`[Game ${this.id}] Игрок ${identifier} (сокет ${socket.id}) (${characterData?.name || 'Неизвестно'}) присоединился как ${assignedPlayerId} (персонаж: ${actualCharacterKey}). Всего игроков: ${this.playerCount}. Owner: ${this.ownerIdentifier || 'N/A'}`);

        // Логика старта игры перемещена в GameManager после вызова addPlayer.
        // Логика уведомления "Ожидание оппонента" тоже перемещена в GameManager.

        return true; // Игрок успешно добавлен
    }

    /**
     * Удаляет игрока из игры (например, при дисконнекте сокета).
     * Вызывается GameManager при событии 'disconnect'.
     * @param {string} socketId - ID сокета игрока, который отключился.
     */
    removePlayer(socketId) { // <--- Принимает socketId
        const playerInfo = this.players[socketId]; // Находим информацию об игроке по socketId
        if (playerInfo) {
            const playerRole = playerInfo.id; // 'player' or 'opponent'
            const identifierOfLeavingPlayer = playerInfo.identifier; // Идентификатор уходящего игрока
            const characterKeyOfLeavingPlayer = playerInfo.chosenCharacterKey;

            const characterData = this._getCharacterBaseData(characterKeyOfLeavingPlayer);
            console.log(`[Game ${this.id}] Игрок ${identifierOfLeavingPlayer} (сокет: ${socketId}, роль: ${playerRole}, персонаж: ${characterKeyOfLeavingPlayer || 'N/A'}) покинул игру.`);

            // Удаляем сокет из комнаты Socket.IO (если сокет объект еще валиден)
            if (playerInfo.socket) {
                // Проверяем, что это именно тот сокет, который отключился,
                // чтобы не пытаться удалить сокет, если он уже был обновлен на новый при переподключении.
                const actualSocket = this.io.sockets.sockets.get(socketId);
                if (actualSocket && actualSocket.id === socketId) { // Убедимся, что это тот же сокет
                    actualSocket.leave(this.id);
                } else if (playerInfo.socket.id === socketId) { // Если сокет объект в playerInfo совпадает с socketId
                    // Это может произойти, если Socket.IO уже удалил сокет из io.sockets.sockets,
                    // но ссылка в playerInfo еще существует.
                    // Попробуем выполнить leave на старом объекте сокета (может не работать, но безопасно)
                    try { playerInfo.socket.leave(this.id); } catch(e) { console.warn(`[Game ${this.id}] Error leaving room for old socket ${socketId}: ${e.message}`); }
                }
            }

            // Удаляем запись об игроке из map players по socketId
            delete this.players[socketId];
            this.playerCount--; // Уменьшаем количество активных сокетов (сокетов, подключенных к игре)

            // Удаляем ссылку на сокет из playerSockets по роли, ТОЛЬКО если она указывала на отключившийся сокет
            if (this.playerSockets[playerRole] && this.playerSockets[playerRole].id === socketId) {
                delete this.playerSockets[playerRole];
            }

            // Логика завершения игры при дисконнекте полностью в GameManager.handleDisconnect.
            // GameManager проверит playerCount после вызова removePlayer и примет решение.
            // GameManager также управляет userIdentifierToGameId и pendingPvPGames.

            // Если игра была активна и еще не окончена, GameManager вызовет endGameDueToDisconnect - Moved
        } else {
            console.warn(`[Game ${this.id}] removePlayer called for unknown socketId ${socketId}.`);
        }
    }

    /**
     * Инициализирует или перезапускает состояние игры.
     * Вызывается GameManager, когда игра полностью готова (PvP 2 игрока, AI 1 игрок) ИЛИ
     * для частичной инициализации PvP игры с 1 игроком.
     * @returns {boolean} true, если оба бойца определены и gameState создан корректно, иначе false.
     */
    initializeGame() {
        console.log(`[Game ${this.id}] Initializing game state. Mode: ${this.mode}. Current PlayerCount: ${this.playerCount}.`);

        // Определяем ключи персонажей для слотов 'player' и 'opponent' в этой игре
        // В AI режиме всегда Елена vs Балард
        if (this.mode === 'ai' && this.playerCount === 1) {
            this.playerCharacterKey = 'elena';
            this.opponentCharacterKey = 'balard';
        } else if (this.mode === 'pvp' && this.playerCount === 2) { // PvP режим с 2 игроками
            // Находим информацию об игроках по их ролям
            const player1Info = Object.values(this.players).find(p => p.id === GAME_CONFIG.PLAYER_ID);
            const player2Info = Object.values(this.players).find(p => p.id === GAME_CONFIG.OPPONENT_ID);

            // Первый игрок (в слоте PLAYER_ID) сохраняет свой выбранный персонаж
            this.playerCharacterKey = player1Info?.chosenCharacterKey || 'elena'; // Фоллбэк

            // Второй игрок (в слоте OPPONENT_ID) имеет зеркального персонажа
            const expectedOpponentKey = (this.playerCharacterKey === 'elena') ? 'almagest' : 'elena';
            // Убеждаемся, что у второго игрока действительно назначен этот персонаж.
            // В addPlayer мы уже стараемся назначить правильно, но тут финальное подтверждение.
            if (player2Info && player2Info.chosenCharacterKey !== expectedOpponentKey) {
                console.warn(`[Game ${this.id}] initializeGame: Expected opponent character ${expectedOpponentKey} but player2Info had ${player2Info.chosenCharacterKey}.`);
                // Не меняем chosenCharacterKey здесь, это данные игрока.
                // Просто используем expectedOpponentKey для определения персонажа в слоте gameState.
            }
            this.opponentCharacterKey = expectedOpponentKey; // Устанавливаем ключ для слота

        } else if (this.mode === 'pvp' && this.playerCount === 1) {
            // PvP игра ожидает второго игрока. Инициализируем gameState только для Player 1.
            const player1Info = Object.values(this.players).find(p => p.id === GAME_CONFIG.PLAYER_ID);
            this.playerCharacterKey = player1Info?.chosenCharacterKey || 'elena';
            this.opponentCharacterKey = null; // Оппонент еще не определен

        } else {
            console.error(`[Game ${this.id}] Unexpected state for initialization! Mode: ${this.mode}, PlayerCount: ${this.playerCount}. Cannot initialize gameState.`);
            this.gameState = null; // Не создаем gameState
            return false; // <-- Возвращаем false при ошибке
        }

        console.log(`[Game ${this.id}] Finalizing characters for gameState - Player Slot ('${GAME_CONFIG.PLAYER_ID}'): ${this.playerCharacterKey}, Opponent Slot ('${GAME_CONFIG.OPPONENT_ID}'): ${this.opponentCharacterKey || 'N/A (Waiting)'}`);

        const playerBase = this._getCharacterBaseData(this.playerCharacterKey);
        const playerAbilities = this._getCharacterAbilities(this.playerCharacterKey);
        let opponentBase = null;
        let opponentAbilities = null;

        // Загружаем данные оппонента, только если он определен (т.е. PvP игра с 2 игроками или AI игра)
        const isOpponentDefined = !!this.opponentCharacterKey;
        if (isOpponentDefined) {
            opponentBase = this._getCharacterBaseData(this.opponentCharacterKey);
            opponentAbilities = this._getCharacterAbilities(this.opponentCharacterKey);
        }

        // Проверяем наличие данных для создания базового gameState
        if (!playerBase || !playerAbilities) {
            console.error(`[Game ${this.id}] CRITICAL ERROR: initializeGame - Failed to load player character data! PlayerKey: ${this.playerCharacterKey}`);
            this.logBuffer = []; // Очищаем лог
            this.addToLog('Критическая ошибка сервера при инициализации персонажа игрока!', GAME_CONFIG.LOG_TYPE_SYSTEM);
            // Уведомляем игроков в комнате об ошибке (если есть)
            Object.values(this.players).forEach(p => p.socket.emit('gameError', { message: 'Критическая ошибка сервера при инициализации игры. Не удалось загрузить данные персонажа игрока.' }));
            this.gameState = null; // Не создаем gameState
            return false; // <-- Возвращаем false при ошибке
        }

        // Проверяем наличие данных оппонента, если он должен быть определен
        if (isOpponentDefined && (!opponentBase || !opponentAbilities)) {
            console.error(`[Game ${this.id}] CRITICAL ERROR: initializeGame - Failed to load opponent character data! OpponentKey: ${this.opponentCharacterKey}`);
            this.logBuffer = [];
            this.addToLog('Критическая ошибка сервера при инициализации персонажа оппонента!', GAME_CONFIG.LOG_TYPE_SYSTEM);
            Object.values(this.players).forEach(p => p.socket.emit('gameError', { message: 'Критическая ошибка сервера при инициализации игры. Не удалось загрузить данные персонажа оппонента.' }));
            this.gameState = null;
            return false; // <-- Возвращаем false при ошибке
        }

        // Проверка, если оппонент определен, что у него есть maxHp > 0 (для startGame)
        if (isOpponentDefined && (!opponentBase.maxHp || opponentBase.maxHp <= 0)) {
            console.error(`[Game ${this.id}] CRITICAL ERROR: initializeGame - Opponent has invalid maxHp (${opponentBase.maxHp}) for key ${this.opponentCharacterKey}.`);
            this.logBuffer = [];
            this.addToLog('Критическая ошибка сервера: некорректные данные оппонента!', GAME_CONFIG.LOG_TYPE_SYSTEM);
            Object.values(this.players).forEach(p => p.socket.emit('gameError', { message: 'Критическая ошибка сервера: некорректные данные персонажа оппонента.' }));
            this.gameState = null;
            return false; // <-- Возвращаем false при ошибке
        }


        // Создаем начальное gameState
        this.gameState = {
            player: { // Состояние игрока в слоте 'player'
                id: GAME_CONFIG.PLAYER_ID, characterKey: this.playerCharacterKey, name: playerBase.name,
                currentHp: playerBase.maxHp, maxHp: playerBase.maxHp,
                currentResource: playerBase.maxResource, maxResource: playerBase.maxResource,
                resourceName: playerBase.resourceName, attackPower: playerBase.attackPower,
                isBlocking: false, activeEffects: [], disabledAbilities: [], abilityCooldowns: {}
            },
            opponent: { // Состояние оппонента в слоте 'opponent'. Если оппонент еще не определен (PvP ожидание), будут плейсхолдеры.
                id: GAME_CONFIG.OPPONENT_ID, characterKey: this.opponentCharacterKey,
                name: opponentBase?.name || 'Ожидание игрока...',
                currentHp: opponentBase?.maxHp || 1, maxHp: opponentBase?.maxHp || 1, // HP > 0, чтобы не триггерить победу сразу, если оппонент не определен
                currentResource: opponentBase?.maxResource || 0, maxResource: opponentBase?.maxResource || 0,
                resourceName: opponentBase?.resourceName || 'Ресурс', attackPower: opponentBase?.attackPower || 0,
                isBlocking: false, activeEffects: [], disabledAbilities: [], abilityCooldowns: {},
                // Специальные кулдауны для Баларда (AI) - undefined для других персонажей
                silenceCooldownTurns: (this.opponentCharacterKey === 'balard') ? 0 : undefined,
                manaDrainCooldownTurns: (this.opponentCharacterKey === 'balard') ? 0 : undefined,
            },
            // Случайный первый ход только если оба игрока (бойца) определены (т.е. игра готова к старту)
            isPlayerTurn: isOpponentDefined ? Math.random() < 0.5 : true, // Если ожидаем, всегда ход первого игрока (кто создал)
            isGameOver: false,
            turnNumber: 1,
            gameMode: this.mode
        };

        // Инициализация кулдаунов способностей для обоих бойцов (если они определены)
        // Для игрока (player slot)
        if (playerAbilities) {
            playerAbilities.forEach(ability => {
                if (typeof ability.cooldown === 'number' && ability.cooldown >= 0) {
                    this.gameState.player.abilityCooldowns[ability.id] = 0;
                }
            });
        } else {
            console.error(`[Game ${this.id}] Cannot initialize player abilities cooldowns: playerAbilities is null.`);
        }

        // Для оппонента (opponent slot), только если он определен
        if (isOpponentDefined && opponentAbilities) {
            opponentAbilities.forEach(ability => {
                let cd = 0;
                if (typeof ability.cooldown === 'number' && ability.cooldown >= 0) {
                    cd = ability.cooldown;
                }
                if (this.opponentCharacterKey === 'balard') { // Специальные КД для Баларда
                    if (ability.id === GAME_CONFIG.ABILITY_ID_BALARD_SILENCE && typeof GAME_CONFIG.BALARD_SILENCE_INTERNAL_COOLDOWN === 'number') {
                        cd = GAME_CONFIG.BALARD_SILENCE_INTERNAL_COOLDOWN;
                    } else if (ability.id === GAME_CONFIG.ABILITY_ID_BALARD_MANA_DRAIN && typeof ability.internalCooldownValue === 'number') {
                        cd = ability.internalCooldownValue;
                    }
                }
                if (cd > 0) {
                    this.gameState.opponent.abilityCooldowns[ability.id] = 0;
                }
            });
        } else if (isOpponentDefined) {
            console.warn(`[Game ${this.id}] Cannot initialize opponent abilities cooldowns: opponentAbilities is null for key ${this.opponentCharacterKey}.`);
        }


        // Добавляем начальное сообщение в лог и стартовую насмешку, только если игра полностью готова к старту (оба бойца определены)
        if (isOpponentDefined) { // Проверяем, определен ли оппонент (значит, игра полностью готова)
            const isRestart = this.logBuffer.length > 0; // Сейчас этот буфер всегда пустой при инициализации новой игры
            this.logBuffer = []; // Очищаем лог перед новой игрой

            this.addToLog(isRestart ? '⚔️ Игра перезапущена! ⚔️' : '⚔️ Новая битва начинается! ⚔️', GAME_CONFIG.LOG_TYPE_SYSTEM);

            // Добавляем стартовую насмешку игрока (в слоте 'player'), если она есть
            const playerCharKey = this.gameState.player.characterKey;
            if (playerCharKey === 'elena' || playerCharKey === 'almagest') {
                // getRandomTaunt теперь принимает gameDataForLogic и gameState
                const startTaunt = serverGameLogic.getRandomTaunt(playerCharKey, 'battleStart', {}, GAME_CONFIG, gameData, this.gameState);
                if (startTaunt !== "(Молчание)") this.addToLog(`${this.gameState.player.name}: "${startTaunt}"`, GAME_CONFIG.LOG_TYPE_INFO);
            }
        }
        // Если оппонент не определен (PvP ожидание),gameState инициализируется частично, но не считается готовым к старту.
        // LogBuffer не очищается, waitingForOpponent отправлено клиенту.
        console.log(`[Game ${this.id}] Game state initialized. isGameOver: ${this.gameState?.isGameOver}. First turn (if ready): ${this.gameState?.isPlayerTurn ? this.gameState?.player?.name : (this.gameState?.opponent?.name || 'Оппонент')}. Opponent Defined (Ready for Start): ${isOpponentDefined}`);

        // Возвращаем флаг, готово ли gameState к началу игры (т.е. определены ли оба бойца)
        return isOpponentDefined; // <-- Важно: возвращаем, готова ли игра к старту
    }

    /**
     * Запускает игру (отправляет начальное состояние клиентам и начинает первый ход).
     * Вызывается GameManager после успешной инициализации gameState и наличия 2 игроков (PvP) или 1 (AI),
     * и если initializeGame вернул true.
     */
    startGame() {
        // Проверяем, что игра полностью готова к запуску (gameState инициализирован с обоими персонажами)
        if (!this.gameState || !this.gameState.player || !this.gameState.opponent || !this.opponentCharacterKey || this.gameState.opponent.name === 'Ожидание игрока...' || !this.gameState.opponent.maxHp || this.gameState.opponent.maxHp <= 0) {
            console.error(`[Game ${this.id}] startGame: Game state is not fully ready for start. Aborting.`);
            // initializeGame должен был вернуть false и добавить ошибку в лог.
            // GameManager должен был вызвать cleanupGame при ошибке initializeGame.
            return;
        }
        // Убеждаемся, что у нас есть 1 или 2 игрока (сокета) - GameManager должен гарантировать это перед вызовом startGame
        if (this.playerCount === 0 || (this.mode === 'pvp' && this.playerCount === 1)) {
            console.warn(`[Game ${this.id}] startGame called with insufficient players (${this.playerCount}). Mode: ${this.mode}. Aborting start.`);
            return;
        }


        console.log(`[Game ${this.id}] Starting game. Broadcasting 'gameStarted' to players. isGameOver: ${this.gameState.isGameOver}`);

        // Получаем данные персонажей для отправки клиентам
        const playerCharDataForSlotPlayer = this._getCharacterData(this.playerCharacterKey);
        const opponentCharDataForSlotOpponent = this._getCharacterData(this.opponentCharacterKey);

        if (!playerCharDataForSlotPlayer || !opponentCharDataForSlotOpponent) {
            console.error(`[Game ${this.id}] CRITICAL ERROR: startGame - Failed to load character data! PlayerKey: ${this.playerCharacterKey}, OpponentKey: ${this.opponentCharacterKey}`);
            this.io.to(this.id).emit('gameError', { message: 'Критическая ошибка сервера при старте игры (не удалось загрузить данные персонажей).' });
            // Критическая ошибка, игра должна быть очищена
            if (this.gameManager && typeof this.gameManager._cleanupGame === 'function') {
                this.gameManager._cleanupGame(this.id, 'start_data_load_failed');
            }
            return;
        }

        // Отправляем каждому игроку его персональные данные для игры и начальное состояние
        Object.values(this.players).forEach(playerInfo => {
            // Проверяем, что сокет игрока еще подключен
            if (playerInfo.socket && playerInfo.socket.connected) {
                let dataForThisClient;
                // playerInfo.id - это технический ID слота этого конкретного клиента в рамках игры ('player' или 'opponent')
                if (playerInfo.id === GAME_CONFIG.PLAYER_ID) { // Этот клиент играет за слот 'player'
                    dataForThisClient = {
                        gameId: this.id, yourPlayerId: playerInfo.id, initialGameState: this.gameState,
                        playerBaseStats: playerCharDataForSlotPlayer.baseStats, opponentBaseStats: opponentCharDataForSlotOpponent.baseStats,
                        playerAbilities: playerCharDataForSlotPlayer.abilities, opponentAbilities: opponentCharDataForSlotOpponent.abilities,
                        log: this.consumeLogBuffer(), // Игрок в слоте player получает весь накопленный лог
                        clientConfig: { ...GAME_CONFIG } // Копия конфига для клиента
                    };
                } else { // Этот клиент играет за слот 'opponent' (только в PvP)
                    dataForThisClient = {
                        gameId: this.id, yourPlayerId: playerInfo.id, initialGameState: this.gameState,
                        // Меняем местами статы и абилки, чтобы клиент видел себя как 'player', а противника как 'opponent'
                        playerBaseStats: opponentCharDataForSlotOpponent.baseStats, opponentBaseStats: playerCharDataForSlotPlayer.baseStats,
                        playerAbilities: opponentCharDataForSlotOpponent.abilities, opponentAbilities: playerCharDataForSlotPlayer.abilities,
                        log: this.consumeLogBuffer(), // Второй игрок тоже получает весь накопленный лог
                        clientConfig: { ...GAME_CONFIG }
                    };
                }
                playerInfo.socket.emit('gameStarted', dataForThisClient);
                console.log(`[Game ${this.id}] Sent gameStarted to ${playerInfo.identifier} (socket ${playerInfo.socket.id}).`);
            } else {
                console.warn(`[Game ${this.id}] Player ${playerInfo.identifier} (socket ${playerInfo.socket?.id}) is disconnected. Cannot send gameStarted.`);
                // Если один из игроков отключен в момент старта PvP игры, она должна быть завершена.
                // GameManager.handleDisconnect уже должен был вызвать endGameDueToDisconnect.
            }
        });

        // Добавляем лог о начале хода после отправки gameStarted, чтобы лог был актуальным
        const firstTurnActorState = this.gameState.isPlayerTurn ? this.gameState.player : this.gameState.opponent;
        this.addToLog(`--- Начинается ход ${this.gameState.turnNumber} для: ${firstTurnActorState.name} ---`, GAME_CONFIG.LOG_TYPE_TURN);
        // Отправляем этот лог всем клиентам
        this.broadcastLogUpdate();


        // Если ход AI, запускаем его логику с небольшой задержкой
        // AI Балард всегда в слоте 'opponent' в AI режиме, и его characterKey === 'balard'
        if (!this.gameState.isPlayerTurn && this.aiOpponent && this.opponentCharacterKey === 'balard') {
            console.log(`[Game ${this.id}] AI (Балард) ходит первым. Запускаем AI turn.`);
            // Небольшая задержка для старта AI хода, чтобы клиент успел отрисовать
            setTimeout(() => this.processAiTurn(), GAME_CONFIG.DELAY_OPPONENT_TURN || 1200);
        } else { // Ход реального игрока (первого или второго в PvP)
            // Игрок, чей ход, получит индикатор хода и активные кнопки через gameStateUpdate в клиентском ui.js
            console.log(`[Game ${this.id}] Ход реального игрока ${firstTurnActorState.name} (роль: ${firstTurnActorState.id}).`);
        }
    }


    /**
     * Обрабатывает действие игрока, полученное от клиента.
     * Вызывается GameManager при событии 'playerAction'.
     * @param {string} requestingSocketId - ID сокета игрока, запросившего действие.
     * @param {object} actionData - Данные о действии ({ actionType: 'attack' | 'ability', abilityId?: string }).
     */
    processPlayerAction(requestingSocketId, actionData) {
        // Проверяем, что игра активна и не окончена
        if (!this.gameState || this.gameState.isGameOver) {
            const playerSocket = this.io.sockets.sockets.get(requestingSocketId);
            if (playerSocket) playerSocket.emit('gameError', { message: 'Игра уже завершена или неактивна.' });
            return;
        }

        // Находим информацию об игроке по socketId (используем текущий socketId, т.к. он пришел в запросе)
        const actingPlayerInfo = this.players[requestingSocketId];
        if (!actingPlayerInfo) {
            // Этого не должно происходить, если GameManager корректно ищет игру по идентификатору и передает
            // текущий socketId. Но если произошло, возможно, сокет отправил действие после удаления из players.
            console.error(`[Game ${this.id}] Action from socket ${requestingSocketId} not found in players map.`);
            const playerSocket = this.io.sockets.sockets.get(requestingSocketId);
            if (playerSocket && playerSocket.connected) playerSocket.disconnect(true); // Отключаем подозрительный сокет
            return;
        }

        const actingPlayerRole = actingPlayerInfo.id; // 'player' или 'opponent' (технический ID слота)
        const isCorrectTurn = (this.gameState.isPlayerTurn && actingPlayerRole === GAME_CONFIG.PLAYER_ID) ||
            (!this.gameState.isPlayerTurn && actingPlayerRole === GAME_CONFIG.OPPONENT_ID);

        if (!isCorrectTurn) {
            // Неправильный ход - просто игнорируем действие. Клиентский UI должен предотвращать такое.
            // actingPlayerInfo.socket.emit('gameError', { message: "Сейчас не ваш ход!" }); // Можно отправлять ошибку, но это спамит лог
            console.warn(`[Game ${this.id}] Action from ${actingPlayerInfo.identifier} (socket ${requestingSocketId}): Not their turn.`);
            return; // Игнорируем действие
        }

        const attackerState = this.gameState[actingPlayerRole]; // Состояние того, кто ходит (в gameState по роли)
        const defenderRole = actingPlayerRole === GAME_CONFIG.PLAYER_ID ? GAME_CONFIG.OPPONENT_ID : GAME_CONFIG.PLAYER_ID;
        const defenderState = this.gameState[defenderRole]; // Состояние противника (в gameState по роли)

        // Получаем базовые данные персонажей для логики
        const attackerData = this._getCharacterData(attackerState.characterKey);
        const defenderData = this._getCharacterData(defenderState.characterKey);

        if (!attackerData || !defenderData) {
            console.error(`[Game ${this.id}] CRITICAL ERROR: processPlayerAction - Failed to load character data! AttackerKey: ${attackerState.characterKey}, DefenderKey: ${defenderState.characterKey}`);
            this.addToLog('Критическая ошибка сервера при обработке действия (не найдены данные персонажа)!', GAME_CONFIG.LOG_TYPE_SYSTEM);
            this.broadcastLogUpdate(); // Уведомляем клиентов об ошибке через лог
            // Критическая ошибка, игра должна быть очищена
            if (this.gameManager && typeof this.gameManager._cleanupGame === 'function') {
                this.gameManager._cleanupGame(this.id, 'action_data_load_failed');
            }
            return;
        }
        let actionValid = true; // Флаг валидности действия (логической, не очередности хода)

        // --- Обработка базовой атаки ---
        if (actionData.actionType === 'attack') {
            // Добавляем насмешку при базовой атаке (если есть такие для говорящего)
            // Насмешки при атаке могут быть специфичны для говорящего и противника
            // getRandomTaunt теперь принимает gameDataForLogic и gameState
            const taunt = serverGameLogic.getRandomTaunt(attackerState.characterKey, 'basicAttack', {}, GAME_CONFIG, gameData, this.gameState);
            if (taunt !== "(Молчание)") this.addToLog(`${attackerState.name}: "${taunt}"`, GAME_CONFIG.LOG_TYPE_INFO);

            // Выполняем логику атаки через gameLogic
            // performAttack теперь принимает gameDataForLogic и gameState
            serverGameLogic.performAttack(
                attackerState, defenderState, attackerData.baseStats, defenderData.baseStats,
                this.gameState, this.addToLog.bind(this), GAME_CONFIG, gameData
            );

            // Логика для "Силы Природы" и аналогов - бафф применяется после атаки
            // Проверяем, есть ли активный "отложенный" бафф (isDelayed) на атакующем
            const delayedAttackBuffEffect = attackerState.activeEffects.find(eff => eff.isDelayed && (eff.id === GAME_CONFIG.ABILITY_ID_NATURE_STRENGTH || eff.id === GAME_CONFIG.ABILITY_ID_ALMAGEST_BUFF_ATTACK));

            if (delayedAttackBuffEffect && !delayedAttackBuffEffect.justCast) { // Если эффект активен и не только что наложен в этом ходу
                const actualRegen = Math.min(GAME_CONFIG.NATURE_STRENGTH_MANA_REGEN, attackerData.baseStats.maxResource - attackerState.currentResource);
                if (actualRegen > 0) {
                    attackerState.currentResource = Math.round(attackerState.currentResource + actualRegen);
                    this.addToLog(`🌿 ${attackerState.name} восстанавливает ${actualRegen} ${attackerState.resourceName} от эффекта "${delayedAttackBuffEffect.name}"!`, GAME_CONFIG.LOG_TYPE_HEAL);
                }
            }


            // --- Обработка использования способности ---
        } else if (actionData.actionType === 'ability' && actionData.abilityId) {
            const ability = attackerData.abilities.find(ab => ab.id === actionData.abilityId);
            if (!ability) {
                actingPlayerInfo.socket.emit('gameError', { message: "Неизвестная способность." });
                console.warn(`[Game ${this.id}] Игрок ${actingPlayerInfo.identifier} (сокет ${requestingSocketId}) попытался использовать неизвестную способность ID: ${actionData.abilityId}.`);
                return; // Неизвестная способность
            }

            // Проверки валидности использования способности (ресурс, КД, безмолвие и т.д.)
            // Эти проверки дублируют клиентские, но нужны на сервере для безопасности
            const hasEnoughResource = attackerState.currentResource >= ability.cost;
            const isOnCooldown = (attackerState.abilityCooldowns?.[ability.id] || 0) > 0;
            const isCasterFullySilenced = attackerState.activeEffects.some(eff => eff.isFullSilence && eff.turnsLeft > 0);
            const isAbilitySpecificallySilenced = attackerState.disabledAbilities?.some(dis => dis.abilityId === ability.id && dis.turnsLeft > 0);
            const isSilenced = isCasterFullySilenced || isAbilitySpecificallySilenced;

            // Специальные КД для Баларда (AI) - эти поля undefined для других персонажей
            let isOnSpecialCooldown = false;
            if (attackerState.characterKey === 'balard') {
                if (ability.id === GAME_CONFIG.ABILITY_ID_BALARD_SILENCE && attackerState.silenceCooldownTurns !== undefined && attackerState.silenceCooldownTurns > 0) isOnSpecialCooldown = true;
                if (ability.id === GAME_CONFIG.ABILITY_ID_BALARD_MANA_DRAIN && attackerState.manaDrainCooldownTurns !== undefined && attackerState.manaDrainCooldownTurns > 0) isOnSpecialCooldown = true;
            }

            // Нельзя кастовать бафф, если он уже активен (для баффов, которые не должны стакаться)
            const isBuffAlreadyActive = ability.type === GAME_CONFIG.ACTION_TYPE_BUFF && attackerState.activeEffects.some(e => e.id === ability.id);

            // Нельзя кастовать дебафф на цель, если он уже на ней (для определенных дебаффов)
            const isTargetedDebuff = ability.id === GAME_CONFIG.ABILITY_ID_SEAL_OF_WEAKNESS || ability.id === GAME_CONFIG.ABILITY_ID_ALMAGEST_DEBUFF;
            const effectIdForDebuff = 'effect_' + ability.id;
            const isDebuffAlreadyOnTarget = isTargetedDebuff && defenderState.activeEffects.some(e => e.id === effectIdForDebuff);


            // Проверка всех условий
            if (!hasEnoughResource) { this.addToLog(`${attackerState.name} пытается применить "${ability.name}", но не хватает ${attackerState.resourceName}!`, GAME_CONFIG.LOG_TYPE_INFO); actionValid = false; }
            if (actionValid && (isOnCooldown || isOnSpecialCooldown)) { this.addToLog(`"${ability.name}" еще на перезарядке.`, GAME_CONFIG.LOG_TYPE_INFO); actionValid = false; }
            if (actionValid && isSilenced) { this.addToLog(`${attackerState.name} не может использовать способности из-за безмолвия!`, GAME_CONFIG.LOG_TYPE_INFO); actionValid = false; }
            if (actionValid && isBuffAlreadyActive) { this.addToLog(`Эффект "${ability.name}" уже активен!`, GAME_CONFIG.LOG_TYPE_INFO); actionValid = false; }
            if (actionValid && isDebuffAlreadyOnTarget) { this.addToLog(`Эффект "${ability.name}" уже наложен на ${defenderState.name}!`, GAME_CONFIG.LOG_TYPE_INFO); actionValid = false; }


            // Если все проверки пройдены, действие считается логически валидным
            if (actionValid) {
                attackerState.currentResource = Math.round(attackerState.currentResource - ability.cost); // Расходуем ресурс

                // Добавляем насмешку ПРИ КАСТЕ СПОСОБНОСТИ (если есть такие для говорящего)
                // getRandomTaunt теперь принимает gameDataForLogic и gameState
                const taunt = serverGameLogic.getRandomTaunt(attackerState.characterKey, 'selfCastAbility', { abilityId: ability.id }, GAME_CONFIG, gameData, this.gameState);
                if (taunt !== "(Молчание)") this.addToLog(`${attackerState.name}: "${taunt}"`, GAME_CONFIG.LOG_TYPE_INFO);


                // Применяем эффекты способности через gameLogic
                // applyAbilityEffect теперь принимает gameDataForLogic и gameState
                serverGameLogic.applyAbilityEffect(ability, attackerState, defenderState, attackerData.baseStats, defenderData.baseStats, this.gameState, this.addToLog.bind(this), GAME_CONFIG, gameData);

                // Установка кулдауна способности (после успешного каста)
                let baseCooldown = 0;
                if (typeof ability.cooldown === 'number' && ability.cooldown >= 0) {
                    baseCooldown = ability.cooldown;
                }
                // Специальные внутренние КД для Баларда - перебивают общий КД, если заданы
                if (attackerState.characterKey === 'balard') {
                    if (ability.id === GAME_CONFIG.ABILITY_ID_BALARD_SILENCE && typeof GAME_CONFIG.BALARD_SILENCE_INTERNAL_COOLDOWN === 'number') {
                        attackerState.silenceCooldownTurns = GAME_CONFIG.BALARD_SILENCE_INTERNAL_COOLDOWN;
                        baseCooldown = GAME_CONFIG.BALARD_SILENCE_INTERNAL_COOLDOWN; // Специальный КД становится актуальным кулдауном
                    } else if (ability.id === GAME_CONFIG.ABILITY_ID_BALARD_MANA_DRAIN && typeof ability.internalCooldownValue === 'number') {
                        attackerState.manaDrainCooldownTurns = ability.internalCooldownValue;
                        baseCooldown = ability.internalCooldownValue; // Специальный КД становится актуальным кулдауном
                    }
                }

                if (baseCooldown > 0 && attackerState.abilityCooldowns) {
                    // Устанавливаем кулдаун. Добавляем +1, т.к. кулдаун уменьшится в конце этого хода.
                    attackerState.abilityCooldowns[ability.id] = baseCooldown + 1;
                }
            }
        } else {
            // Неизвестный тип действия
            actingPlayerInfo.socket.emit('gameError', { message: `Неизвестный тип действия: ${actionData?.actionType}` });
            console.warn(`[Game ${this.id}] Получен неизвестный тип действия от ${actingPlayerInfo.identifier} (сокет ${requestingSocketId}): ${actionData?.actionType}`);
            actionValid = false; // Помечаем как невалидное
        }

        // --- Завершение хода ---
        // Проверяем конец игры после выполнения действия
        if (this.checkGameOver()) {
            this.broadcastGameStateUpdate(); // Отправляем финальное состояние и лог всем
            // Очистка игры теперь происходит ВНУТРИ checkGameOver через вызов _notifyGameEnded
            return; // Если игра окончена, не переключаем ход
        }

        // Если действие было логически валидным и игра не окончена, переключаем ход с задержкой
        if (actionValid) {
            console.log(`[Game ${this.id}] Player action valid. Switching turn in ${GAME_CONFIG.DELAY_AFTER_PLAYER_ACTION || 500}ms.`);
            setTimeout(() => { this.switchTurn(); }, GAME_CONFIG.DELAY_AFTER_PLAYER_ACTION || 500);
        } else {
            // Если действие невалидно, просто обновляем лог (если что-то было добавлено), ход не переключается
            console.log(`[Game ${this.id}] Player action invalid. Broadcasting log update.`);
            this.broadcastLogUpdate();
        }
    }

    /**
     * Переключает ход на следующего бойца, обрабатывает эффекты конца хода и кулдауны.
     */
    switchTurn() {
        // Проверяем, что игра активна и не окончена
        if (!this.gameState || this.gameState.isGameOver) return; // Не переключаем ход, если игра окончена или состояние некорректно

        // Определяем бойца, чей ход только что закончился
        const endingTurnActorRole = this.gameState.isPlayerTurn ? GAME_CONFIG.PLAYER_ID : GAME_CONFIG.OPPONENT_ID;
        const endingTurnActorState = this.gameState[endingTurnActorRole];
        const endingTurnCharacterData = this._getCharacterData(endingTurnActorState.characterKey); // Получаем данные персонажа

        // Проверяем наличие данных для обработки эффектов и КД
        if (!endingTurnCharacterData) {
            console.error(`[Game ${this.id}] SwitchTurn Error: No character data found for ending turn actor role ${endingTurnActorRole} with key ${endingTurnActorState.characterKey}. Cannot process end-of-turn effects.`);
            // Критическая ошибка, игра должна быть очищена
            if (this.gameManager && typeof this.gameManager._cleanupGame === 'function') {
                this.gameManager._cleanupGame(this.id, 'switch_turn_data_error');
            }
            return;
        } else {
            // Обработка активных эффектов в конце хода (DoT, HoT, истечение баффов/debuffs) для бойца, чей ход закончился
            // processEffects теперь принимает gameDataForLogic и gameState
            serverGameLogic.processEffects(endingTurnActorState.activeEffects, endingTurnActorState, endingTurnCharacterData.baseStats, endingTurnActorRole, this.gameState, this.addToLog.bind(this), GAME_CONFIG, gameData);

            // Обновляем статус блока для обоих бойцов после обработки эффектов (т.к. эффекты блока могли закончиться)
            // Важно обновить статус для ОБОИХ, т.к. эффекты на одном могут зависеть от статуса блока другого.
            serverGameLogic.updateBlockingStatus(this.gameState.player);
            serverGameLogic.updateBlockingStatus(this.gameState.opponent);

            // Уменьшение общих кулдаунов способностей для бойца, чей ход закончился
            if (endingTurnActorState.abilityCooldowns) {
                // processPlayerAbilityCooldowns теперь принимает gameDataForLogic (хотя он там не используется напрямую)
                serverGameLogic.processPlayerAbilityCooldowns(endingTurnActorState.abilityCooldowns, endingTurnCharacterData.abilities, endingTurnActorState.name, this.addToLog.bind(this));
            }
            // Специальные КД для Баларда (если он в слоте, который сейчас заканчивает ход)
            if (endingTurnActorState.characterKey === 'balard') {
                if (endingTurnActorState.silenceCooldownTurns !== undefined && endingTurnActorState.silenceCooldownTurns > 0) endingTurnActorState.silenceCooldownTurns--;
                if (endingTurnActorState.manaDrainCooldownTurns !== undefined && endingTurnActorState.manaDrainCooldownTurns > 0) endingTurnActorState.manaDrainCooldownTurns--;
            }

            // Уменьшение длительности заглушения конкретных способностей (если это ход того, кто был заглушен)
            if (endingTurnActorState.disabledAbilities?.length > 0) {
                const charAbilitiesForDisabledCheck = this._getCharacterAbilities(endingTurnActorState.characterKey); // Список абилок для поиска по ID
                if (charAbilitiesForDisabledCheck) {
                    // processDisabledAbilities теперь принимает gameDataForLogic (хотя он там не используется напрямую)
                    serverGameLogic.processDisabledAbilities(endingTurnActorState.disabledAbilities, charAbilitiesForDisabledCheck, endingTurnActorState.name, this.addToLog.bind(this));
                } else {
                    console.warn(`[Game ${this.id}] SwitchTurn: Cannot process disabledAbilities for ${endingTurnActorState.name}: character abilities data not found.`);
                }
            }
        }


        // Проверяем конец игры после обработки эффектов конца хода
        if (this.checkGameOver()) {
            this.broadcastGameStateUpdate(); // Отправляем финальное состояние и лог всем
            // Очистка игры теперь происходит ВНУТРИ checkGameOver через вызов _notifyGameEnded
            return; // Если игра окончена, останавливаемся
        }

        // Если игра не окончена, переключаем ход на следующего бойца с задержкой
        this.gameState.isPlayerTurn = !this.gameState.isPlayerTurn;
        // Если ход переключился на первого игрока (player), увеличиваем номер хода
        if (this.gameState.isPlayerTurn) {
            this.gameState.turnNumber++;
        }

        // Определяем бойца, чей ход начинается
        const currentTurnActorState = this.gameState.isPlayerTurn ? this.gameState.player : this.gameState.opponent;
        this.addToLog(`--- Начинается ход ${this.gameState.turnNumber} для: ${currentTurnActorState.name} ---`, GAME_CONFIG.LOG_TYPE_TURN);

        // Отправляем обновленное состояние игры и лог всем клиентам в комнате
        this.broadcastGameStateUpdate();

        // Если ход AI, запускаем его логику с задержкой
        // AI Балард всегда в слоте 'opponent' в AI режиме, и его characterKey === 'balard'
        if (!this.gameState.isPlayerTurn && this.aiOpponent && this.opponentCharacterKey === 'balard') {
            console.log(`[Game ${this.id}] Ход AI (Балард). Запускаем AI turn.`);
            setTimeout(() => this.processAiTurn(), GAME_CONFIG.DELAY_OPPONENT_TURN || 1200);
        } else { // Ход реального игрока (первого или второго в PvP)
            // Игрок, чей ход, получит индикатор хода и активные кнопки через gameStateUpdate в клиентском ui.js
            console.log(`[Game ${this.id}] Ход реального игрока ${currentTurnActorState.name} (роль: ${currentTurnActorState.id}).`);
        }
    }

    /**
     * Обрабатывает ход AI (Балард).
     * Вызывается из switchTurn, если следующий ход принадлежит AI.
     */
    processAiTurn() {
        // Проверка: это точно ход AI Баларда и игра активна?
        // AI Балард только в режиме 'ai', и его characterKey в gameState должен быть 'balard'.
        if (!this.gameState || this.gameState.isGameOver || this.gameState.isPlayerTurn || !this.aiOpponent || this.gameState.opponent?.characterKey !== 'balard') {
            if(!this.gameState || this.gameState.isGameOver) return; // Если игра закончена, ничего не делаем
            // Если не ход AI или это не AI Балард, выходим (на всякий случай)
            // console.warn(`[Game ${this.id}] processAiTurn called when it's not AI Balard's turn or not AI mode.`);
            // Если AI ход по какой-то причине пропущен, переключаем ход на игрока
            console.warn(`[Game ${this.id}] Skipping AI turn logic (not AI Balard's turn or game not ready). Switching turn.`);
            this.switchTurn(); // Пропускаем AI ход и переключаем обратно
            return;
        }

        const attackerState = this.gameState.opponent; // AI Балард всегда в слоте 'opponent' в AI режиме
        const defenderState = this.gameState.player; // Игрок всегда в слоте 'player' в AI режиме
        // Получаем базовые данные персонажей для логики
        const attackerData = this._getCharacterData('balard'); // Базовые данные Баларда
        const defenderData = this._getCharacterData('elena'); // Базовые данные Елены (противник AI)

        if (!attackerData || !defenderData) {
            console.error(`[Game ${this.id}] CRITICAL ERROR: processAiTurn - Failed to load character data!`);
            this.addToLog("AI не может действовать: ошибка данных персонажа.", GAME_CONFIG.LOG_TYPE_SYSTEM);
            this.broadcastLogUpdate(); // Отправляем ошибку в лог
            // Критическая ошибка, игра должна быть очищена
            if (this.gameManager && typeof this.gameManager._cleanupGame === 'function') {
                this.gameManager._cleanupGame(this.id, 'ai_data_load_failed');
            }
            this.switchTurn(); // Пропускаем ход AI и переключаем обратно на игрока
            return;
        }

        // Проверка полного безмолвия Баларда (от Гипнотического Взгляда Елены или Раскола Разума Альмагест) - повторная проверка из decideAiAction, на всякий случай
        const isBalardFullySilenced = attackerState.activeEffects.some(
            eff => eff.isFullSilence && eff.turnsLeft > 0
        );

        if (isBalardFullySilenced) {
            // AI под полным безмолвием просто атакует
            // Лог о безмолвии и атаке в смятении добавляется в processAiTurn перед вызовом performAttack.
            // decideAiAction просто возвращает действие.
            // if (this.logBuffer.filter(log => log.message.includes('под действием Безмолвия')).length === 0) { // Логируем только если еще не логировали в decideAiAction
            //    this.addToLog(`😵 ${attackerState.name} под действием Безмолвия! Атакует в смятении.`, GAME_CONFIG.LOG_TYPE_EFFECT);
            // }
            serverGameLogic.performAttack(attackerState, defenderState, attackerData.baseStats, defenderData.baseStats, this.gameState, this.addToLog.bind(this), GAME_CONFIG, gameData);

            // Логика для "Силы Природы" и аналогов (неактуально для Баларда)
            const delayedAttackBuffEffect = attackerState.activeEffects.find(eff => eff.isDelayed && (eff.id === GAME_CONFIG.ABILITY_ID_NATURE_STRENGTH || eff.id === GAME_CONFIG.ABILITY_ID_ALMAGEST_BUFF_ATTACK));
            if (delayedAttackBuffEffect && !delayedAttackBuffEffect.justCast) {
                const actualRegen = Math.min(GAME_CONFIG.NATURE_STRENGTH_MANA_REGEN, attackerState.maxResource - attackerState.currentResource);
                if (actualRegen > 0) {
                    attackerState.currentResource = Math.round(attackerState.currentResource + actualRegen);
                    this.addToLog(`🌿 ${attackerState.name} восстанавливает ${actualRegen} ${attackerState.resourceName} от эффекта "${delayedAttackBuffEffect.name}"!`, GAME_CONFIG.LOG_TYPE_HEAL);
                }
            }


            // После атаки под безмолвием, переключаем ход
            if (this.checkGameOver()) { this.broadcastGameStateUpdate(); return; } // Проверяем Game Over
            console.log(`[Game ${this.id}] AI (Балард) attacked while silenced. Switching turn in ${GAME_CONFIG.DELAY_AFTER_PLAYER_ACTION || 500}ms.`);
            setTimeout(() => { this.switchTurn(); }, GAME_CONFIG.DELAY_AFTER_PLAYER_ACTION || 500);
            return; // Завершаем обработку AI хода
        }


        // AI принимает решение о действии, используя логику из gameLogic
        // decideAiAction теперь принимает gameDataForLogic и gameState
        const aiDecision = serverGameLogic.decideAiAction(this.gameState, gameData, GAME_CONFIG, this.addToLog.bind(this));

        // --- Выполнение выбранного AI действия ---
        if (aiDecision.actionType === 'attack') {
            // AI Балард пока не имеет специфических насмешек при базовой атаке

            serverGameLogic.performAttack(attackerState, defenderState, attackerData.baseStats, defenderData.baseStats, this.gameState, this.addToLog.bind(this), GAME_CONFIG, gameData);

            // Логика для "Силы Природы" и аналогов (неактуально для Баларда)
            const delayedAttackBuffEffect = attackerState.activeEffects.find(eff => eff.isDelayed && (eff.id === GAME_CONFIG.ABILITY_ID_NATURE_STRENGTH || eff.id === GAME_CONFIG.ABILITY_ID_ALMAGEST_BUFF_ATTACK));
            if (delayedAttackBuffEffect && !delayedAttackBuffEffect.justCast) {
                const actualRegen = Math.min(GAME_CONFIG.NATURE_STRENGTH_MANA_REGEN, attackerState.maxResource - attackerState.currentResource);
                if (actualRegen > 0) {
                    attackerState.currentResource = Math.round(attackerState.currentResource + actualRegen);
                    this.addToLog(`🌿 ${attackerState.name} восстанавливает ${actualRegen} ${attackerState.resourceName} от эффекта "${delayedAttackBuffEffect.name}"!`, GAME_CONFIG.LOG_TYPE_HEAL);
                }
            }

        } else if (aiDecision.actionType === 'ability' && aiDecision.ability) {
            const ability = aiDecision.ability;
            // decideAiAction уже проверил ресурс, КД и специфические условия перед тем, как выбрать эту способность.
            // Теперь просто выполняем ее.

            attackerState.currentResource = Math.round(attackerState.currentResource - ability.cost); // Расходуем ресурс

            // Добавляем насмешку ПРИ КАСТЕ СПОСОБНОСТИ Балардом (если есть такие для говорящего)
            // В текущей реализации Балард не имеет секции selfCastAbility
            // const taunt = serverGameLogic.getRandomTaunt(attackerState.characterKey, 'selfCastAbility', { abilityId: ability.id }, GAME_CONFIG, gameData, this.gameState);
            // if (taunt !== "(Молчание)") this.addToLog(`${attackerState.name}: "${taunt}"`, GAME_CONFIG.LOG_TYPE_INFO);

            // applyAbilityEffect теперь принимает gameDataForLogic и gameState
            serverGameLogic.applyAbilityEffect(ability, attackerState, defenderState, attackerData.baseStats, defenderData.baseStats, this.gameState, this.addToLog.bind(this), GAME_CONFIG, gameData);

            // Установка кулдауна способности Баларда (после успешного каста)
            let baseCooldown = 0;
            if (typeof ability.cooldown === 'number' && ability.cooldown >= 0) {
                baseCooldown = ability.cooldown;
            }
            // Специальные внутренние КД для Баларда - перебивают общий КД, если заданы
            if (ability.id === GAME_CONFIG.ABILITY_ID_BALARD_SILENCE && typeof GAME_CONFIG.BALARD_SILENCE_INTERNAL_COOLDOWN === 'number') {
                attackerState.silenceCooldownTurns = GAME_CONFIG.BALARD_SILENCE_INTERNAL_COOLDOWN;
                baseCooldown = GAME_CONFIG.BALARD_SILENCE_INTERNAL_COOLDOWN; // Специальный КД становится актуальным кулдауном
            } else if (ability.id === GAME_CONFIG.ABILITY_ID_BALARD_MANA_DRAIN && typeof ability.internalCooldownValue === 'number') {
                attackerState.manaDrainCooldownTurns = ability.internalCooldownValue;
                baseCooldown = ability.internalCooldownValue; // Специальный КД становится актуальным кулдауном
            }

            if (baseCooldown > 0 && attackerState.abilityCooldowns) {
                // Устанавливаем кулдаун. Добавляем +1, т.к. кулдаун уменьшится в конце этого хода.
                attackerState.abilityCooldowns[ability.id] = baseCooldown + 1;
            }

        } else if (aiDecision.actionType === 'pass') { // Если AI решил пропустить ход
            if (aiDecision.logMessage) this.addToLog(aiDecision.logMessage.message, aiDecision.logMessage.type);
            else this.addToLog(`${attackerState.name} обдумывает свой следующий ход...`, GAME_CONFIG.LOG_TYPE_INFO);
        } else { // Неизвестное решение AI или ошибка в логике decideAiAction
            console.error(`[Game ${this.id}] AI (Балард) chose an invalid action type: ${aiDecision.actionType}. Defaulting to pass and logging error.`);
            this.addToLog(`AI ${attackerState.name} не смог выбрать действие из-за ошибки. Пропускает ход.`, GAME_CONFIG.LOG_TYPE_INFO);
            // Критическая ошибка в логике AI, возможно, стоит завершить игру?
            // Но пропуск хода - это более мягкое решение.
        }

        // Проверяем конец игры после выполнения действия AI
        if (this.checkGameOver()) {
            this.broadcastGameStateUpdate(); // Отправляем финальное состояние и лог всем
            // Очистка игры теперь происходит ВНУТРИ checkGameOver через вызов _notifyGameEnded
            return; // Если игра окончена, останавливаемся
        }

        // Если игра не окончена, переключаем ход на игрока с задержкой
        console.log(`[Game ${this.id}] AI action complete. Switching turn in ${GAME_CONFIG.DELAY_AFTER_PLAYER_ACTION || 500}ms.`);
        setTimeout(() => { this.switchTurn(); }, GAME_CONFIG.DELAY_AFTER_PLAYER_ACTION || 500);
    }

    /**
     * Проверяет, окончена ли игра (по HP бойцов).
     * Если да, обновляет gameState, отправляет событие gameOver и уведомляет GameManager.
     * Вызывается после каждого действия (игрока или AI) и после обработки эффектов конца хода.
     * @returns {boolean} true, если игра окончена, иначе false.
     */
    checkGameOver() {
        // Проверка на конец игры происходит только если gameState существует и игра еще не помечена как оконченная
        if (!this.gameState || this.gameState.isGameOver) return this.gameState ? this.gameState.isGameOver : true;

        // Убеждаемся, что оба бойца определены в gameState и не являются плейсхолдерами
        // Проверка maxHp > 0 в gameState.opponent гарантирует, что оппонент не плейсхолдер
        if (!this.gameState.player || !this.gameState.opponent || this.gameState.opponent.maxHp <= 0) {
            // Если один из бойцов не готов (например, PvP игра ожидает второго игрока), игра не может закончиться по HP
            return false;
        }


        // Используем внутреннюю функцию gameLogic для проверки условий победы/поражения по HP
        // checkGameOverInternal теперь принимает gameDataForLogic и gameState
        const isOver = serverGameLogic.checkGameOverInternal(this.gameState, GAME_CONFIG, gameData);

        // Если игра только что стала оконченной (был false, стал true)
        if (isOver && !this.gameState.isGameOver) {
            this.gameState.isGameOver = true; // Устанавливаем флаг

            const playerDead = this.gameState.player?.currentHp <= 0;
            const opponentDead = this.gameState.opponent?.currentHp <= 0;

            // Определяем победителя и проигравшего по ролям
            // Если оба мертвы одновременно, побеждает тот, кто не был убит последним действием,
            // или определяется по правилам (здесь: player побеждает при одновременной смерти)
            let winnerRole = null;
            let loserRole = null;
            // В AI режиме победителем всегда считается Player (если выжил), даже если AI тоже погиб.
            // При дисконнекте в AI режиме победителя нет.
            if (this.mode === 'ai') {
                winnerRole = playerDead ? null : GAME_CONFIG.PLAYER_ID; // Player победил, если не умер. AI не "побеждает".
                loserRole = playerDead ? GAME_CONFIG.PLAYER_ID : GAME_CONFIG.OPPONENT_ID;
            } else { // PvP режим
                if (playerDead && opponentDead) {
                    // Оба мертвы, побеждает игрок (по правилам игры)
                    winnerRole = GAME_CONFIG.PLAYER_ID;
                    loserRole = GAME_CONFIG.OPPONENT_ID;
                } else if (playerDead) {
                    // Игрок мертв, побеждает оппонент
                    winnerRole = GAME_CONFIG.OPPONENT_ID;
                    loserRole = GAME_CONFIG.PLAYER_ID;
                } else if (opponentDead) {
                    // Оппонент мертв, побеждает игрок
                    winnerRole = GAME_CONFIG.PLAYER_ID;
                    loserRole = GAME_CONFIG.OPPONENT_ID;
                } else {
                    // Этого не должно произойти, если isOver = true, но никто не мертв
                    console.error(`[Game ${this.id}] checkGameOverInternal returned true, but neither fighter is dead. GameState:`, this.gameState);
                    this.gameState.isGameOver = false; // Сбрасываем флаг, игра не окончена
                    return false;
                }
            }


            const winnerState = this.gameState[winnerRole];
            const loserState = this.gameState[loserRole];

            const winnerName = winnerState?.name || (winnerRole === GAME_CONFIG.PLAYER_ID ? "Игрок" : "Противник");
            const loserName = loserState?.name || (loserRole === GAME_CONFIG.PLAYER_ID ? "Игрок" : "Противник");
            const loserCharacterKey = loserState?.characterKey || 'unknown'; // Ключ персонажа проигравшего

            // Добавляем сообщение о победе в лог
            // В AI режиме при победе игрока лог специфичный. Если AI "победил" (игрок умер), лог тоже специфичный.
            if (this.mode === 'ai') {
                if (winnerRole === GAME_CONFIG.PLAYER_ID) { // Игрок победил
                    this.addToLog(`🏁 ПОБЕДА! Вы одолели ${loserName}! 🏁`, GAME_CONFIG.LOG_TYPE_SYSTEM);
                } else { // Игрок проиграл AI
                    this.addToLog(`😭 ПОРАЖЕНИЕ! ${winnerName} оказался(лась) сильнее! 😭`, GAME_CONFIG.LOG_TYPE_SYSTEM);
                }
            } else { // PvP режим
                this.addToLog(`🏁 ПОБЕДА! ${winnerName} одолел(а) ${loserName}! 🏁`, GAME_CONFIG.LOG_TYPE_SYSTEM);
            }


            // Добавляем дополнительные сообщения о конце игры (насмешка победителя)
            // В AI режиме AI Балард не "говорит" в конце. Говорит только игрок, если победил.
            const winningCharacterKey = winnerState?.characterKey;
            if (this.mode === 'ai' && winningCharacterKey === 'elena') {
                const taunt = serverGameLogic.getRandomTaunt(winningCharacterKey, 'opponentNearDefeatCheck', {}, GAME_CONFIG, gameData, this.gameState);
                if (taunt && taunt !== "(Молчание)") this.addToLog(`${winnerName}: "${taunt}"`, GAME_CONFIG.LOG_TYPE_INFO);
            } else if (this.mode === 'pvp' && (winningCharacterKey === 'elena' || winningCharacterKey === 'almagest')) {
                const taunt = serverGameLogic.getRandomTaunt(winningCharacterKey, 'opponentNearDefeatCheck', {}, GAME_CONFIG, gameData, this.gameState);
                if (taunt && taunt !== "(Молчание)") this.addToLog(`${winnerName}: "${taunt}"`, GAME_CONFIG.LOG_TYPE_INFO);
            }

            // Специальные системные логи в конце игры, зависящие от проигравшего
            if (loserCharacterKey === 'balard') {
                this.addToLog(`Елена исполнила свой тяжкий долг. ${loserName} развоплощен...`, GAME_CONFIG.LOG_TYPE_SYSTEM);
            } else if (loserCharacterKey === 'almagest') {
                this.addToLog(`Над полем битвы воцаряется тишина. ${loserName} побежден(а).`, GAME_CONFIG.LOG_TYPE_SYSTEM);
            } else if (loserCharacterKey === 'elena') {
                this.addToLog(`Свет погас. ${loserName} повержен(а).`, GAME_CONFIG.LOG_TYPE_SYSTEM);
            }

            console.log(`[Game ${this.id}] Game is over. Winner: ${winnerName} (${winnerRole}). Loser: ${loserName} (${loserRole}). Reason: HP <= 0.`);

            // Отправляем событие конца игры всем клиентам в комнате
            this.io.to(this.id).emit('gameOver', {
                // В AI режиме winnerId отправляем только если победил игрок, иначе null
                winnerId: this.mode === 'ai' ? (winnerRole === GAME_CONFIG.PLAYER_ID ? winnerRole : null) : winnerRole,
                reason: `${loserName} побежден(а)`, // Причина для отображения на клиенте
                finalGameState: this.gameState, // Финальное состояние игры для отображения
                log: this.consumeLogBuffer(), // Отправляем весь накопленный лог (включая финальные сообщения)
                loserCharacterKey: loserCharacterKey // Передаем characterKey проигравшего для клиентской анимации
            });

            // УВЕДОМЛЯЕМ GAMEMANAGER ОБ ОКОНЧАНИИ ИГРЫ ДЛЯ ОЧИСТКИ
            if (this.gameManager && typeof this.gameManager._cleanupGame === 'function') {
                this.gameManager._cleanupGame(this.id, 'hp_zero');
            } else {
                console.error(`[Game ${this.id}] GameManager reference missing or _cleanupGame not found! Game state will not be cleaned.`);
            }


            return true; // Игра окончена
        }

        // Если isOver было false, или gameState был некорректен, игра не окончена
        return isOver;
    }

    /**
     * Завершает игру из-за отключения одного из игроков.
     * Вызывается GameManager.
     * @param {string} disconnectedSocketId - socketId отключившегося игрока.
     * @param {string} disconnectedPlayerRole - Роль ('player' или 'opponent') отключившегося игрока.
     * @param {string} disconnectedCharacterKey - Ключ персонажа отключившегося игрока.
     */
    endGameDueToDisconnect(disconnectedSocketId, disconnectedPlayerRole, disconnectedCharacterKey) {
        if (this.gameState && !this.gameState.isGameOver) {
            this.gameState.isGameOver = true; // Помечаем игру как оконченную

            // Победитель - тот, кто остался. Его роль противоположна роли отключившегося.
            const winnerRole = disconnectedPlayerRole === GAME_CONFIG.PLAYER_ID ? GAME_CONFIG.OPPONENT_ID : GAME_CONFIG.PLAYER_ID;
            const disconnectedCharacterData = this._getCharacterBaseData(disconnectedCharacterKey); // Данные уходящего персонажа
            // Ключ персонажа победителя берем из gameState, т.к. там актуальное состояние слотов
            const winnerCharacterKey = (winnerRole === GAME_CONFIG.PLAYER_ID) ? this.playerCharacterKey : this.opponentCharacterKey;
            const winnerCharacterData = this._getCharacterBaseData(winnerCharacterKey); // Данные оставшегося персонажа

            // Добавляем сообщение о дисконнекте в лог
            this.addToLog(`🔌 Игрок ${disconnectedCharacterData?.name || 'Неизвестный'} (${disconnectedPlayerRole}) отключился. Игра завершена.`, GAME_CONFIG.LOG_TYPE_SYSTEM);
            // В AI режиме, если игрок отключился, AI не "побеждает" в стандартном смысле. Можно сделать специфичный лог.
            if (this.mode === 'pvp') {
                this.addToLog(`🏁 Победа присуждается ${winnerCharacterData?.name || winnerRole}! 🏁`, GAME_CONFIG.LOG_TYPE_SYSTEM);
            } else { // AI режим
                // AI игра завершается без формальной победы AI, если игрок ушел
                // Лог выше уже достаточен
            }


            // Отправляем событие конца игры всем клиентам в комнате
            // GameManager отвечает за удаление игры и очистку ссылок userIdentifierToGameId.
            // Здесь мы только уведомляем клиентов.
            this.io.to(this.id).emit('gameOver', {
                // В AI режиме winnerId отправляем null, т.к. нет формального победителя
                winnerId: this.mode === 'pvp' ? winnerRole : null,
                reason: 'opponent_disconnected', // Причина для клиента
                finalGameState: this.gameState, // Финальное состояние игры для отображения
                log: this.consumeLogBuffer(), // Отправляем весь накопленный лог
                // Передаем characterKey проигравшего (того, кто отключился) для клиентской анимации (растворения)
                loserCharacterKey: disconnectedCharacterKey
            });

            console.log(`[Game ${this.id}] Game ended due to disconnect. Winner (PvP only): ${winnerCharacterData?.name || winnerRole}. Disconnected: ${disconnectedCharacterData?.name || disconnectedPlayerRole}.`);

            // УВЕДОМЛЯЕМ GAMEMANAGER ОБ ОКОНЧАНИИ ИГРЫ ДЛЯ ОЧИСТКИ
            if (this.gameManager && typeof this.gameManager._cleanupGame === 'function') {
                this.gameManager._cleanupGame(this.id, 'opponent_disconnected');
            } else {
                console.error(`[Game ${this.id}] GameManager reference missing or _cleanupGame not found! Game state will not be cleaned.`);
            }

        }
    }


    /**
     * Добавляет сообщение в буфер лога игры.
     * @param {string} message - Текст сообщения.
     * @param {string} [type=GAME_CONFIG.LOG_TYPE_INFO] - Тип сообщения (для стилей на клиенте).
     */
    addToLog(message, type = GAME_CONFIG.LOG_TYPE_INFO) {
        if (!message) return;
        this.logBuffer.push({ message, type, timestamp: Date.now() });
        // В активной игре можно сразу отправлять лог, не дожидаясь gameStateUpdate
        // Это может улучшить отзывчивость лога.
        // if (this.gameState && !this.gameState.isGameOver) {
        //     this.broadcastLogUpdate();
        // }
    }

    /**
     * Очищает буфер лога и возвращает его содержимое.
     * @returns {Array<object>} Массив сообщений лога.
     */
    consumeLogBuffer() {
        const logs = [...this.logBuffer];
        this.logBuffer = [];
        return logs;
    }

    /**
     * Отправляет текущее состояние игры и содержимое лога всем клиентам в комнате игры.
     */
    broadcastGameStateUpdate() {
        if (!this.gameState) return;
        // Отправляем полное состояние игры и буфер лога всем клиентам в комнате
        this.io.to(this.id).emit('gameStateUpdate', { gameState: this.gameState, log: this.consumeLogBuffer() });
    }

    /**
     * Отправляет только содержимое лога всем клиентам в комнате игры (если нужно обновить только лог).
     * Полезно для сообщений, которые не меняют gameState, но должны отобразиться.
     */
    broadcastLogUpdate() {
        if (this.logBuffer.length > 0) {
            this.io.to(this.id).emit('logUpdate', { log: this.consumeLogBuffer() });
        }
    }

    // --- Вспомогательные функции для получения данных персонажа из data.js ---
    // Скопировано из gameManager.js, т.к. GameInstance тоже использует gameData напрямую
    /**
     * Получает базовые статы и список способностей для персонажа по ключу.
     * Эти функции предназначены для использования ВНУТРИ GameManager или GameInstance.
     * @param {string} key - Ключ персонажа ('elena', 'balard', 'almagest').
     * @returns {{baseStats: object, abilities: array}|null} Объект с базовыми статами и способностями, или null.
     */
    _getCharacterData(key) {
        if (!key) { console.warn("GameInstance::_getCharacterData called with null/undefined key."); return null; }
        switch (key) {
            case 'elena': return { baseStats: gameData.playerBaseStats, abilities: gameData.playerAbilities };
            case 'balard': return { baseStats: gameData.opponentBaseStats, abilities: gameData.opponentAbilities }; // Балард использует opponentAbilities из data.js
            case 'almagest': return { baseStats: gameData.almagestBaseStats, abilities: gameData.almagestAbilities }; // Альмагест использует almagestAbilities из data.js
            default: console.error(`GameInstance::_getCharacterData: Unknown character key "${key}"`); return null;
        }
    }
    /**
     * Получает только базовые статы для персонажа по ключу.
     * @param {string} key - Ключ персонажа.
     * @returns {object|null} Базовые статы или null.
     */
    _getCharacterBaseData(key) {
        const charData = this._getCharacterData(key);
        return charData ? charData.baseStats : null;
    }
    /**
     * Получает только список способностей для персонажа по ключу.
     * @param {string} key - Ключ персонажа.
     * @returns {array|null} Список способностей или null.
     */
    _getCharacterAbilities(key) {
        const charData = this._getCharacterData(key);
        return charData ? charData.abilities : null;
    }
}

module.exports = GameInstance;