package com.craigvg.lichun_android.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.craigvg.lichun_android.domain.models.* import com.craigvg.lichun_android.managers.AnalyticsManager import com.craigvg.lichun_android.managers.SoundManager import com.craigvg.lichun_android.managers.ToastManager import com.craigvg.lichun_android.managers.TooltipData import com.craigvg.lichun_android.managers.TooltipManager import com.craigvg.lichun_android.network.WebSocketManager import com.craigvg.lichun_android.utils.WebSocketCommands import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject /** * ViewModel for cross-feature game state * Ported from iOS GameStateViewModel.swift */ @HiltViewModel class GameStateViewModel @Inject constructor( private val webSocketManager: WebSocketManager, private val soundManager: SoundManager, val analyticsManager: AnalyticsManager, private val toastManager: ToastManager, private val tooltipManager: TooltipManager ) : ViewModel() { // Connection state val isConnected = webSocketManager.isConnected val appLoaded = webSocketManager.appLoaded // Player state val player = webSocketManager.player val person = webSocketManager.person // Derived game state val energy: StateFlow = webSocketManager.person.map { it.calcEnergy } .stateIn(viewModelScope, SharingStarted.Eagerly, 0) val money: StateFlow = webSocketManager.person.map { it.money } .stateIn(viewModelScope, SharingStarted.Eagerly, 0.0) val diamonds: StateFlow = webSocketManager.person.map { it.diamonds } .stateIn(viewModelScope, SharingStarted.Eagerly, 0) val health: StateFlow = webSocketManager.person.map { it.health } .stateIn(viewModelScope, SharingStarted.Eagerly, 0.0) val happiness: StateFlow = webSocketManager.person.map { it.happiness } .stateIn(viewModelScope, SharingStarted.Eagerly, 0) val intelligence: StateFlow = webSocketManager.person.map { it.intelligence } .stateIn(viewModelScope, SharingStarted.Eagerly, 50) val prestige: StateFlow = webSocketManager.person.map { it.prestige } .stateIn(viewModelScope, SharingStarted.Eagerly, 0) val gameSpeed: StateFlow = webSocketManager.player.map { it.gameSpeed } .stateIn(viewModelScope, SharingStarted.Eagerly, 0) val season: StateFlow = webSocketManager.player.map { it.season } .stateIn(viewModelScope, SharingStarted.Eagerly, "") val date: StateFlow = webSocketManager.player.map { it.date } .stateIn(viewModelScope, SharingStarted.Eagerly, "") val hourOfDay: StateFlow = webSocketManager.player.map { it.hourOfDay } .stateIn(viewModelScope, SharingStarted.Eagerly, 0) val minuteOfHour: StateFlow = webSocketManager.player.map { it.minuteOfHour } .stateIn(viewModelScope, SharingStarted.Eagerly, 0) // Life events val lifeEvents = webSocketManager.lifeEvents val unclaimedEventCount = webSocketManager.unclaimedEventCount // Questions and events val currentQuestion = webSocketManager.currentQuestion val currentMessageEvent = webSocketManager.currentMessageEvent val currentRelationshipEvent = webSocketManager.currentRelationshipEvent // Error state val currentError = webSocketManager.currentError val dataExportPayload = webSocketManager.dataExportPayload val accountDeletionUpdate = webSocketManager.accountDeletionUpdate // Monetization val showEnergyRefillModal = webSocketManager.showEnergyRefillModal val showTimeSkipModal = webSocketManager.showTimeSkipModal val showTimeSkipSummary = webSocketManager.showTimeSkipSummary val energyRefillTiers = webSocketManager.energyRefillTiers val timeSkipTiers = webSocketManager.timeSkipTiers val lastTimeSkipSummary = webSocketManager.lastTimeSkipSummary // Retention val achievements = webSocketManager.achievements val showAchievementUnlock = webSocketManager.showAchievementUnlock val unacknowledgedAchievements = webSocketManager.unacknowledgedAchievements val dailyRewardState = webSocketManager.dailyRewardState val showDailyRewards = webSocketManager.showDailyRewards val dailyQuestsState = webSocketManager.dailyQuestsState val showDailyQuests = webSocketManager.showDailyQuests // Unlimited energy tracking val unlimitedEnergyUntil = webSocketManager.unlimitedEnergyUntil // Tier 2 retention spine val lifeSummary = webSocketManager.lifeSummary val offlineDigest = webSocketManager.offlineDigest val lifeGoals = webSocketManager.lifeGoals // Wave 2 fun/balance — deeper quest loop + achievement collection summary val questEngagement = webSocketManager.questEngagement val achievementSummary = webSocketManager.achievementSummary // Tier 3 polish — transient floating resource/stat deltas val statDeltas = webSocketManager.statDeltas fun pruneStatDelta(id: String) = webSocketManager.pruneStatDelta(id) // Family prestige (generational layer) for the prestige surfacing screen val familyPrestige: StateFlow = webSocketManager.player.map { it.familyPrestige } .stateIn(viewModelScope, SharingStarted.Eagerly, 0.0) // Toast val currentToast = toastManager.currentToast fun dismissToast() = toastManager.dismiss() // Tooltip val currentTooltip = tooltipManager.currentTooltip val tooltipVisible = tooltipManager.isVisible fun dismissTooltip() = tooltipManager.dismiss() fun showTooltipIfNeeded(id: String, message: String) { tooltipManager.show(TooltipData(id = id, message = message)) } // Tutorial val tutorialStep = webSocketManager.tutorialStep val tutorialComplete = webSocketManager.tutorialComplete val tutorialMessage = webSocketManager.tutorialMessage // Formatted time string val formattedTime: StateFlow = combine(hourOfDay, minuteOfHour) { hour, minute -> val period = if (hour < 12) "AM" else "PM" val displayHour = when { hour == 0 -> 12 hour > 12 -> hour - 12 else -> hour } "$displayHour:${minute.toString().padStart(2, '0')} $period" }.stateIn(viewModelScope, SharingStarted.Eagerly, "12:00 AM") // Season emoji val seasonEmoji: StateFlow = season.map { seasonName -> when (seasonName.lowercase()) { "spring" -> "🌸" "summer" -> "☀️" "autumn", "fall" -> "🍂" "winter" -> "❄️" else -> "🌍" } }.stateIn(viewModelScope, SharingStarted.Eagerly, "🌍") init { connect() } fun connect() { webSocketManager.connect() } fun disconnect() { webSocketManager.disconnect() } fun setSpeed(speed: Int) { webSocketManager.setSpeed(speed) analyticsManager.logGameSpeedChange(speed) } fun claimEvent(event: MessageEvent) { webSocketManager.claimEvent(event) soundManager.playClaimReward() analyticsManager.logEventClaim(event.id, event.type) } fun sendAnswer(answer: AnswerOption, questionId: String) { webSocketManager.sendAnswer(answer, questionId) } fun dismissCurrentQuestion() { webSocketManager.dismissCurrentQuestion() } fun dismissCurrentMessageEvent() { webSocketManager.dismissCurrentMessageEvent() } fun dismissCurrentError() { webSocketManager.dismissCurrentError() } fun dismissCurrentRelationshipEvent() { webSocketManager.dismissCurrentRelationshipEvent() } fun respondToRelationshipEvent(eventId: String, choiceId: String) { webSocketManager.respondToRelationshipEvent(eventId, choiceId) } fun breakUp(partnerId: String) { webSocketManager.breakUp(partnerId) } // Monetization actions fun showEnergyRefillModal() { webSocketManager.fetchEnergyRefillTiers() webSocketManager.setShowEnergyRefillModal(true) } fun hideEnergyRefillModal() { webSocketManager.setShowEnergyRefillModal(false) } fun purchaseEnergyRefill(tierType: String) { webSocketManager.purchaseEnergyRefill(tierType) } fun showTimeSkipModal() { webSocketManager.fetchTimeSkipTiers() webSocketManager.setShowTimeSkipModal(true) } fun hideTimeSkipModal() { webSocketManager.setShowTimeSkipModal(false) } fun purchaseTimeSkip(tierType: String) { webSocketManager.purchaseTimeSkip(tierType) } fun hideTimeSkipSummary() { webSocketManager.setShowTimeSkipSummary(false) } // Retention actions fun fetchAchievements() { webSocketManager.fetchAchievements() } fun acknowledgeAchievement(achievementId: String) { webSocketManager.acknowledgeAchievement(achievementId) } fun hideAchievementUnlock() { webSocketManager.setShowAchievementUnlock(false) } fun fetchDailyRewards() { webSocketManager.fetchDailyRewards() } fun claimDailyReward(day: Int) { webSocketManager.claimDailyReward(day) } fun showDailyRewards() { webSocketManager.fetchDailyRewards() webSocketManager.setShowDailyRewards(true) } fun hideDailyRewards() { webSocketManager.setShowDailyRewards(false) } fun fetchDailyQuests() { webSocketManager.fetchDailyQuests() } fun claimQuestReward(questId: String) { webSocketManager.claimQuestReward(questId) } fun showDailyQuests() { webSocketManager.fetchDailyQuests() webSocketManager.setShowDailyQuests(true) } fun hideDailyQuests() { webSocketManager.setShowDailyQuests(false) } // Resource checking fun canAfford(energyCost: Int = 0, moneyCost: Double = 0.0, diamondCost: Int = 0): Boolean { return energy.value >= energyCost && money.value >= moneyCost && diamonds.value >= diamondCost } // Monetization tier fetching fun fetchEnergyRefillTiers() { webSocketManager.fetchEnergyRefillTiers() } fun fetchTimeSkipTiers() { webSocketManager.fetchTimeSkipTiers() } // Character setup (onboarding) fun setupCharacter(name: String, age: Int, sex: String) { webSocketManager.setupCharacter(name, age, sex) } // Tier 2 retention spine actions /** Begin a new life from the death screen. mode = "heir" | "fresh". */ fun startNewLife(mode: String = "fresh") { webSocketManager.startNewLife(mode) } /** Perform a player-initiated activity (UI lands in Wave 2 / T015b). */ fun performActivity(activityId: String) { webSocketManager.performActivity(activityId) } /** In-life restart (ends current life -> character creation). Guarded in UI. */ fun restart() { webSocketManager.restart() } fun clearOfflineDigest() { webSocketManager.clearOfflineDigest() } fun clearLifeSummary() { webSocketManager.clearLifeSummary() } // Data export (GDPR) fun requestDataExport() { webSocketManager.requestDataExport() } fun clearDataExportPayload() { webSocketManager.clearDataExportPayload() } // Account deletion fun requestAccountDeletion() { webSocketManager.requestAccountDeletion() } fun clearAccountDeletionUpdate() { webSocketManager.clearAccountDeletionUpdate() } fun onMessageSent(characterId: String) { soundManager.playMessageSent() analyticsManager.logMessageSent(characterId) } fun onStorePurchase(itemId: String, name: String, price: Double) { soundManager.playCoins() analyticsManager.logPurchaseStart(itemId, name, price) } fun onDiamondPurchase() { soundManager.playDiamonds() } fun purchaseStoreItem(itemId: String) { webSocketManager.purchaseStoreItem(itemId) } val lastStorePurchaseItemId = webSocketManager.lastStorePurchaseItemId fun clearStorePurchaseCelebration() { webSocketManager.clearLastStorePurchaseItemId() } fun fetchStoreItems() { webSocketManager.fetchStoreItems() } fun enrollInActivity(activityId: String) { webSocketManager.enrollInActivity(activityId) } fun dropActivity(activityId: String) { webSocketManager.dropActivity(activityId) } fun sendPurchaseComplete(productId: String, purchaseToken: String) { webSocketManager.sendPurchaseComplete(productId, purchaseToken) } fun registerPushToken(token: String) { webSocketManager.registerPushToken(token) } fun sendDebugGrant( money: Int? = null, energy: Int? = null, diamonds: Int? = null, mode: String = "add" ) { webSocketManager.sendMessage( WebSocketCommands.debugGrant( money = money, energy = energy, diamonds = diamonds, mode = mode ) ) } fun sendDebugSetup(preset: String) { webSocketManager.sendMessage(WebSocketCommands.debugSetup(preset)) } }