LocalCreatureReader.kt

package io.github.lishangbu.avalon.dataset.service.api

import io.github.lishangbu.avalon.dataset.api.model.CreatureAbilityOptionInfo
import io.github.lishangbu.avalon.dataset.api.model.CreatureInfo
import io.github.lishangbu.avalon.dataset.api.service.CreatureReader
import io.github.lishangbu.avalon.dataset.entity.dto.CreatureSpecification
import io.github.lishangbu.avalon.dataset.entity.dto.CreatureView
import io.github.lishangbu.avalon.dataset.repository.AbilityRepository
import io.github.lishangbu.avalon.dataset.repository.CreatureAbilityRepository
import io.github.lishangbu.avalon.dataset.repository.CreatureElementRepository
import io.github.lishangbu.avalon.dataset.repository.CreatureRepository
import io.github.lishangbu.avalon.dataset.repository.CreatureSpeciesRepository
import io.github.lishangbu.avalon.dataset.repository.CreatureStatRepository
import io.github.lishangbu.avalon.dataset.repository.StatRepository
import io.github.lishangbu.avalon.dataset.repository.TypeRepository
import org.springframework.stereotype.Service

@Service
class LocalCreatureReader(
    private val creatureRepository: CreatureRepository,
    private val creatureSpeciesRepository: CreatureSpeciesRepository,
    private val creatureElementRepository: CreatureElementRepository,
    private val creatureStatRepository: CreatureStatRepository,
    private val creatureAbilityRepository: CreatureAbilityRepository,
    private val abilityRepository: AbilityRepository,
    private val typeRepository: TypeRepository,
    private val statRepository: StatRepository,
) : CreatureReader {
    override fun findCreatureById(id: Long): CreatureInfo? = creatureRepository.loadViewById(id)?.toCreatureInfo()

    override fun findCreatureByInternalName(internalName: String): CreatureInfo? =
        creatureRepository
            .listViews(CreatureSpecification(internalName = internalName))
            .firstOrNull { creature -> creature.internalName == internalName }
            ?.toCreatureInfo()

    private fun CreatureView.toCreatureInfo(): CreatureInfo {
        val creatureId = id.toLong()
        val species = creatureSpecies?.id?.toLong()?.let(creatureSpeciesRepository::loadViewById)
        return CreatureInfo(
            id = creatureId,
            speciesId = species?.id?.toLong(),
            internalName = requireNotNull(internalName) { "Creature id=$creatureId is missing internalName." },
            name = name ?: requireNotNull(internalName),
            weight = weight,
            growthRateInternalName = species?.growthRate?.internalName,
            captureRate = species?.captureRate,
            typeInternalNames = loadCreatureElements(creatureId),
            baseStats = loadCreatureStats(creatureId),
            abilityOptions = loadCreatureAbilities(creatureId),
        )
    }

    private fun loadCreatureElements(creatureId: Long): List<String> {
        val rows = creatureElementRepository.findAll().filter { row -> row.id.creatureId == creatureId }
        val slots = rows.associate { row -> row.id.typeId to (row.slot ?: Int.MAX_VALUE) }
        return typeRepository
            .findAllById(slots.keys)
            .sortedBy { type -> slots[type.id] ?: Int.MAX_VALUE }
            .mapNotNull { type -> type.internalName }
    }

    private fun loadCreatureStats(creatureId: Long): Map<String, Int> {
        val rows = creatureStatRepository.findAll().filter { row -> row.id.creatureId == creatureId }
        val statsById = statRepository.findAllById(rows.map { row -> row.id.statId }.toSet()).associateBy { stat -> stat.id }
        return rows
            .mapNotNull { row ->
                val stat = statsById[row.id.statId] ?: return@mapNotNull null
                val internalName = stat.internalName ?: return@mapNotNull null
                internalName to (row.baseStat ?: 0)
            }.toMap(LinkedHashMap())
    }

    private fun loadCreatureAbilities(creatureId: Long): List<CreatureAbilityOptionInfo> {
        val rows = creatureAbilityRepository.findAll().filter { row -> row.id.creatureId == creatureId }
        val abilitiesById = abilityRepository.findAllById(rows.map { row -> row.id.abilityId }.toSet()).associateBy { ability -> ability.id }
        return rows
            .mapNotNull { row ->
                val ability = abilitiesById[row.id.abilityId] ?: return@mapNotNull null
                val internalName = ability.internalName ?: return@mapNotNull null
                CreatureAbilityOptionInfo(
                    internalName = internalName,
                    slot = row.slot ?: Int.MAX_VALUE,
                    hidden = row.hidden == true,
                )
            }.sortedWith(compareBy<CreatureAbilityOptionInfo> { ability -> ability.hidden }.thenBy { ability -> ability.slot })
    }
}