BattleUnitEffectDurationManager.kt

package io.github.lishangbu.avalon.game.battle.engine.core.session

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.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.runtime.flow.BattleRuntimeSnapshot
import io.github.lishangbu.avalon.game.battle.engine.core.type.StandardTargetSelectorIds

/**
 * 单位挂载效果的持续回合推进器。
 *
 * 设计意图:
 * - 在回合末统一递减主状态、挥发状态和普通 condition 的剩余回合;
 * - 只维护 runtime state,不再同步旧的镜像 id 字段。
 */
internal class BattleUnitEffectDurationManager {
    /**
     * 推进当前快照中全部单位挂载效果的剩余持续时间。
     */
    fun advance(snapshot: BattleRuntimeSnapshot): BattleEffectDurationAdvanceResult {
        if (snapshot.units.isEmpty()) {
            return BattleEffectDurationAdvanceResult(snapshot)
        }
        val expirationMutations = mutableListOf<BattleSessionScopedMutation>()
        val nextUnits =
            snapshot.units.mapValues { (_, unit) ->
                advanceUnit(unit, expirationMutations)
            }
        val nextSnapshot =
            if (nextUnits == snapshot.units) {
                snapshot
            } else {
                snapshot.copy(units = nextUnits)
            }
        return BattleEffectDurationAdvanceResult(
            snapshot = nextSnapshot,
            expirationMutations = expirationMutations,
        )
    }

    /**
     * 推进单个单位上的全部挂载效果。
     */
    private fun advanceUnit(
        unit: UnitState,
        expirationMutations: MutableList<BattleSessionScopedMutation>,
    ): UnitState {
        val nextStatusState =
            advanceState(unit.statusState) { state ->
                expirationMutations +=
                    BattleSessionScopedMutation(
                        selfId = unit.id,
                        targetId = unit.id,
                        sourceId = state.sourceId,
                        mutation = RemoveStatusMutation(StandardTargetSelectorIds.SELF),
                    )
            }
        val nextConditionStates =
            advanceStateMap(unit.conditionStates) { state ->
                expirationMutations +=
                    BattleSessionScopedMutation(
                        selfId = unit.id,
                        targetId = unit.id,
                        sourceId = state.sourceId,
                        mutation =
                            RemoveConditionMutation(
                                target = StandardTargetSelectorIds.SELF,
                                conditionEffectId = state.effectId,
                            ),
                    )
            }
        val nextVolatileStates =
            advanceStateMap(unit.volatileStates) { state ->
                expirationMutations +=
                    BattleSessionScopedMutation(
                        selfId = unit.id,
                        targetId = unit.id,
                        sourceId = state.sourceId,
                        mutation =
                            RemoveVolatileMutation(
                                target = StandardTargetSelectorIds.SELF,
                                volatileEffectId = state.effectId,
                            ),
                    )
            }

        return if (
            nextStatusState == unit.statusState &&
            nextConditionStates == unit.conditionStates &&
            nextVolatileStates == unit.volatileStates
        ) {
            unit
        } else {
            unit.copy(
                statusState = nextStatusState,
                conditionStates = nextConditionStates,
                volatileStates = nextVolatileStates,
            )
        }
    }

    /**
     * 推进一组 map 形式的挂载效果状态。
     */
    private fun advanceStateMap(
        states: Map<String, AttachedEffectState>,
        onExpired: (AttachedEffectState) -> Unit,
    ): Map<String, AttachedEffectState> {
        if (states.isEmpty()) {
            return states
        }
        val nextStates = linkedMapOf<String, AttachedEffectState>()
        states.values
            .sortedBy(AttachedEffectState::effectOrder)
            .forEach { state ->
                val nextState = advanceState(state, onExpired)
                if (nextState != null) {
                    nextStates[nextState.effectId] = nextState
                }
            }
        return nextStates
    }

    /**
     * 推进单个挂载效果的剩余回合。
     *
     * 返回空值表示该效果已在本次回合结束时自然到期。
     */
    private fun advanceState(
        state: AttachedEffectState?,
        onExpired: (AttachedEffectState) -> Unit = {},
    ): AttachedEffectState? {
        val currentState = state ?: return null
        val duration = currentState.duration ?: return currentState
        val nextDuration = duration - 1
        return if (nextDuration > 0) {
            currentState.copy(duration = nextDuration)
        } else {
            onExpired(currentState)
            currentState
        }
    }
}