BattleSessionChoiceValidator.kt

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

import io.github.lishangbu.avalon.game.battle.engine.core.model.SideState
import io.github.lishangbu.avalon.game.battle.engine.core.session.specification.BattleSessionCaptureChoiceSpecification
import io.github.lishangbu.avalon.game.battle.engine.core.session.specification.BattleSessionItemChoiceSpecification
import io.github.lishangbu.avalon.game.battle.engine.core.session.specification.BattleSessionMoveChoiceSpecification
import io.github.lishangbu.avalon.game.battle.engine.core.session.specification.BattleSessionRunChoiceSpecification
import io.github.lishangbu.avalon.game.battle.engine.core.session.specification.BattleSessionTargetChoiceSpecification
import io.github.lishangbu.avalon.game.battle.engine.core.session.specification.BattleSessionTurnReadySpecification
import io.github.lishangbu.avalon.game.battle.engine.core.session.specification.BattleSessionUnitChoiceSpecification
import io.github.lishangbu.avalon.game.battle.engine.core.session.specification.BattleSessionValidationResult

/**
 * 负责所有“当前这个输入是否合法”的校验逻辑。
 *
 * 这样 `BattleSession` 的主文件就不需要同时承担:
 * - 状态推进
 * - 输入收集
 * - 输入合法性判断
 *
 * @property session 当前 battle session。
 * @property turnReadySpecification 回合结算前置规格。
 * @property unitChoiceSpecification active 单位行动提交规格。
 * @property runChoiceSpecification side 逃跑提交规格。
 * @property targetChoiceSpecification effect 目标合法性规格。
 * @property captureChoiceSpecification 捕捉动作合法性规格。
 * @property moveChoiceSpecification 招式选择限制规格。
 * @property itemChoiceSpecification 物品选择限制规格。
 */
internal class BattleSessionChoiceValidator(
    private val session: BattleSession,
    private val turnReadySpecification: BattleSessionTurnReadySpecification,
    private val unitChoiceSpecification: BattleSessionUnitChoiceSpecification,
    private val runChoiceSpecification: BattleSessionRunChoiceSpecification,
    private val targetChoiceSpecification: BattleSessionTargetChoiceSpecification,
    private val captureChoiceSpecification: BattleSessionCaptureChoiceSpecification,
    private val moveChoiceSpecification: BattleSessionMoveChoiceSpecification,
    private val itemChoiceSpecification: BattleSessionItemChoiceSpecification,
) {
    /**
     * 确认当前回合已经满足结算条件。
     */
    fun ensureTurnReady() {
        turnReadySpecification.validate(session).requireSatisfied()
    }

    /**
     * 确认某个 active 单位本回合还允许继续提交行动。
     *
     * @param unitId 待提交行动的单位 ID
     */
    fun ensureUnitCanSubmitChoice(unitId: String) {
        unitChoiceSpecification.validate(session, unitId).requireSatisfied()
    }

    /**
     * 确认某个 side 允许提交逃跑选择。
     *
     * @param sideId 待逃跑的 side ID
     */
    fun ensureSideCanSubmitRunChoice(sideId: String) {
        runChoiceSpecification.validate(session, sideId).requireSatisfied()
    }

    /**
     * 确认目标是否符合 effect 的 targeting 规则。
     *
     * @param effectId effect ID
     * @param actorUnitId 发起单位 ID
     * @param targetUnitId 目标单位 ID
     */
    fun ensureTargetIsLegalForAction(
        effectId: String,
        actorUnitId: String,
        targetUnitId: String,
    ) {
        targetChoiceSpecification.validate(session, effectId, actorUnitId, targetUnitId).requireSatisfied()
    }

    /**
     * 确认当前允许提交捕捉动作。
     */
    fun ensureCaptureIsLegal(
        playerId: String,
        sourceUnitId: String,
        targetUnitId: String,
    ) {
        captureChoiceSpecification.validate(session, playerId, sourceUnitId, targetUnitId).requireSatisfied()
        ensureUnitCanSubmitChoice(sourceUnitId)
    }

    /**
     * 确认当前单位允许选择该招式。
     */
    fun ensureMoveChoiceIsLegal(
        moveId: String,
        actorUnitId: String,
    ) {
        moveChoiceSpecification.validate(session, moveId, actorUnitId).requireSatisfied()
    }

    /**
     * 确认当前单位允许使用该物品。
     */
    fun ensureItemChoiceIsLegal(
        itemId: String,
        actorUnitId: String,
    ) {
        itemChoiceSpecification.validate(session, itemId, actorUnitId).requireSatisfied()
    }

    /**
     * 确认某条规格校验已经通过。
     *
     * @receiver 需要被转换为 `require` 断言的校验结果。
     */
    private fun BattleSessionValidationResult.requireSatisfied() {
        require(satisfied) {
            message ?: "Battle session validation failed."
        }
    }

    /**
     * 根据动作反推出它属于哪个 side。
     */
    fun submittedSideId(action: BattleSessionAction): String? =
        when (action) {
            is BattleSessionSideAction -> action.sideId
            is BattleSessionSubmittingAction -> sideIdOfUnit(action.submittingUnitId)
            else -> null
        }

    /**
     * 根据单位 ID 反查其所属 side。
     */
    fun sideIdOfUnit(unitId: String): String? =
        session.currentSnapshot.sides.values
            .firstOrNull { side -> unitId in side.unitIds }
            ?.id

    /**
     * 统计某个 side 当前已经由哪些 active 单位提交过行动。
     */
    fun submittedUnitIdsForSide(side: SideState): List<String> {
        val pending = session.pendingActions()
        if (pending.any { action -> action is BattleSessionRunAction && action.sideId == side.id }) {
            return side.activeUnitIds
        }
        return pending
            .filterIsInstance<BattleSessionSubmittingAction>()
            .map(BattleSessionSubmittingAction::submittingUnitId)
            .filter { unitId -> unitId in side.activeUnitIds }
    }
}