package com.hsdiary.ui.trends import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.hsdiary.data.db.entity.ConditionDefinitionEntity import com.hsdiary.data.db.entity.ConditionEntryEntity import com.hsdiary.data.db.entity.DayLogEntity import com.hsdiary.data.db.entity.ProfileEntity import com.hsdiary.data.model.ProfileType import com.hsdiary.data.preferences.UserPreferences import com.hsdiary.data.repository.CycleRepository import com.hsdiary.data.repository.DayLogRepository import com.hsdiary.data.repository.ProfileRepository import com.hsdiary.domain.CyclePredictionEngine import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import java.time.LocalDate import javax.inject.Inject enum class TrendsRange { DAYS_30, MONTHS_3, MONTHS_6, ALL } data class ConditionFrequency( val definition: ConditionDefinitionEntity, val count: Int, val isRecurring: Boolean, val avgRating: Float, val weeklyData: List ) data class HealthTrendsUiState( val profile: ProfileEntity? = null, val range: TrendsRange = TrendsRange.DAYS_30, val conditionFrequencies: List = emptyList(), val selectedCondition: ConditionDefinitionEntity? = null, val isLoading: Boolean = true, val isFemale: Boolean = false ) @HiltViewModel class HealthTrendsViewModel @Inject constructor( private val profileRepository: ProfileRepository, private val dayLogRepository: DayLogRepository, private val userPreferences: UserPreferences ) : ViewModel() { private val _range = MutableStateFlow(TrendsRange.DAYS_30) private val _selectedCondition = MutableStateFlow(null) private val _uiState = MutableStateFlow(HealthTrendsUiState()) val uiState: StateFlow = _uiState.asStateFlow() init { viewModelScope.launch { load() } } private suspend fun load() { val activeId = userPreferences.activeProfileId.first() val profiles = profileRepository.getAllProfilesOnce() val profile = profiles.find { it.id == activeId } ?: profiles.firstOrNull() ?: return val isFemale = profile.profileType == ProfileType.FEMALE.name val allDefs = dayLogRepository.getAllDefinitions() .filter { it.profileVisibility == "ALL" || (isFemale && it.profileVisibility == "FEMALE_ONLY") } _range.combine(_selectedCondition) { range, sel -> range to sel } .onEach { (range, sel) -> val today = LocalDate.now() val startDate = when (range) { TrendsRange.DAYS_30 -> today.minusDays(29) TrendsRange.MONTHS_3 -> today.minusMonths(3) TrendsRange.MONTHS_6 -> today.minusMonths(6) TrendsRange.ALL -> today.minusYears(5) } val entries = dayLogRepository.getConditionsInRange( profile.id, startDate.toString(), today.toString() ) val dayLogs = dayLogRepository.getDayLogsInRangeOnce( profile.id, startDate.toString(), today.toString() ) val dayLogMap = dayLogs.associateBy { it.id } val grouped = entries.groupBy { it.conditionKey } val frequencies = allDefs.mapNotNull { def -> val entriesForKey = grouped[def.conditionKey] ?: return@mapNotNull null val weeklyData = buildWeeklyData(entriesForKey, dayLogMap, startDate, today) ConditionFrequency( definition = def, count = entriesForKey.size, isRecurring = entriesForKey.size >= 3, avgRating = entriesForKey.map { it.rating }.average().toFloat(), weeklyData = weeklyData ) }.sortedByDescending { it.count } _uiState.value = HealthTrendsUiState( profile = profile, range = range, conditionFrequencies = frequencies, selectedCondition = sel, isLoading = false, isFemale = isFemale ) } .launchIn(viewModelScope) } private fun buildWeeklyData( entries: List, dayLogMap: Map, startDate: LocalDate, today: LocalDate ): List { val weeks = mutableListOf() var weekStart = startDate while (!weekStart.isAfter(today)) { val weekEnd = weekStart.plusDays(6) val count = entries.count { entry -> val log = dayLogMap[entry.dayLogId] ?: return@count false val date = LocalDate.parse(log.date) !date.isBefore(weekStart) && !date.isAfter(weekEnd) } weeks.add(count) weekStart = weekStart.plusWeeks(1) } return weeks } fun setRange(range: TrendsRange) { _range.value = range } fun selectCondition(def: ConditionDefinitionEntity?) { _selectedCondition.value = def } }