UnitMutationStateApplier.kt

package io.github.lishangbu.avalon.game.battle.engine.core.runtime.apply

import io.github.lishangbu.avalon.game.battle.engine.core.model.AttachedEffectState
import io.github.lishangbu.avalon.game.battle.engine.core.model.UnitState
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.AddVolatileMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ApplyConditionMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.BoostMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ChangeTypeMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ClearBoostsMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ClearProbeMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ConsumeItemMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.DamageMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ForceSwitchMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.HealMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RemoveConditionMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RemoveStatusMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RemoveVolatileMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RestorePpMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.SetBoostsMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.SetProbeMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.SetStatusMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.SetSwitchBoostCarryMutation
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleStatStageSupport
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.MutationTargetSelectorResolver

/**
 * unit 级 mutation 写回助手。
 *
 * 设计意图:
 * - 把 `DefaultMutationApplier` 里所有单位状态写回逻辑收口到一个独立文件;
 * - 保持 `DefaultMutationApplier` 只负责分发 mutation 类型,而不是继续堆所有具体状态变换细节。
 */
internal object UnitMutationStateApplier {
    fun applyDamage(
        mutation: DamageMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            val damage = calculateAmount(unit.maxHp, mutation.mode, mutation.value)
            unit.copy(currentHp = (unit.currentHp - damage).coerceAtLeast(0))
        }

    fun applyHeal(
        mutation: HealMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            val healing = calculateAmount(unit.maxHp, mutation.mode, mutation.value)
            unit.copy(currentHp = (unit.currentHp + healing).coerceAtMost(unit.maxHp))
        }

    fun applySetStatus(
        mutation: SetStatusMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(
                statusState =
                    AttachedEffectState(
                        effectId = mutation.statusEffectId,
                        sourceId = mutation.sourceId,
                        duration = mutation.duration,
                        effectOrder = unit.statusState?.effectOrder ?: 1,
                    ),
            )
        }

    fun applyRemoveStatus(
        mutation: RemoveStatusMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(statusState = null)
        }

    fun applyAddVolatile(
        mutation: AddVolatileMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            val existingState = unit.volatileStates[mutation.volatileEffectId]
            val nextOrder =
                existingState?.effectOrder
                    ?: ((unit.volatileStates.values.maxOfOrNull(AttachedEffectState::effectOrder) ?: 0) + 1)
            unit.copy(
                volatileStates =
                    unit.volatileStates +
                        (
                            mutation.volatileEffectId to
                                AttachedEffectState(
                                    effectId = mutation.volatileEffectId,
                                    sourceId = mutation.sourceId,
                                    duration = mutation.duration,
                                    effectOrder = nextOrder,
                                )
                        ),
            )
        }

    fun applyRemoveVolatile(
        mutation: RemoveVolatileMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(volatileStates = unit.volatileStates - mutation.volatileEffectId)
        }

    fun applyBoost(
        mutation: BoostMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(
                boosts =
                    BattleStatStageSupport.applyBoostDeltas(
                        currentBoosts = unit.boosts,
                        deltas = mutation.boosts,
                    ),
            )
        }

    fun applyClearBoosts(
        mutation: ClearBoostsMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(boosts = emptyMap())
        }

    fun applySetBoosts(
        mutation: SetBoostsMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(
                boosts = BattleStatStageSupport.normalizeStoredBoosts(mutation.boosts),
            )
        }

    fun applyConsumeItem(
        mutation: ConsumeItemMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(itemId = null)
        }

    fun applyRestorePp(
        mutation: RestorePpMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            if (mutation.moveId == null) {
                unit
            } else {
                val nextPp = unit.movePp.toMutableMap()
                nextPp[mutation.moveId] = (nextPp[mutation.moveId] ?: 0) + mutation.value
                unit.copy(movePp = nextPp)
            }
        }

    fun applyChangeType(
        mutation: ChangeTypeMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(typeIds = mutation.values.toSet())
        }

    fun applyForceSwitch(
        mutation: ForceSwitchMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(forceSwitchRequested = true)
        }

    fun applySetSwitchBoostCarry(
        mutation: SetSwitchBoostCarryMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(
                sessionState =
                    unit.sessionState.copy(
                        pendingSwitchBoostCarry = BattleStatStageSupport.normalizeStoredBoosts(mutation.boosts),
                    ),
            )
        }

    fun applyAddCondition(
        mutation: ApplyConditionMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            val existingState = unit.conditionStates[mutation.conditionEffectId]
            val nextOrder =
                existingState?.effectOrder
                    ?: ((unit.conditionStates.values.maxOfOrNull(AttachedEffectState::effectOrder) ?: 0) + 1)
            unit.copy(
                conditionStates =
                    unit.conditionStates +
                        (
                            mutation.conditionEffectId to
                                AttachedEffectState(
                                    effectId = mutation.conditionEffectId,
                                    sourceId = mutation.sourceId,
                                    duration = mutation.duration,
                                    effectOrder = nextOrder,
                                )
                        ),
            )
        }

    fun applyRemoveCondition(
        mutation: RemoveConditionMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(conditionStates = unit.conditionStates - mutation.conditionEffectId)
        }

    fun applySetProbe(
        mutation: SetProbeMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(debugState = unit.debugState.copy(probes = unit.debugState.probes + (mutation.key to mutation.value)))
        }

    fun applyClearProbe(
        mutation: ClearProbeMutation,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
    ): Map<String, UnitState> =
        updateTargets(mutation.target, context, units) { unit ->
            unit.copy(debugState = unit.debugState.copy(probes = unit.debugState.probes - mutation.key))
        }

    private fun updateTargets(
        selector: io.github.lishangbu.avalon.game.battle.engine.core.type.TargetSelectorId,
        context: MutationApplicationContext,
        units: Map<String, UnitState>,
        transform: (UnitState) -> UnitState,
    ): Map<String, UnitState> {
        val targetIds = MutationTargetSelectorResolver.resolve(selector, context)
        if (targetIds.isEmpty()) {
            return units
        }
        val nextUnits = units.toMutableMap()
        targetIds.forEach { unitId ->
            val unit = requireNotNull(nextUnits[unitId]) { "Target unit '$unitId' was not found." }
            nextUnits[unitId] = transform(unit)
        }
        return nextUnits
    }

    private fun calculateAmount(
        maxHp: Int,
        mode: String?,
        value: Double,
    ): Int =
        when (mode) {
            "max_hp_ratio" -> (maxHp * value).toInt()
            else -> value.toInt()
        }
}