package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations import dev.inmo.micro_utils.coroutines.safelyWithResult import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.utils.flatMap import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.lowLevelRiskFeatureMessage import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.* private val cancelledByFilterException = CancellationException("Cancelled by filter precreatedException") typealias RequestBuilder = suspend (Update) -> Request typealias NullableRequestBuilder = suspend (Update) -> Request? /** * @param initRequest If not null, this request will be sent by [bot] before returning value * @param count If set, result [Flow] will return [count] elements on each [Flow.collect] * @param errorFactory If set, this factory will be used to produce requests in case when user have sent incorrect data * @param cancelRequestFactory If set, this factory will be used to produce requests in case when it is required to say * user that chain of scenario has been cancelled * @param cancelTrigger When this trigger returns true, chain is cancelled * @param filter It is main param, which will be called on each update. When it return not null, result will be returned * as is, but when it returns null, then will be called [cancelTrigger] (if it will return true - [cancelRequestFactory] * will be called too), [errorFactory] and then will be returned null */ @RiskFeature(lowLevelRiskFeatureMessage) suspend fun FlowsUpdatesFilter.expectFlow( bot: TelegramBot, initRequest: Request<*>? = null, count: Int? = null, errorFactory: NullableRequestBuilder<*> = { null }, cancelRequestFactory: NullableRequestBuilder<*> = { null }, cancelTrigger: suspend (Update) -> Boolean = { cancelRequestFactory(it) != null }, filter: suspend (Update) -> List ): Flow { val flow = allUpdatesFlow.map { val result = safelyWithResult { filter(it) } if (result.isFailure || result.getOrThrow().isEmpty()) { if (cancelTrigger(it)) { cancelRequestFactory(it) ?.also { safelyWithResult { bot.execute(it) } throw cancelledByFilterException } } errorFactory(it) ?.also { errorRequest -> safelyWithoutExceptions { bot.execute(errorRequest) } } emptyList() } else { result.getOrThrow() } }.flatMap() val result = if (count == null) { flow } else { flow.take(count) } initRequest ?.also { safelyWithoutExceptions { bot.execute(initRequest) } } return result } /** * @param initRequest If not null, this request will be sent by [bot] before returning value * @param count If set, result [Flow] will return [count] elements on each [Flow.collect] * @param errorFactory If set, this factory will be used to produce requests in case when user have sent incorrect data * @param cancelRequestFactory If set, this factory will be used to produce requests in case when it is required to say * user that chain of scenario has been cancelled * @param cancelTrigger When this trigger returns true, chain is cancelled * @param filter It is main param, which will be called on each update. When it return not null, result will be returned * as is, but when it returns null, then will be called [cancelTrigger] (if it will return true - [cancelRequestFactory] * will be called too), [errorFactory] and then will be returned null */ suspend fun BehaviourContext.expectFlow( initRequest: Request<*>? = null, count: Int? = null, errorFactory: NullableRequestBuilder<*> = { null }, cancelRequestFactory: NullableRequestBuilder<*> = { null }, cancelTrigger: suspend (Update) -> Boolean = { cancelRequestFactory(it) != null }, filter: suspend (Update) -> List ) = flowsUpdatesFilter.expectFlow(bot, initRequest, count, errorFactory, cancelRequestFactory, cancelTrigger, filter) /** * @param initRequest If not null, this request will be sent by [bot] before returning value * @param errorFactory If set, this factory will be used to produce requests in case when user have sent incorrect data * @param cancelRequestFactory If set, this factory will be used to produce requests in case when it is required to say * user that chain of scenario has been cancelled * @param cancelTrigger When this trigger returns true, chain is cancelled * @param filter It is main param, which will be called on each update. When it return not null, result will be returned * as is, but when it returns null, then will be called [cancelTrigger] (if it will return true - [cancelRequestFactory] * will be called too), [errorFactory] and then will be returned null */ @RiskFeature(lowLevelRiskFeatureMessage) suspend fun FlowsUpdatesFilter.expectOne( bot: TelegramBot, initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, cancelRequestFactory: NullableRequestBuilder<*> = { null }, cancelTrigger: suspend (Update) -> Boolean = { cancelRequestFactory(it) != null }, filter: suspend (Update) -> T? ): T = expectFlow(bot, initRequest, 1, errorFactory, cancelRequestFactory, cancelTrigger) { listOfNotNull(filter.invoke(it)) }.first() /** * @param initRequest If not null, this request will be sent by [bot] before returning value * @param errorFactory If set, this factory will be used to produce requests in case when user have sent incorrect data * @param cancelRequestFactory If set, this factory will be used to produce requests in case when it is required to say * user that chain of scenario has been cancelled * @param cancelTrigger When this trigger returns true, chain is cancelled * @param filter It is main param, which will be called on each update. When it return not null, result will be returned * as is, but when it returns null, then will be called [cancelTrigger] (if it will return true - [cancelRequestFactory] * will be called too), [errorFactory] and then will be returned null */ suspend fun BehaviourContext.expectOne( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, cancelRequestFactory: NullableRequestBuilder<*> = { null }, cancelTrigger: suspend (Update) -> Boolean = { cancelRequestFactory(it) != null }, filter: suspend (Update) -> T? ) = flowsUpdatesFilter.expectOne(bot, initRequest, errorFactory, cancelRequestFactory, cancelTrigger, filter)