BattleStatAliasResolver.kt

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

import io.github.lishangbu.avalon.game.battle.engine.core.constant.BattleStatIds

/**
 * battle 能力项别名解析器。
 *
 * 设计意图:
 * - 统一处理 `attack/atk`、`speed/spe` 这类长短写别名;
 * - 让伤害、逃跑、条件判断、boost 落盘等路径共享同一套 stat id 解析规则;
 * - 为后续继续收口到单一规范命名提供唯一入口,避免各文件继续手写 if/when 分支。
 *
 * 当前约定:
 * - 运行时基础能力值 `stats` 仍允许读取长写和短写;
 * - stage / boost 在真正写回时会优先归一到短写,便于与既有 fixture 及规则数据保持一致;
 * - 对于未知 stat id,不做额外猜测,按原值透传。
 */
object BattleStatAliasResolver {
    /**
     * 给出一个 stat id 的“读取优先顺序”。
     *
     * 说明:
     * - 会优先返回调用方传入的原始写法,避免同一份 map 中同时存在长写和短写时出现意外覆盖;
     * - 如果该 stat 存在标准别名,则把另一种写法追加到后面作为兜底。
     */
    fun aliasesOf(statId: String): List<String> {
        val counterpart = counterpartOf(statId) ?: return listOf(statId)
        return listOf(statId, counterpart)
    }

    /**
     * 返回一个 stat id 所属别名族。
     *
     * 适用于“删除旧键后写回规范键”这类需要整族处理的场景。
     */
    fun equivalentIds(statId: String): Set<String> = aliasesOf(statId).toSet()

    /**
     * 从任意 stat map 中读取能力值。
     *
     * 设计目标不是“把 map 彻底改写”,而是提供一个稳定读取入口,
     * 让各调用方不用重复写 `speed ?: spe`、`attack ?: atk` 这类兼容分支。
     */
    fun readValue(
        values: Map<String, Int>,
        statId: String,
    ): Int? = aliasesOf(statId).firstNotNullOfOrNull(values::get)

    /**
     * 把任意 boost/stat id 归一成 stage map 的标准短写键。
     *
     * 例如:
     * - `attack` -> `atk`
     * - `special-defense` -> `spd`
     * - 未知键保持原样
     */
    fun canonicalBoostId(statId: String): String =
        when (statId) {
            BattleStatIds.ATTACK,
            BattleStatIds.ATTACK_SHORT,
            -> BattleStatIds.ATTACK_SHORT

            BattleStatIds.DEFENSE,
            BattleStatIds.DEFENSE_SHORT,
            -> BattleStatIds.DEFENSE_SHORT

            BattleStatIds.SPECIAL_ATTACK,
            BattleStatIds.SPECIAL_ATTACK_SHORT,
            -> BattleStatIds.SPECIAL_ATTACK_SHORT

            BattleStatIds.SPECIAL_DEFENSE,
            BattleStatIds.SPECIAL_DEFENSE_SHORT,
            -> BattleStatIds.SPECIAL_DEFENSE_SHORT

            BattleStatIds.SPEED,
            BattleStatIds.SPEED_SHORT,
            -> BattleStatIds.SPEED_SHORT

            BattleStatIds.ACCURACY,
            BattleStatIds.ACCURACY_SHORT,
            -> BattleStatIds.ACCURACY_SHORT

            BattleStatIds.EVASION,
            BattleStatIds.EVASION_SHORT,
            -> BattleStatIds.EVASION_SHORT

            else -> statId
        }

    private fun counterpartOf(statId: String): String? =
        when (statId) {
            BattleStatIds.ATTACK -> BattleStatIds.ATTACK_SHORT
            BattleStatIds.ATTACK_SHORT -> BattleStatIds.ATTACK
            BattleStatIds.DEFENSE -> BattleStatIds.DEFENSE_SHORT
            BattleStatIds.DEFENSE_SHORT -> BattleStatIds.DEFENSE
            BattleStatIds.SPECIAL_ATTACK -> BattleStatIds.SPECIAL_ATTACK_SHORT
            BattleStatIds.SPECIAL_ATTACK_SHORT -> BattleStatIds.SPECIAL_ATTACK
            BattleStatIds.SPECIAL_DEFENSE -> BattleStatIds.SPECIAL_DEFENSE_SHORT
            BattleStatIds.SPECIAL_DEFENSE_SHORT -> BattleStatIds.SPECIAL_DEFENSE
            BattleStatIds.SPEED -> BattleStatIds.SPEED_SHORT
            BattleStatIds.SPEED_SHORT -> BattleStatIds.SPEED
            BattleStatIds.ACCURACY -> BattleStatIds.ACCURACY_SHORT
            BattleStatIds.ACCURACY_SHORT -> BattleStatIds.ACCURACY
            BattleStatIds.EVASION -> BattleStatIds.EVASION_SHORT
            BattleStatIds.EVASION_SHORT -> BattleStatIds.EVASION
            else -> null
        }
}