SmartBattleUnitImportService.kt

package io.github.lishangbu.avalon.game.service.unit

import io.github.lishangbu.avalon.dataset.api.service.CreatureReader
import io.github.lishangbu.avalon.dataset.api.service.MoveReader
import io.github.lishangbu.avalon.dataset.api.service.NatureReader
import io.github.lishangbu.avalon.game.battle.engine.core.unit.BattleMoveSlotInput
import io.github.lishangbu.avalon.game.battle.engine.core.unit.BattleUnitAssembler
import io.github.lishangbu.avalon.game.battle.engine.core.unit.BattleUnitAssemblyRequest
import io.github.lishangbu.avalon.game.battle.engine.core.unit.CreatureAbilityOptionRecord
import io.github.lishangbu.avalon.game.battle.engine.core.unit.CreatureUnitImportRecord
import io.github.lishangbu.avalon.game.battle.engine.core.unit.NatureImportRecord
import io.github.lishangbu.avalon.game.calculator.growthrate.GrowthRateCalculatorFactory
import io.github.lishangbu.avalon.game.calculator.stat.StatCalculatorFactory
import org.springframework.stereotype.Service

/**
 * 基于真实数据的战斗单位智能导入服务。
 */
@Service
class SmartBattleUnitImportService(
    private val creatureReader: CreatureReader,
    private val natureReader: NatureReader,
    private val moveReader: MoveReader,
    private val statCalculatorFactory: StatCalculatorFactory,
    private val growthRateCalculatorFactory: GrowthRateCalculatorFactory,
) : BattleUnitImporter {
    override fun importUnit(request: BattleUnitImportRequest): BattleUnitImportResult {
        val creature = loadCreature(request)
        val nature = loadNature(request)
        val movePpDefaults = loadMovePpDefaults(request.moves)
        val assembled =
            BattleUnitAssembler.assemble(
                request =
                    BattleUnitAssemblyRequest(
                        unitId = request.unitId,
                        metadata =
                            io.github.lishangbu.avalon.game.battle.engine.core.model.UnitMetadataState(
                                level = request.metadata.level,
                                creatureId = request.metadata.creatureId,
                                creatureInternalName = request.metadata.creatureInternalName,
                                natureId = request.metadata.natureId,
                                captureContext = request.metadata.captureContext,
                                ivs = request.metadata.ivs,
                                evs = request.metadata.evs,
                            ),
                        abilityInternalName = request.abilityInternalName,
                        itemId = request.itemId,
                        moves = request.moves.map { move -> BattleMoveSlotInput(move.moveId, move.currentPp) },
                        currentHp = request.currentHp,
                        statusState = request.statusState,
                        volatileStates = request.volatileStates,
                        conditionStates = request.conditionStates,
                        boosts = request.boosts,
                        forceSwitchRequested = request.forceSwitchRequested,
                    ),
                creature = creature,
                nature = nature,
                movePpDefaults = movePpDefaults,
                statCalculatorFactory = statCalculatorFactory,
                growthRateCalculatorFactory = growthRateCalculatorFactory,
            )

        return BattleUnitImportResult(
            unit = assembled.unit,
            creatureId = assembled.creatureId,
            creatureInternalName = assembled.creatureInternalName,
            creatureName = assembled.creatureName,
            level = assembled.level,
            requiredExperience = assembled.requiredExperience,
            calculatedStats = assembled.calculatedStats,
        )
    }

    private fun loadCreature(request: BattleUnitImportRequest): CreatureUnitImportRecord {
        val creature =
            when {
                request.metadata.creatureId != null -> creatureReader.findCreatureById(request.metadata.creatureId)
                request.metadata.creatureInternalName != null -> creatureReader.findCreatureByInternalName(request.metadata.creatureInternalName)
                else -> error("Either creatureId or creatureInternalName must be provided.")
            } ?: error("Creature was not found for request '${request.unitId}'.")

        return CreatureUnitImportRecord(
            id = creature.id,
            speciesId = creature.speciesId,
            internalName = creature.internalName,
            name = creature.name,
            weight = creature.weight,
            growthRateInternalName = creature.growthRateInternalName,
            captureRate = creature.captureRate,
            typeIds = creature.typeInternalNames,
            baseStats = creature.baseStats,
            abilityOptions =
                creature.abilityOptions.map { ability ->
                    CreatureAbilityOptionRecord(
                        internalName = ability.internalName,
                        slot = ability.slot,
                        hidden = ability.hidden,
                    )
                },
        )
    }

    private fun loadNature(request: BattleUnitImportRequest): NatureImportRecord? {
        val nature =
            when {
                request.metadata.natureId != null -> natureReader.findNatureById(request.metadata.natureId)
                request.metadata.natureInternalName != null -> natureReader.findNatureByInternalName(request.metadata.natureInternalName)
                else -> null
            } ?: return null

        return NatureImportRecord(
            id = nature.id,
            internalName = nature.internalName,
            increasedStatInternalName = nature.increasedStatInternalName,
            decreasedStatInternalName = nature.decreasedStatInternalName,
        )
    }

    private fun loadMovePpDefaults(moves: List<BattleMoveImportRequest>): Map<String, Int> {
        val movesByInternalName = moveReader.findMovesByInternalNames(moves.map { move -> move.moveId }.toSet())
        return moves.associate { move ->
            move.moveId to (movesByInternalName[move.moveId]?.pp ?: 0)
        }
    }
}