All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.freya022.botcommands.internal.modals.ModalHandlerInfo.kt Maven / Gradle / Ivy

package io.github.freya022.botcommands.internal.modals

import gnu.trove.map.TObjectLongMap
import gnu.trove.map.hash.TObjectLongHashMap
import io.github.freya022.botcommands.api.core.service.getService
import io.github.freya022.botcommands.api.core.utils.findAnnotationRecursive
import io.github.freya022.botcommands.api.core.utils.hasAnnotationRecursive
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
import io.github.freya022.botcommands.api.modals.ModalEvent
import io.github.freya022.botcommands.api.modals.annotations.ModalHandler
import io.github.freya022.botcommands.api.modals.annotations.ModalInput
import io.github.freya022.botcommands.api.parameters.AggregatedParameter
import io.github.freya022.botcommands.internal.ExecutableMixin
import io.github.freya022.botcommands.internal.core.BContextImpl
import io.github.freya022.botcommands.internal.core.options.OptionImpl
import io.github.freya022.botcommands.internal.core.options.OptionType
import io.github.freya022.botcommands.internal.core.reflection.MemberParamFunction
import io.github.freya022.botcommands.internal.modals.options.ModalHandlerDataOption
import io.github.freya022.botcommands.internal.modals.options.ModalHandlerInputOption
import io.github.freya022.botcommands.internal.modals.options.ModalHandlerParameterImpl
import io.github.freya022.botcommands.internal.modals.options.builder.ModalHandlerDataOptionBuilderImpl
import io.github.freya022.botcommands.internal.modals.options.builder.ModalHandlerInputOptionBuilderImpl
import io.github.freya022.botcommands.internal.options.transformParameters
import io.github.freya022.botcommands.internal.parameters.*
import io.github.freya022.botcommands.internal.requireUser
import io.github.freya022.botcommands.internal.throwUser
import io.github.freya022.botcommands.internal.utils.*
import kotlin.reflect.full.callSuspendBy
import kotlin.reflect.jvm.jvmErasure
import io.github.freya022.botcommands.api.modals.annotations.ModalData as ModalDataAnnotation

internal class ModalHandlerInfo internal constructor(
    override val context: BContextImpl,
    override val eventFunction: MemberParamFunction
) : ExecutableMixin {
    override val parameters: List

    private val expectedModalDatas: Int
    private val expectedModalInputs: Int

    internal val handlerName: String

    init {
        val annotation = function.findAnnotationRecursive()!!
        handlerName = annotation.name

        val resolverContainer = context.serviceContainer.getService()
        parameters = eventFunction.transformParameters(
            builderBlock = { function, parameter, declaredName ->
                val optionParameter = OptionParameter.fromSelfAggregate(function, declaredName)
                if (parameter.hasAnnotationRecursive()) {
                    ModalHandlerInputOptionBuilderImpl(optionParameter)
                } else if (parameter.hasAnnotationRecursive()) {
                    ModalHandlerDataOptionBuilderImpl(optionParameter)
                } else {
                    optionParameter.toFallbackOptionBuilder(context.serviceContainer, resolverContainer)
                }
            },
            aggregateBlock = { ModalHandlerParameterImpl(this, it) }
        )

        val options = parameters.flatMap { it.allOptions }
        expectedModalDatas = options.filterIsInstance().count()
        expectedModalInputs = options.filterIsInstance().count()
    }

    @Suppress("DeprecatedCallableAddReplaceWith")
    @Deprecated("For removal, confusing on whether it searches nested parameters, prefer using collection operations on 'parameters' instead, make an extension or an utility method")
    override fun getParameter(declaredName: String): AggregatedParameter? =
        parameters.find { it.name == declaredName }

    internal suspend fun execute(modalData: ModalData, event: ModalEvent) {
        val handlerData = modalData.handlerData as? PersistentModalHandlerData ?: throwInternal("This method should have not been ran as there is no handler data")

        val inputDataMap = modalData.inputDataMap
        val inputNameToInputIdMap: TObjectLongMap = TObjectLongHashMap()
        inputDataMap.forEachEntry { inputId: Long, inputData: InputData ->
            inputNameToInputIdMap.put(inputData.inputName, inputId)
            true
        }

        val userDatas = handlerData.userData

        //Check if there's enough arguments to fit user data + modal inputs
        requireAt(expectedModalDatas == userDatas.size && expectedModalInputs == event.values.size, function) {
            """
                Modal handler does not match the received modal data:
                Method signature: $expectedModalDatas userdata parameters and $expectedModalInputs modal input(s)
                Discord data: ${userDatas.size} userdata parameters and ${event.values.size} modal input(s)
            """.trimIndent()
        }

        val userDataIterator = userDatas.iterator()
        val optionValues = parameters.mapOptions { option ->
            if (tryInsertOption(event, option, inputNameToInputIdMap, userDataIterator, this) == InsertOptionResult.ABORT)
                throwInternal(::tryInsertOption, "Insertion function shouldn't have been aborted")
        }

        function.callSuspendBy(parameters.mapFinalParameters(event, optionValues))
    }

    private suspend fun tryInsertOption(
        event: ModalEvent,
        option: OptionImpl,
        inputNameToInputIdMap: TObjectLongMap,
        userDataIterator: Iterator,
        optionMap: MutableMap
    ): InsertOptionResult {
        val value = when (option.optionType) {
            OptionType.OPTION -> {
                option as ModalHandlerInputOption

                //We have the modal input's ID
                // But we have a Map of input *name* -> InputData (contains input ID)
                val inputId = inputNameToInputIdMap[option.inputName].takeIf { it != inputNameToInputIdMap.noEntryValue }
                    ?: throwUser("Modal input named '${option.inputName}' was not found")
                val modalMapping = event.getValue(ModalMaps.getInputId(inputId))
                    ?: throwUser("Modal input ID '$inputId' was not found on the event")

                option.resolver.resolveSuspend(option, event, modalMapping).also { obj ->
                    // Technically not required, but provides additional info
                    requireUser(obj != null || option.isOptionalOrNullable) {
                        "The parameter '${option.declaredName}' of value '${modalMapping.asString}' could not be resolved into a ${option.type.simpleNestedName}"
                    }
                }
            }

            OptionType.GENERATED -> {
                option as ModalHandlerDataOption

                if (!userDataIterator.hasNext())
                    throwInternal("Mismatch in amount of user data provided by the user and the amount requested by the aggregates, this should have been checked")

                val userData = userDataIterator.next()
                if (userData != null) {
                    requireUser(option.type.jvmErasure.isInstance(userData)) {
                        "The modal user data '${option.declaredName}' is not a valid type (expected a ${option.type.simpleNestedName}, got a ${userData.javaClass.simpleName})"
                    }
                }

                userData
            }

            OptionType.CUSTOM -> {
                option as CustomMethodOption
                option.resolver.resolveSuspend(option, event)
            }

            OptionType.SERVICE -> (option as ServiceMethodOption).getService()

            OptionType.CONSTANT -> throwInternal("${option.optionType} has not been implemented")
        }

        return tryInsertNullableOption(value, option, optionMap)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy