// /server_modules/gameLogic.js
const GAME_CONFIG = require('./config');
const gameData = require('./data'); // Загружаем один раз на уровне модуля

// --- Вспомогательные Функции для gameLogic ---

// Вспомогательная функция для получения данных персонажа (baseStats и abilities)
// Нужна здесь, так как объект gameData сам по себе не имеет этих методов.
// Принимает gameDataForLogic как аргумент для гибкости, по умолчанию использует глобальный gameData.
function _getCharacterDataForLogic(key, gameDataForLogic = gameData) {
    if (!key) return null;
    switch (key) {
        case 'elena': return { baseStats: gameDataForLogic.playerBaseStats, abilities: gameDataForLogic.playerAbilities };
        case 'balard': return { baseStats: gameDataForLogic.opponentBaseStats, abilities: gameDataForLogic.opponentAbilities }; // Балард использует opponentAbilities
        case 'almagest': return { baseStats: gameDataForLogic.almagestBaseStats, abilities: gameDataForLogic.almagestAbilities }; // Альмагест использует almagestAbilities
        default: console.error(`_getCharacterDataForLogic: Неизвестный ключ персонажа "${key}"`); return null;
    }
}
// Вспомогательная функция для получения только базовых статов персонажа
function _getCharacterBaseDataForLogic(key, gameDataForLogic = gameData) {
    const charData = _getCharacterDataForLogic(key, gameDataForLogic);
    return charData ? charData.baseStats : null;
}
// Вспомогательная функция для получения только способностей персонажа
function _getCharacterAbilitiesForLogic(key, gameDataForLogic = gameData) {
    const charData = _getCharacterDataForLogic(key, gameDataForLogic);
    return charData ? charData.abilities : null;
}


/**
 * Обрабатывает активные эффекты (баффы/дебаффы) для бойца в конце его хода.
 * Длительность эффекта уменьшается на 1.
 * Периодические эффекты (DoT, ресурсный дебафф и т.п.) срабатывают, если эффект не "justCast" в этом ходу.
 * @param {Array} effectsArray - Массив активных эффектов бойца.
 * @param {Object} ownerState - Состояние бойца (currentHp, currentResource и т.д.).
 * @param {Object} ownerBaseStats - Базовые статы бойца (включая characterKey).
 * @param {String} ownerId - Технический ID слота бойца ('player' или 'opponent').
 * @param {Object} currentGameState - Полное состояние игры.
 * @param {Function} addToLogCallback - Функция для добавления сообщений в лог игры.
 * @param {Object} configToUse - Конфигурационный объект игры.
 * @param {Object} gameDataForLogic - Полный объект gameData (для доступа к способностям и т.д.).
 * @returns {void} - Модифицирует effectsArray и ownerState напрямую.
 */
function processEffects(effectsArray, ownerState, ownerBaseStats, ownerId, currentGameState, addToLogCallback, configToUse, gameDataForLogic = gameData) {
    if (!effectsArray) return;
    const ownerName = ownerBaseStats.name;

    let effectsToRemoveIndexes = [];

    // Важно: Сначала обрабатываем эффекты, затем уменьшаем длительность, затем удаляем.
    for (let i = 0; i < effectsArray.length; i++) {
        const eff = effectsArray[i];

        // --- Применяем эффект (DoT, сжигание ресурса и т.п.), если он не только что наложен в этом ходу ---
        if (!eff.justCast) {
            // Обработка урона от эффектов полного безмолвия (Гипнотический Взгляд, Раскол Разума)
            // Эти эффекты наносят урон цели В КОНЦЕ ее хода
            if (eff.isFullSilence && typeof eff.power === 'number' && eff.power > 0) {
                const damage = eff.power;
                // ИСПРАВЛЕНО: Округляем результат вычитания HP
                ownerState.currentHp = Math.max(0, Math.round(ownerState.currentHp - damage));
                if (addToLogCallback) addToLogCallback(`😵 Эффект "${eff.name}" наносит ${damage} урона ${ownerName}!`, configToUse.LOG_TYPE_DAMAGE);
            }

            // Обработка сжигания ресурса (Печать Слабости, Проклятие Увядания)
            // Эти эффекты сжигают ресурс цели В КОНЦЕ ее хода
            if ((eff.id === 'effect_' + configToUse.ABILITY_ID_SEAL_OF_WEAKNESS || eff.id === 'effect_' + configToUse.ABILITY_ID_ALMAGEST_DEBUFF) && eff.power > 0) {
                const resourceToBurn = eff.power;
                if (ownerState.currentResource > 0) {
                    const actualBurn = Math.min(ownerState.currentResource, resourceToBurn);
                    // ИСПРАВЛЕНО: Округляем результат вычитания ресурса
                    ownerState.currentResource = Math.max(0, Math.round(ownerState.currentResource - actualBurn));
                    if (addToLogCallback) addToLogCallback(`🔥 Эффект "${eff.name}" сжигает ${actualBurn} ${ownerBaseStats.resourceName} у ${ownerName}!`, configToUse.LOG_TYPE_EFFECT);
                }
            }
            // Примечание: Отложенные эффекты (например, Сила Природы) применяют свою силу в gameInstance.processPlayerAction (после атаки), а не здесь.
        }

        // --- Уменьшаем длительность ---
        eff.turnsLeft--;
        eff.justCast = false; // Эффект больше не считается "just cast" после обработки этого хода

        // --- Отмечаем для удаления, если длительность закончилась ---
        if (eff.turnsLeft <= 0) {
            effectsToRemoveIndexes.push(i);
            if (addToLogCallback) {
                addToLogCallback(`Эффект "${eff.name}" на ${ownerName} закончился.`, configToUse.LOG_TYPE_EFFECT);
            }
        }
    }

    // Удаляем эффекты с конца, чтобы не нарушить индексы
    for (let i = effectsToRemoveIndexes.length - 1; i >= 0; i--) {
        effectsArray.splice(effectsToRemoveIndexes[i], 1);
    }
}


/**
 * Обрабатывает отсчет для отключенных (заглушенных) способностей игрока в конце его хода.
 * Длительность заглушения уменьшается на 1.
 * @param {Array<object>} disabledAbilitiesArray - Массив объектов заглушенных способностей.
 * @param {Array<object>} characterAbilities - Полный список способностей персонажа (для получения имени).
 * @param {string} characterName - Имя персонажа (для лога).
 * @param {function} addToLogCallback - Функция для добавления лога.
 * @returns {void} - Модифицирует disabledAbilitiesArray напрямую.
 */
function processDisabledAbilities(disabledAbilitiesArray, characterAbilities, characterName, addToLogCallback) {
    if (!disabledAbilitiesArray || disabledAbilitiesArray.length === 0) return;
    const stillDisabled = [];
    disabledAbilitiesArray.forEach(dis => {
        dis.turnsLeft--; // Уменьшаем длительность заглушения
        if (dis.turnsLeft > 0) {
            stillDisabled.push(dis);
        } else {
            if (addToLogCallback) {
                const ability = characterAbilities.find(ab => ab.id === dis.abilityId);
                // Проверка на заглушающий эффект тоже должна быть удалена из activeEffects в processEffects
                // Здесь мы только обрабатываем список disabledAbilities, удаляя запись
                if (ability) addToLogCallback(`Способность ${characterName} "${ability.name}" больше не заглушена!`, GAME_CONFIG.LOG_TYPE_INFO);
            }
        }
    });
    // Очищаем исходный массив и добавляем только те, что еще активны
    disabledAbilitiesArray.length = 0;
    disabledAbilitiesArray.push(...stillDisabled);
}

/**
 * Обрабатывает отсчет общих кулдаунов для способностей в конце хода.
 * Длительность кулдауна уменьшается на 1.
 * @param {object} cooldownsObject - Объект с кулдаунами способностей ({ abilityId: turnsLeft }).
 * @param {Array<object>} abilitiesList - Полный список способностей персонажа (для получения имени).
 * @param {string} ownerName - Имя персонажа (для лога).
 * @param {function} addToLogCallback - Функция для добавления лога.
 * @returns {void} - Модифицирует cooldownsObject напрямую.
 */
function processPlayerAbilityCooldowns(cooldownsObject, abilitiesList, ownerName, addToLogCallback) {
    if (!cooldownsObject || !abilitiesList) return;
    for (const abilityId in cooldownsObject) {
        if (cooldownsObject.hasOwnProperty(abilityId) && cooldownsObject[abilityId] > 0) {
            cooldownsObject[abilityId]--; // Уменьшаем кулдаун
            if (cooldownsObject[abilityId] === 0) {
                const ability = abilitiesList.find(ab => ab.id === abilityId);
                if (ability && addToLogCallback) {
                    addToLogCallback(`Способность ${ownerName} "${ability.name}" снова готова!`, GAME_CONFIG.LOG_TYPE_INFO);
                }
            }
        }
    }
}

/**
 * Обновляет статус 'isBlocking' на основе активных эффектов.
 * @param {object} fighterState - Состояние бойца.
 * @returns {void} - Модифицирует fighterState.isBlocking.
 */
function updateBlockingStatus(fighterState) {
    if (!fighterState) return;
    // Боец считается блокирующим, если у него есть активный эффект, дающий блок (grantsBlock: true) с turnsLeft > 0
    fighterState.isBlocking = fighterState.activeEffects.some(eff => eff.grantsBlock && eff.turnsLeft > 0);
}


/**
 * Получает случайную насмешку из системы насмешек для определенного персонажа.
 * Ищет фразу в gameData.tauntSystem[speakerCharacterKey][opponentCharacterKey][trigger][context].
 * @param {string} speakerCharacterKey - Ключ персонажа, который произносит насмешку ('elena' или 'almagest' или 'balard').
 * @param {string} trigger - Тип события, вызвавшего насмешку (например, 'selfCastAbility', 'onOpponentAction', 'battleStart', 'basicAttack', 'opponentNearDefeatCheck').
 * @param {object} context - Дополнительный контекст (например, { abilityId: 'fireball' }, { outcome: 'success' }).
 * @param {object} configToUse - Конфигурационный объект игры.
 * @param {object} gameDataForLogic - Полный объект gameData.
 * @param {object} currentGameState - Текущее состояние игры.
 * @returns {string} Текст насмешки или "(Молчание)".
 */
function getRandomTaunt(speakerCharacterKey, trigger, context = {}, configToUse, gameDataForLogic = gameData, currentGameState) {
    // Проверяем наличие системы насмешек для говорящего персонажа
    const speakerTauntSystem = gameDataForLogic?.tauntSystem?.[speakerCharacterKey];
    if (!speakerTauntSystem) return "(Молчание)"; // Нет насмешек для этого персонажа

    // Определяем противника, чтобы выбрать соответствующую ветку насмешек
    // Для этого нужно найти в gameState, кто из player/opponent имеет characterKey говорящего,
    // и взять characterKey другого.
    const speakerRole = currentGameState?.player?.characterKey === speakerCharacterKey ?
        GAME_CONFIG.PLAYER_ID :
        (currentGameState?.opponent?.characterKey === speakerCharacterKey ?
            GAME_CONFIG.OPPONENT_ID : null);

    if (speakerRole === null) {
        console.warn(`getRandomTaunt: Speaker character key "${speakerCharacterKey}" not found in current game state roles.`);
        return "(Молчание)";
    }

    const opponentRole = speakerRole === GAME_CONFIG.PLAYER_ID ? GAME_CONFIG.OPPONENT_ID : GAME_CONFIG.PLAYER_ID;
    const opponentCharacterKey = currentGameState?.[opponentRole]?.characterKey;

    const tauntBranch = speakerTauntSystem[opponentCharacterKey];
    if (!tauntBranch) {
        // console.warn(`getRandomTaunt: No taunt branch found for speaker "${speakerCharacterKey}" against opponent "${opponentCharacterKey}".`);
        return "(Молчание)"; // Нет насмешек против этого оппонента
    }


    let potentialTaunts = [];

    // Навигация по структуре tauntSystem в зависимости от триггера и контекста
    if (trigger === 'battleStart') {
        potentialTaunts = tauntBranch.onBattleState?.start;
    }
    else if (trigger === 'opponentNearDefeatCheck') { // Проверка на низкое HP противника для специальных фраз
        const opponentState = currentGameState?.[opponentRole]; // Состояние противника для проверки HP
        // Проверяем, что состояние оппонента существует и его HP ниже порога (например, 20%)
        if (opponentState && opponentState.maxHp > 0 && opponentState.currentHp / opponentState.maxHp < 0.20) {
            potentialTaunts = tauntBranch.onBattleState?.opponentNearDefeat;
        }
    }
    else if (trigger === 'selfCastAbility' && context.abilityId) {
        potentialTaunts = tauntBranch.selfCastAbility?.[context.abilityId];
    }
    else if (trigger === 'basicAttack' && tauntBranch.basicAttack) {
        const opponentState = currentGameState?.[opponentRole]; // Состояние противника
        // Специальная логика для базовой атаки Елены против Баларда (милосердие/доминирование)
        if (speakerCharacterKey === 'elena' && opponentCharacterKey === 'balard' && opponentState) {
            const opponentHpPerc = (opponentState.currentHp / opponentState.maxHp) * 100;
            if (opponentHpPerc <= configToUse.PLAYER_MERCY_TAUNT_THRESHOLD_PERCENT) {
                potentialTaunts = tauntBranch.basicAttack.dominating;
            } else {
                potentialTaunts = tauntBranch.basicAttack.merciful;
            }
        } else { // Общая логика для PvP или Елена/Балард вне порога
            potentialTaunts = tauntBranch.basicAttack.general;
        }
    }
    // Реакция на действие противника
    else if (trigger === 'onOpponentAction' && context.abilityId) {
        const actionResponses = tauntBranch.onOpponentAction?.[context.abilityId];
        if (actionResponses) {
            // Если структура содержит вложенные результаты (например, успех/провал Безмолвия)
            if (typeof actionResponses === 'object' && !Array.isArray(actionResponses) && context.outcome && context.outcome in actionResponses) {
                potentialTaunts = actionResponses[context.outcome]; // Например, onOpponentAction.silence.success
            } else if (Array.isArray(actionResponses)) {
                potentialTaunts = actionResponses; // Прямой массив фраз для способности
            }
        }
    }
        // Реакция на попадание/блок атаки противника
        // Примечание: Эти триггеры срабатывают, когда по ГОВОРЯЩЕМУ попала атака или он ее заблокировал.
    // Вызываются из performAttack, где известно, кто атакует и кто защищается.
    else if (trigger === 'onOpponentAttackBlocked' && tauntBranch.onOpponentAction?.attackBlocked) {
        potentialTaunts = tauntBranch.onOpponentAction.attackBlocked;
    }
    else if (trigger === 'onOpponentAttackHit' && tauntBranch.onOpponentAction?.attackHits) {
        potentialTaunts = tauntBranch.onOpponentAction.attackHits;
    }

    // Если по прямому триггеру не найдено, возвращаем "(Молчание)".
    // Можно добавить фоллбэк на общие фразы, если требуется более разговорчивый персонаж.
    // Например: if ((!potentialTaunts || potentialTaunts.length === 0) && tauntBranch.basicAttack?.general) { potentialTaunts = tauntBranch.basicAttack.general; }

    if (!Array.isArray(potentialTaunts) || potentialTaunts.length === 0) {
        return "(Молчание)"; // Возвращаем молчание, если ничего не найдено
    }

    // Возвращаем случайную фразу из найденного массива
    const selectedTaunt = potentialTaunts[Math.floor(Math.random() * potentialTaunts.length)];
    return selectedTaunt || "(Молчание)"; // Фоллбэк на "(Молчание)" если массив был пуст после всех проверок
}


// --- Основные Игровые Функции ---

/**
 * Обрабатывает базовую атаку одного бойца по другому.
 * @param {object} attackerState - Состояние атакующего бойца.
 * @param {object} defenderState - Состояние защищающегося бойца.
 * @param {object} attackerBaseStats - Базовые статы атакующего.
 * @param {object} defenderBaseStats - Базовые статы защищающегося.
 * @param {object} currentGameState - Текущее состояние игры (для насмешек).
 * @param {function} addToLogCallback - Функция для добавления лога.
 * @param {object} configToUse - Конфигурация игры.
 * @param {object} gameDataForLogic - Данные игры (для насмешек).
 */
function performAttack(attackerState, defenderState, attackerBaseStats, defenderBaseStats, currentGameState, addToLogCallback, configToUse, gameDataForLogic = gameData) {
    // Расчет базового урона с вариацией
    let damage = Math.floor(attackerBaseStats.attackPower * (configToUse.DAMAGE_VARIATION_MIN + Math.random() * configToUse.DAMAGE_VARIATION_RANGE));
    let tauntMessagePart = ""; // Переменная для насмешки защищающегося

    // Проверка на блок
    if (defenderState.isBlocking) {
        const initialDamage = damage;
        damage = Math.floor(damage * configToUse.BLOCK_DAMAGE_REDUCTION);
        // Проверка на насмешку ОТ защищающегося (Елены или Альмагест) при блокировании атаки
        if (defenderState.characterKey === 'elena' || defenderState.characterKey === 'almagest') {
            // getRandomTaunt принимает speaker (защищающийся), trigger, context, config, gameData, gameState
            const blockTaunt = getRandomTaunt(defenderState.characterKey, 'onOpponentAttackBlocked', {}, configToUse, gameDataForLogic, currentGameState);
            if (blockTaunt !== "(Молчание)") tauntMessagePart = ` (${blockTaunt})`;
        }

        if (addToLogCallback) addToLogCallback(`🛡️ ${defenderBaseStats.name} блокирует атаку ${attackerBaseStats.name}! Урон снижен (${initialDamage} -> ${damage}).${tauntMessagePart}`, configToUse.LOG_TYPE_BLOCK);
    } else {
        let hitMessage = `${attackerBaseStats.name} атакует ${defenderBaseStats.name}! Наносит ${damage} урона.`;
        // Проверка на насмешку ОТ защищающегося (Елены или Альмагест) при попадании атаки
        if (defenderState.characterKey === 'elena' || defenderState.characterKey === 'almagest') {
            // getRandomTaunt принимает speaker (защищающийся), trigger, context, config, gameData, gameState
            const hitTaunt = getRandomTaunt(defenderState.characterKey, 'onOpponentAttackHit', {}, configToUse, gameDataForLogic, currentGameState);
            if (hitTaunt !== "(Молчание)") hitMessage += ` (${hitTaunt})`;
        }
        if (addToLogCallback) addToLogCallback(hitMessage, configToUse.LOG_TYPE_DAMAGE);
    }
    // Применяем урон, убеждаемся, что HP не ниже нуля
    // ИСПРАВЛЕНО: Округляем результат вычитания HP
    defenderState.currentHp = Math.max(0, Math.round(defenderState.currentHp - damage));
}

/**
 * Применяет эффект способности.
 * @param {object} ability - Объект способности.
 * @param {object} casterState - Состояние бойца, применившего способность.
 * @param {object} targetState - Состояние цели способности.
 * @param {object} casterBaseStats - Базовые статы кастера.
 * @param {object} targetBaseStats - Базовые статы цели.
 * @param {object} currentGameState - Текущее состояние игры (для насмешек).
 * @param {function} addToLogCallback - Функция для добавления лога.
 * @param {object} configToUse - Конфигурация игры.
 * @param {object} gameDataForLogic - Данные игры (для насмешек).
 */
function applyAbilityEffect(ability, casterState, targetState, casterBaseStats, targetBaseStats, currentGameState, addToLogCallback, configToUse, gameDataForLogic = gameData) {
    let tauntMessagePart = ""; // Переменная для насмешки, если она связана с результатом эффекта или реакцией цели

    // Проверка на насмешку ОТ цели (Елены или Альмагест), если она попадает под способность противника
    if (targetState.characterKey === 'elena' || targetState.characterKey === 'almagest') {
        // Триггер 'onOpponentAction' с abilityId противника
        const reactionTaunt = getRandomTaunt(targetState.characterKey, 'onOpponentAction', { abilityId: ability.id }, configToUse, gameDataForLogic, currentGameState);
        if (reactionTaunt !== "(Молчание)") tauntMessagePart = ` (${reactionTaunt})`;
    } else {
        tauntMessagePart = ""; // Другие персонажи (Балард) не имеют реакционных насмешек такого типа
    }


    switch (ability.type) {
        case configToUse.ACTION_TYPE_HEAL:
            const healAmount = Math.floor(ability.power * (configToUse.HEAL_VARIATION_MIN + Math.random() * configToUse.HEAL_VARIATION_RANGE));
            const actualHeal = Math.min(healAmount, casterBaseStats.maxHp - casterState.currentHp);
            if (actualHeal > 0) {
                // ИСПРАВЛЕНО: Округляем результат прибавления HP
                casterState.currentHp = Math.round(casterState.currentHp + actualHeal);
                // --- ИЗМЕНЕНИЕ: Добавляем название способности в лог лечения ---
                if (addToLogCallback) addToLogCallback(`💚 ${casterBaseStats.name} применяет "${ability.name}" и восстанавливает ${actualHeal} HP!${tauntMessagePart}`, configToUse.LOG_TYPE_HEAL);
                // --- КОНЕЦ ИЗМЕНЕНИЯ ---
            } else {
                if (addToLogCallback) addToLogCallback(`✨ ${casterBaseStats.name} применяет "${ability.name}", но не получает лечения.${tauntMessagePart}`, configToUse.LOG_TYPE_INFO);
            }
            break;

        case configToUse.ACTION_TYPE_DAMAGE:
            let damage = Math.floor(ability.power * (configToUse.DAMAGE_VARIATION_MIN + Math.random() * configToUse.DAMAGE_VARIATION_RANGE));
            // Проверка на блок цели
            if (targetState.isBlocking) {
                const initialDamage = damage;
                damage = Math.floor(damage * configToUse.BLOCK_DAMAGE_REDUCTION);
                // Проверка на насмешку ОТ цели (Елены или Альмагест), если она заблокировала урон от способности - перенесено наверх
                // if (targetState.characterKey === 'elena' || targetState.characterKey === 'almagest') {
                //     const blockTaunt = getRandomTaunt(targetState.characterKey, 'onOpponentAttackBlocked', {abilityId: ability.id} , configToUse, gameDataForLogic, currentGameState);
                //     if (blockTaunt !== "(Молчание)") tauntMessagePart = ` (${blockTaunt})`;
                // }
                if (addToLogCallback) addToLogCallback(`🛡️ ${targetBaseStats.name} блокирует "${ability.name}" от ${casterBaseStats.name}! Урон снижен (${initialDamage} -> ${damage}).${tauntMessagePart}`, configToUse.LOG_TYPE_BLOCK);
            }
            // Применяем урон, убеждаемся, что HP не ниже нуля
            // ИСПРАВЛЕНО: Округляем результат вычитания HP
            targetState.currentHp = Math.max(0, Math.round(targetState.currentHp - damage));
            if (addToLogCallback && !targetState.isBlocking) {
                let hitMessage = `💥 ${casterBaseStats.name} применяет "${ability.name}" на ${targetBaseStats.name}, нанося ${damage} урона!${tauntMessagePart}`;
                // Проверка на насмешку ОТ цели (Елены или Альмагест), если по ней попала способность - перенесено наверх
                // if (targetState.characterKey === 'elena' || targetState.characterKey === 'almagest') {
                //     const hitTaunt = getRandomTaunt(targetState.characterKey, 'onOpponentAction', {abilityId: ability.id}, configToUse, gameDataForLogic, currentGameState);
                //     if (hitTaunt !== "(Молчание)") hitMessage += ` (${hitTaunt})`;
                // }
                addToLogCallback(hitMessage, configToUse.LOG_TYPE_DAMAGE);
            }
            break;

        case configToUse.ACTION_TYPE_BUFF:
            // Если бафф уже активен, не применяем его повторно (эта проверка уже есть в gameInstance)
            // Проверка на .some здесь опциональна, т.к. вызывающий код должен гарантировать уникальность
            if (!casterState.activeEffects.some(e => e.id === ability.id)) {
                let effectDescription = ability.description;
                if (typeof ability.descriptionFunction === 'function') {
                    // Для описания баффа может потребоваться информация о противнике
                    const opponentRole = casterState.id === configToUse.PLAYER_ID ? configToUse.OPPONENT_ID : configToUse.PLAYER_ID;
                    const opponentCurrentState = currentGameState[opponentRole];
                    // Получаем базовые статы противника, если он определен, для функции описания
                    const opponentDataForDesc = opponentCurrentState?.characterKey ? _getCharacterBaseDataForLogic(opponentCurrentState.characterKey, gameDataForLogic) : null; // ИСПОЛЬЗУЕМ _getCharacterBaseDataForLogic
                    effectDescription = ability.descriptionFunction(configToUse, opponentDataForDesc);
                }
                // isDelayed: true используется для эффектов, которые срабатывают ПОСЛЕ следующего действия (например, Сила Природы).
                // duration: исходная длительность из данных, turnsLeft: сколько ходов осталось (включая текущий, если !justCast)
                casterState.activeEffects.push({
                    id: ability.id, name: ability.name, description: effectDescription,
                    type: ability.type, duration: ability.duration, // Сохраняем исходную длительность для отображения в UI или логики
                    turnsLeft: ability.duration, // Длительность жизни эффекта в ходах владельца
                    grantsBlock: !!ability.grantsBlock,
                    isDelayed: !!ability.isDelayed, // Флаг, что эффект отложенный (срабатывает после действия)
                    justCast: true // Флаг, что эффект только что наложен (для логики processEffects)
                });
                if (ability.grantsBlock) updateBlockingStatus(casterState); // Обновляем статус блока кастера, если бафф его дает
                // Насмешки при применении баффа (selfCastAbility) добавляются в GameInstance перед вызовом applyAbilityEffect
                if (addToLogCallback) addToLogCallback(`✨ ${casterBaseStats.name} накладывает эффект "${ability.name}"!${tauntMessagePart}`, configToUse.LOG_TYPE_EFFECT);
            } else {
                // Сообщение "уже активен" отправляется из gameInstance перед вызовом applyAbilityEffect
            }
            break;

        case configToUse.ACTION_TYPE_DISABLE: // Безмолвие, Стан и т.п.
            // Проверка на насмешку ОТ цели (Елены или Альмагест), если она попадает под дизейбл противника - перенесено наверх
            // if (targetState.characterKey === 'elena' || targetState.characterKey === 'almagest') {
            //     const disableTaunt = getRandomTaunt(targetState.characterKey, 'onOpponentAction', {abilityId: ability.id}, configToUse, gameDataForLogic, currentGameState);
            //     if (disableTaunt !== "(Молчание)") tauntMessagePart = ` (${disableTaunt})`;
            // }

            // Гипнотический взгляд Елены / Раскол Разума Альмагест (полное безмолвие)
            if (ability.id === configToUse.ABILITY_ID_HYPNOTIC_GAZE || ability.id === configToUse.ABILITY_ID_ALMAGEST_DISABLE) {
                const effectId = ability.id === configToUse.ABILITY_ID_HYPNOTIC_GAZE ? 'fullSilenceByElena' : 'fullSilenceByAlmagest';
                // Не накладываем повторно, если эффект уже есть на цели
                if (!targetState.activeEffects.some(e => e.id === effectId)) {
                    targetState.activeEffects.push({
                        id: effectId, name: ability.name, description: ability.description,
                        type: ability.type, duration: ability.effectDuration, turnsLeft: ability.effectDuration, // Длительность в ходах цели
                        power: ability.power, // Урон от эффекта (применяется в конце хода цели)
                        isFullSilence: true,
                        justCast: true // Эффект только что наложен
                    });
                    if (addToLogCallback) addToLogCallback(`🌀 ${casterBaseStats.name} применяет "${ability.name}" на ${targetBaseStats.name}! Способности ${targetBaseStats.name} заблокированы на ${ability.effectDuration} хода, и он(а) получает урон!${tauntMessagePart}`, configToUse.LOG_TYPE_EFFECT);
                } else {
                    if (addToLogCallback) addToLogCallback(`${casterBaseStats.name} пытается применить "${ability.name}", но эффект уже активен на ${targetState.name}!${tauntMessagePart}`, configToUse.LOG_TYPE_INFO);
                }
            }
            // Эхо Безмолвия Баларда (заглушает случайную абилку)
            else if (ability.id === configToUse.ABILITY_ID_BALARD_SILENCE && casterState.characterKey === 'balard') {
                const success = Math.random() < configToUse.SILENCE_SUCCESS_RATE;
                const silenceOutcome = success ? 'success' : 'fail';
                // Реакция цели (Елены) на успех/провал безмолвия Баларда - перенесено наверх, но с context.outcome
                // if (targetState.characterKey === 'elena') { // Балард применяет это только на Елену
                //     const specificSilenceTaunt = getRandomTaunt(targetState.characterKey, 'onOpponentAction', { abilityId: ability.id, outcome: silenceOutcome }, configToUse, gameDataForLogic, currentGameState);
                //     tauntMessagePart = (specificSilenceTaunt !== "(Молчание)") ? ` (${specificSilenceTaunt})` : "";
                // } else {
                //     tauntMessagePart = ""; // Другие персонажи не реагируют на Безмолвие Баларда
                // }

                // Нужно получить насмешку с outcome здесь, так как она зависит от результата броска шанса
                // Временно сохраняем общую насмешку и получаем специфичную
                let specificSilenceTaunt = "(Молчание)";
                if (targetState.characterKey === 'elena') { // Балард применяет это только на Елену
                    specificSilenceTaunt = getRandomTaunt(targetState.characterKey, 'onOpponentAction', { abilityId: ability.id, outcome: silenceOutcome }, configToUse, gameDataForLogic, currentGameState);
                }
                tauntMessagePart = (specificSilenceTaunt !== "(Молчание)") ? ` (${specificSilenceTaunt})` : tauntMessagePart; // Используем специфичную, если найдена, иначе общую (хотя общая для этого абилки вряд ли есть)


                if (success) {
                    const targetAbilities = _getCharacterAbilitiesForLogic(targetState.characterKey, gameDataForLogic); // Глушим абилки цели
                    // Фильтруем способности, которые еще не заглушены этим типом безмолвия
                    const availableAbilities = targetAbilities.filter(pa =>
                        !targetState.disabledAbilities?.some(d => d.abilityId === pa.id) &&
                        !targetState.activeEffects?.some(eff => eff.id === `playerSilencedOn_${pa.id}`) // Проверка на эффект заглушения для UI/ProcessEffects
                    );

                    if (availableAbilities.length > 0) {
                        const abilityToSilence = availableAbilities[Math.floor(Math.random() * availableAbilities.length)];
                        const turns = configToUse.SILENCE_DURATION; // Длительность из конфига (в ходах цели)
                        // Добавляем запись о заглушенной способности в disabledAbilities цели
                        targetState.disabledAbilities.push({ abilityId: abilityToSilence.id, turnsLeft: turns + 1 }); // +1, т.к. длительность уменьшается в конце хода цели
                        // Добавляем эффект заглушения в activeEffects цели (для UI и ProcessEffects)
                        const silenceEffectIdOnPlayer = `playerSilencedOn_${abilityToSilence.id}`;
                        targetState.activeEffects.push({
                            id: silenceEffectIdOnPlayer, name: `Безмолвие: ${abilityToSilence.name}`,
                            description: `Способность "${abilityToSilence.name}" временно недоступна.`,
                            type: configToUse.ACTION_TYPE_DISABLE, sourceAbilityId: ability.id, // Добавлено sourceAbilityId
                            duration: turns, turnsLeft: turns + 1,
                            justCast: true // Эффект только что наложен
                        });
                        if (addToLogCallback) addToLogCallback(`🔇 Эхо Безмолвия! "${abilityToSilence.name}" ${targetBaseStats.name} заблокировано на ${turns} хода!${tauntMessagePart}`, configToUse.LOG_TYPE_EFFECT);
                    } else {
                        if (addToLogCallback) addToLogCallback(`${casterBaseStats.name} пытается наложить Безмолвие, но у ${targetBaseStats.name} нечего глушить!${tauntMessagePart}`, configToUse.LOG_TYPE_INFO);
                    }
                } else {
                    if (addToLogCallback) addToLogCallback(`💨 Попытка ${casterBaseStats.name} наложить Безмолвие на ${targetBaseStats.name} провалилась!${tauntMessagePart}`, configToUse.LOG_TYPE_INFO);
                }
            }
            break;

        case configToUse.ACTION_TYPE_DEBUFF: // Ослабления, DoT и т.п.
            // Проверка на насмешку ОТ цели (Елены или Альмагест), если она попадает под дебафф противника - перенесено наверх
            // if (targetState.characterKey === 'elena' || targetState.characterKey === 'almagest') {
            //     const debuffTaunt = getRandomTaunt(targetState.characterKey, 'onOpponentAction', {abilityId: ability.id}, configToUse, gameDataForLogic, currentGameState);
            //     if (debuffTaunt !== "(Молчание)") tauntMessagePart = ` (${debuffTaunt})`;
            // }

            // Печать Слабости Елены / Проклятие Увядания Альмагест (сжигание ресурса)
            if (ability.id === configToUse.ABILITY_ID_SEAL_OF_WEAKNESS || ability.id === configToUse.ABILITY_ID_ALMAGEST_DEBUFF) {
                const effectIdForDebuff = 'effect_' + ability.id; // Уникальный ID эффекта на цели
                // Не накладываем повторно, если эффект уже есть на цели
                if (!targetState.activeEffects.some(e => e.id === effectIdForDebuff)) {
                    let effectDescription = ability.description;
                    if (typeof ability.descriptionFunction === 'function') {
                        effectDescription = ability.descriptionFunction(configToUse, targetBaseStats); // Описание может зависеть от цели
                    }
                    targetState.activeEffects.push({
                        id: effectIdForDebuff, name: ability.name, description: effectDescription,
                        type: configToUse.ACTION_TYPE_DEBUFF, sourceAbilityId: ability.id,
                        duration: ability.effectDuration, turnsLeft: ability.effectDuration, // Длительность в ходах цели
                        power: ability.power, // Количество сжигаемого ресурса в ход (применяется в конце хода цели)
                        justCast: true // Эффект только что наложен
                    });
                    if (addToLogCallback) addToLogCallback(`📉 ${casterBaseStats.name} накладывает "${ability.name}" на ${targetBaseStats.name}! Ресурс будет сжигаться.${tauntMessagePart}`, configToUse.LOG_TYPE_EFFECT);
                } else {
                    if (addToLogCallback) addToLogCallback(`${casterBaseStats.name} пытается применить "${ability.name}", но эффект уже активен на ${targetState.name}!${tauntMessagePart}`, configToUse.LOG_TYPE_INFO);
                }
            }
            break;

        case configToUse.ACTION_TYPE_DRAIN: // Похищение Света Баларда (наносит урон, вытягивает ресурс, лечит кастера)
            if (casterState.characterKey === 'balard') { // Это способность только Баларда
                let manaDrained = 0; let healthGained = 0; let damageDealtDrain = 0;
                // tauntMessagePart уже получена в начале функции

                // Сначала урон от способности
                if (ability.powerDamage > 0) {
                    let baseDamageDrain = ability.powerDamage;
                    // Проверка на блок цели
                    if (targetState.isBlocking) {
                        baseDamageDrain = Math.floor(baseDamageDrain * configToUse.BLOCK_DAMAGE_REDUCTION);
                        let blockDrainTaunt = "";
                        // Реакция цели (Елены/Альмагест) на блок урона от дрейна
                        if (targetState.characterKey === 'elena' || targetState.characterKey === 'almagest') {
                            blockDrainTaunt = getRandomTaunt(targetState.characterKey, 'onOpponentAttackBlocked', {}, configToUse, gameDataForLogic, currentGameState);
                            if (blockDrainTaunt !== "(Молчание)") blockDrainTaunt = ` (${blockDrainTaunt})`;
                        }
                        if (addToLogCallback) addToLogCallback(`🛡️ ${targetBaseStats.name} блокирует часть урона от "${ability.name}" от ${casterBaseStats.name}! Урон снижен до ${baseDamageDrain}.${blockDrainTaunt}`, configToUse.LOG_TYPE_BLOCK);
                    }
                    damageDealtDrain = Math.max(0, baseDamageDrain);
                    // ИСПРАВЛЕНО: Округляем результат вычитания HP
                    targetState.currentHp = Math.max(0, Math.round(targetState.currentHp - damageDealtDrain));
                }

                // Затем вытягивание ресурса и лечение кастера
                const potentialDrain = ability.powerManaDrain;
                const actualDrain = Math.min(potentialDrain, targetState.currentResource);

                if (actualDrain > 0) {
                    // ИСПРАВЛЕНО: Округляем результат вычитания ресурса
                    targetState.currentResource = Math.max(0, Math.round(targetState.currentResource - actualDrain));
                    manaDrained = actualDrain;
                    const potentialHeal = Math.floor(manaDrained * ability.powerHealthGainFactor);
                    const actualHealGain = Math.min(potentialHeal, casterBaseStats.maxHp - casterState.currentHp);
                    // ИСПРАВЛЕНО: Округляем результат прибавления HP
                    casterState.currentHp = Math.round(casterState.currentHp + actualHealGain);
                    healthGained = actualHealGain;
                }

                let logMsgDrain = `⚡ ${casterBaseStats.name} применяет "${ability.name}"! `;
                if (damageDealtDrain > 0) logMsgDrain += `Наносит ${damageDealtDrain} урона. `;
                if (manaDrained > 0) {
                    logMsgDrain += `Вытягивает ${manaDrained} ${targetBaseStats.resourceName} у ${targetBaseStats.name} и исцеляется на ${healthGained} HP!`;
                } else if (damageDealtDrain > 0) {
                    logMsgDrain += `У ${targetBaseStats.name} нет ${targetBaseStats.resourceName} для похищения.`; // Урон прошел, но ресурс не вытянулся
                } else { // Ни урона, ни вытягивания ресурса
                    // ИСПРАВЛЕНО: targetBaseStats.resourceName -> targetState.resourceName (или defenderBaseStats.resourceName, если он передается)
                    logMsgDrain += `У ${targetBaseStats.name} нет ${targetBaseStats.resourceName} для похищения.`; // Оставляем targetBaseStats.resourceName, т.к. он точнее для лога
                    // Если урон был 0, и ресурса нет, можно уточнить лог
                    if(damageDealtDrain === 0 && potentialDrain > 0) logMsgDrain += ` Урон не прошел или равен нулю, ресурс не похищен.`;
                }
                logMsgDrain += tauntMessagePart; // Добавляем насмешку цели, если была
                if (addToLogCallback) addToLogCallback(logMsgDrain, manaDrained > 0 || damageDealtDrain > 0 ? configToUse.LOG_TYPE_DAMAGE : configToUse.LOG_TYPE_INFO);
            } else {
                console.warn(`applyAbilityEffect: Drain type ability ${ability?.name} used by non-Balard character ${casterState.characterKey}`);
            }
            break;

        default:
            console.warn(`applyAbilityEffect: Неизвестный тип способности: ${ability?.type} для "${ability?.name}"`);
    }
}

/**
 * Логика принятия решения для AI (Балард).
 * @param {object} currentGameState - Текущее состояние игры.
 * @param {object} gameDataForLogic - Данные игры.
 * @param {object} configToUse - Конфигурация игры.
 * @param {function} addToLogCallback - Функция для добавления лога.
 * @returns {object} Объект с действием AI ({ actionType: 'attack' | 'ability', ability?: object }).
 */
function decideAiAction(currentGameState, gameDataForLogic = gameData, configToUse, addToLogCallback) {
    const opponentState = currentGameState.opponent; // AI Балард всегда в слоте opponent
    const playerState = currentGameState.player;     // Игрок всегда в слоте player (в AI режиме)

    // Убеждаемся, что это AI Балард
    if (opponentState.characterKey !== 'balard') {
        console.warn("[AI DEBUG] decideAiAction called for non-Balard opponent. This should not happen.");
        return { actionType: 'pass', logMessage: { message: `${opponentState.name} (не AI) пропускает ход.`, type: configToUse.LOG_TYPE_INFO } };
    }

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

    if (isBalardFullySilenced) {
        // AI под полным безмолвием просто атакует
        // Лог о безмолвии и атаке в смятении добавляется в processAiTurn перед вызовом performAttack.
        // decideAiAction просто возвращает действие.
        return { actionType: 'attack' };
    }

    const availableActions = [];
    const opponentAbilities = gameDataForLogic.opponentAbilities; // Способности Баларда

    // Проверяем доступность способностей AI и добавляем их в список возможных действий с весом
    // Вес определяет приоритет: выше вес -> выше шанс выбора (после сортировки)

    const healAbility = opponentAbilities.find(a => a.id === configToUse.ABILITY_ID_BALARD_HEAL);
    if (healAbility && opponentState.currentResource >= healAbility.cost &&
        (opponentState.abilityCooldowns?.[healAbility.id] || 0) <= 0 && // Проверка общего КД (хотя у Баларда могут быть только спец. КД)
        healAbility.condition(opponentState, playerState, currentGameState, configToUse)) {
        availableActions.push({ weight: 80, type: 'ability', ability: healAbility, requiresSuccessCheck: true, successRate: healAbility.successRate });
    }

    const silenceAbility = opponentAbilities.find(a => a.id === configToUse.ABILITY_ID_BALARD_SILENCE);
    if (silenceAbility && opponentState.currentResource >= silenceAbility.cost &&
        (opponentState.silenceCooldownTurns === undefined || opponentState.silenceCooldownTurns <= 0) && // Проверка спец. КД безмолвия
        (opponentState.abilityCooldowns?.[silenceAbility.id] || 0) <= 0 && // Проверка общего КД
        silenceAbility.condition(opponentState, playerState, currentGameState, configToUse)) {
        const playerHpPercent = (playerState.currentHp / playerState.maxHp) * 100;
        // Балард предпочитает безмолвие, если HP Елены не слишком низкое (позволяет ей лечиться, чтобы игра длилась дольше)
        if (playerHpPercent > (configToUse.PLAYER_HP_BLEED_THRESHOLD_PERCENT || 60)) { // Используем порог для текстов Елены как пример
            availableActions.push({ weight: 60, type: 'ability', ability: silenceAbility, requiresSuccessCheck: true, successRate: configToUse.SILENCE_SUCCESS_RATE });
        }
    }

    const drainAbility = opponentAbilities.find(a => a.id === configToUse.ABILITY_ID_BALARD_MANA_DRAIN);
    if (drainAbility && opponentState.currentResource >= drainAbility.cost &&
        (opponentState.manaDrainCooldownTurns === undefined || opponentState.manaDrainCooldownTurns <= 0) && // Проверка спец. КД дрейна
        (opponentState.abilityCooldowns?.[drainAbility.id] || 0) <= 0 && // Проверка общего КД
        drainAbility.condition(opponentState, playerState, currentGameState, configToUse)) {
        availableActions.push({ weight: 50, type: 'ability', ability: drainAbility });
    }

    // Базовая атака - всегда доступна как запасной вариант с низким весом
    availableActions.push({ weight: 30, type: 'attack' });

    // Если по какой-то причине список доступных действий пуст (не должно быть, т.к. атака всегда есть)
    if (availableActions.length === 0) {
        return { actionType: 'pass', logMessage: { message: `${opponentState.name} не может совершить действие.`, type: configToUse.LOG_TYPE_INFO } };
    }

    // Сортируем действия по весу в порядке убывания
    availableActions.sort((a, b) => b.weight - a.weight);

    // Перебираем действия в порядке приоритета и выбираем первое возможное
    for (const action of availableActions) {
        if (action.type === 'ability') {
            // Если способность требует проверки успеха (например, Безмолвие Баларда)
            if (action.requiresSuccessCheck) {
                if (Math.random() < action.successRate) {
                    // Успех, добавляем лог о попытке (чтобы было видно, что AI пытался)
                    if (addToLogCallback) addToLogCallback(`⭐ ${opponentState.name} пытается использовать "${action.ability.name}"...`, configToUse.LOG_TYPE_INFO);
                    return { actionType: action.type, ability: action.ability }; // Успех, выбираем эту способность
                } else {
                    // Провал, добавляем лог о провале и переходим к следующему возможному действию в цикле
                    if (addToLogCallback) addToLogCallback(`💨 Попытка ${opponentState.name} использовать "${action.ability.name}" провалилась!`, configToUse.LOG_TYPE_INFO);
                    continue; // Пробуем следующее действие в списке
                }
            } else {
                // Нет проверки успеха, добавляем лог о попытке и выбираем способность
                if (addToLogCallback) addToLogCallback(`⭐ ${opponentState.name} использует "${action.ability.name}"...`, configToUse.LOG_TYPE_INFO);
                return { actionType: action.type, ability: action.ability };
            }
        } else if (action.type === 'attack') {
            // Атака - всегда возможна (если нет полного безмолвия, проверено выше)
            if (addToLogCallback) addToLogCallback(`🦶 ${opponentState.name} готовится к атаке...`, configToUse.LOG_TYPE_INFO);
            return { actionType: 'attack' };
        }
        // 'pass' не должен быть в доступных действиях, если атака всегда доступна
    }

    // Если все попытки выбрать способность или атаку провалились (очень маловероятно, если атака всегда в списке), пропуск хода
    console.warn("[AI DEBUG] AI failed to select any action. Defaulting to pass.");
    return { actionType: 'pass', logMessage: { message: `${opponentState.name} не смог выбрать подходящее действие. Пропускает ход.`, type: configToUse.LOG_TYPE_INFO } };
}

/**
 * Внутренняя проверка условий конца игры (основано на HP).
 * @param {object} currentGameState - Текущее состояние игры.
 * @param {object} configToUse - Конфигурация игры.
 * @param {object} gameDataForLogic - Данные игры.
 * @returns {boolean} true, если игра окончена, иначе false.
 */
function checkGameOverInternal(currentGameState, configToUse, gameDataForLogic = gameData) {
    // Проверка на конец игры происходит только если gameState существует и игра еще не помечена как оконченная
    if (!currentGameState || currentGameState.isGameOver) return currentGameState ? currentGameState.isGameOver : true;

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


    const playerDead = currentGameState.player.currentHp <= 0;
    const opponentDead = currentGameState.opponent.currentHp <= 0;

    // Игра окончена, если один или оба бойца мертвы
    return playerDead || opponentDead;
}

// Экспортируем все функции, которые используются в других модулях
module.exports = {
    processEffects,
    processDisabledAbilities,
    processPlayerAbilityCooldowns,
    updateBlockingStatus,
    getRandomTaunt, // Экспортируем переименованную функцию
    performAttack,
    applyAbilityEffect,
    decideAiAction,
    checkGameOverInternal
};