package com.suno.android.common_data.billing.mappers import arrow.core.Either import arrow.core.left import arrow.core.right import com.suno.android.common_data.billing.models.PeriodTransitionRule import com.suno.android.common_data.billing.models.PeriodTransitionTimeframe import com.suno.android.common_data.billing.models.PeriodTransitions import com.suno.android.common_data.billing.models.PlanPeriod import com.suno.android.common_data.billing.models.PlanTier import com.suno.android.common_data.billing.models.PlanTransitionRules import com.suno.android.common_data.billing.models.PlanTransitionType import com.suno.android.common_data.billing.models.TierTransitionRule import com.suno.android.common_data.billing.models.TierTransitions import com.suno.android.common_networking.remote.entities.billing.RemoteTimeframeTransition import com.suno.android.common_networking.remote.entities.billing.UsagePlanConfigResponse import kotlinx.collections.immutable.toPersistentList /** * Parse a period transition string (e.g., "month->year") into a PeriodTransitionRule */ fun String.resolvePeriodTransitionRule(): Either { val parts = split("->") if (parts.size != 2) { return Exception("Invalid period transition format: $this").left() } val from = PlanPeriod.fromString(parts[0]) ?: return Exception("Unknown period type: ${parts[0]}").left() val to = PlanPeriod.fromString(parts[1]) ?: return Exception("Unknown period type: ${parts[1]}").left() return PeriodTransitionRule(from = from, to = to).right() } /** * Parse a tier transition string (e.g., "pro->premier") into from/to plan tiers */ fun String.resolveTierPair(): Either { val parts = split("->") if (parts.size != 2) { return Exception("Invalid tier transition format: $this").left() } val from = PlanTier.fromString(parts[0]) ?: return Exception("Unknown plan tier: ${parts[0]}").left() val to = PlanTier.fromString(parts[1]) ?: return Exception("Unknown plan tier: ${parts[1]}").left() return TierTransitionRule(from, to).right() } /** * Map network transitions to domain model */ fun RemoteTimeframeTransition.xAsPeriodTransitions(): PeriodTransitionTimeframe { // Benefits of Either exceptions are dropped here val immediateRules = immediate.mapNotNull { it.resolvePeriodTransitionRule().getOrNull() } val endOfPeriodRules = endOfPeriod.mapNotNull { it.resolvePeriodTransitionRule().getOrNull() } return PeriodTransitionTimeframe( immediate = immediateRules.toPersistentList(), endOfPeriod = endOfPeriodRules.toPersistentList(), ) } /** * Map network response to domain model */ fun UsagePlanConfigResponse.xAsPlanTransitionRules(): Either { // Transform tier transitions val typeToTier = config.tierTransitions.mapNotNull { (tierPairString, tierTransitionString) -> // Benefits of Either exceptions are dropped here val tierTransitionRule = tierPairString.resolveTierPair().getOrNull() ?: return@mapNotNull null val transitionType = PlanTransitionType.fromString(tierTransitionString.transitionType) ?: return@mapNotNull null Pair(transitionType, tierTransitionRule) }.groupBy( keySelector = { it.first }, valueTransform = { it.second }, ) return PlanTransitionRules( periodTransitions = PeriodTransitions( upgrade = config.periodTransitions.upgrade.xAsPeriodTransitions(), downgrade = config.periodTransitions.downgrade.xAsPeriodTransitions(), crossgrade = config.periodTransitions.crossgrade.xAsPeriodTransitions(), ), tierTransitions = TierTransitions( upgrade = typeToTier.getOrDefault(PlanTransitionType.Upgrade, emptyList()).toPersistentList(), downgrade = typeToTier.getOrDefault(PlanTransitionType.Downgrade, emptyList()).toPersistentList(), crossgrade = typeToTier.getOrDefault(PlanTransitionType.Crossgrade, emptyList()).toPersistentList(), ), ).right() }