package com.tt.honeyDue.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tt.honeyDue.data.DataManager import com.tt.honeyDue.data.IDataManager import com.tt.honeyDue.models.ContractorSummary import com.tt.honeyDue.models.MyResidencesResponse import com.tt.honeyDue.models.Residence import com.tt.honeyDue.models.ResidenceCreateRequest import com.tt.honeyDue.models.TaskColumnsResponse import com.tt.honeyDue.models.TotalSummary import com.tt.honeyDue.network.APILayer import com.tt.honeyDue.network.ApiResult import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** * ResidenceViewModel — read-state derived from [IDataManager]. * * All list/detail reads (`residencesState`, `myResidencesState`, * `summaryState`, `residenceTasksState`, `residenceContractorsState`) * are reactive projections of [IDataManager] StateFlows. Mutation * feedback (create/update/delete/join/cancel/uncancel/updateTask/ * generateReport) remains owned by the VM as one-shot [ApiResult] * fields — they track API operation outcomes, not cached data. */ class ResidenceViewModel( private val dataManager: IDataManager = DataManager, ) : ViewModel() { // ---------- Read state (derived from DataManager) ---------- val residencesState: StateFlow>> = dataManager.residences .map { list -> if (list.isNotEmpty()) ApiResult.Success(list) else ApiResult.Idle } .stateIn( viewModelScope, SharingStarted.Eagerly, dataManager.residences.value .takeIf { it.isNotEmpty() } ?.let { ApiResult.Success(it) } ?: ApiResult.Idle, ) val myResidencesState: StateFlow> = dataManager.myResidences .map { if (it != null) ApiResult.Success(it) else ApiResult.Idle } .stateIn( viewModelScope, SharingStarted.Eagerly, dataManager.myResidences.value ?.let { ApiResult.Success(it) } ?: ApiResult.Idle, ) val summaryState: StateFlow> = dataManager.totalSummary .map { if (it != null) ApiResult.Success(it) else ApiResult.Idle } .stateIn( viewModelScope, SharingStarted.Eagerly, dataManager.totalSummary.value ?.let { ApiResult.Success(it) } ?: ApiResult.Idle, ) /** Drives the residence-scoped projections. */ private val _selectedResidenceId = MutableStateFlow(null) /// Residence-scoped kanban derived from `DataManager.allTasks` filtered /// by `_selectedResidenceId`. Single source of truth — eliminates the /// gitea#2 race window where the per-residence cache slot could be /// empty while `_allTasks` was populated. The per-residence cache /// (`tasksByResidence`) was deleted in cec521b. val residenceTasksState: StateFlow> = combine(_selectedResidenceId, DataManager.allTasks) { id, all -> when { id == null -> ApiResult.Idle all == null -> ApiResult.Loading else -> { val filtered = DataManager.getTasksForResidence(id) if (filtered != null) ApiResult.Success(filtered) else ApiResult.Loading } } }.stateIn( viewModelScope, SharingStarted.Eagerly, _selectedResidenceId.value?.let { id -> DataManager.getTasksForResidence(id)?.let { ApiResult.Success(it) } } ?: ApiResult.Idle, ) val residenceContractorsState: StateFlow>> = combine(_selectedResidenceId, dataManager.contractorsByResidence) { id, map -> if (id == null) ApiResult.Idle else map[id]?.let { ApiResult.Success(it) } ?: ApiResult.Idle }.stateIn( viewModelScope, SharingStarted.Eagerly, _selectedResidenceId.value?.let { id -> dataManager.contractorsByResidence.value[id]?.let { ApiResult.Success(it) } } ?: ApiResult.Idle, ) // ---------- Loading / error feedback ---------- private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow = _isLoading private val _loadError = MutableStateFlow(null) val loadError: StateFlow = _loadError // ---------- Mutation-feedback (one-shot, owned by VM) ---------- private val _createResidenceState = MutableStateFlow>(ApiResult.Idle) val createResidenceState: StateFlow> = _createResidenceState private val _updateResidenceState = MutableStateFlow>(ApiResult.Idle) val updateResidenceState: StateFlow> = _updateResidenceState private val _deleteResidenceState = MutableStateFlow>(ApiResult.Idle) val deleteResidenceState: StateFlow> = _deleteResidenceState private val _joinResidenceState = MutableStateFlow>(ApiResult.Idle) val joinResidenceState: StateFlow> = _joinResidenceState private val _cancelTaskState = MutableStateFlow>(ApiResult.Idle) val cancelTaskState: StateFlow> = _cancelTaskState private val _uncancelTaskState = MutableStateFlow>(ApiResult.Idle) val uncancelTaskState: StateFlow> = _uncancelTaskState private val _updateTaskState = MutableStateFlow>(ApiResult.Idle) val updateTaskState: StateFlow> = _updateTaskState private val _generateReportState = MutableStateFlow>(ApiResult.Idle) val generateReportState: StateFlow> = _generateReportState // ---------- Projection selectors ---------- fun selectResidence(residenceId: Int?) { _selectedResidenceId.value = residenceId } // ---------- Load methods (write-through to DataManager) ---------- fun loadResidences(forceRefresh: Boolean = false) { viewModelScope.launch { _isLoading.value = true _loadError.value = null _loadError.value = (APILayer.getResidences(forceRefresh = forceRefresh) as? ApiResult.Error)?.message _isLoading.value = false } } fun loadSummary(forceRefresh: Boolean = false) { viewModelScope.launch { _isLoading.value = true _loadError.value = null _loadError.value = (APILayer.getSummary(forceRefresh = forceRefresh) as? ApiResult.Error)?.message _isLoading.value = false } } fun getResidence(id: Int, onResult: (ApiResult) -> Unit) { viewModelScope.launch { onResult(APILayer.getResidence(id)) } } fun loadMyResidences(forceRefresh: Boolean = false) { viewModelScope.launch { _isLoading.value = true _loadError.value = null _loadError.value = (APILayer.getMyResidences(forceRefresh = forceRefresh) as? ApiResult.Error)?.message _isLoading.value = false } } fun loadResidenceTasks(residenceId: Int) { viewModelScope.launch { _selectedResidenceId.value = residenceId _isLoading.value = true _loadError.value = null _loadError.value = (APILayer.getTasksByResidence(residenceId) as? ApiResult.Error)?.message _isLoading.value = false } } fun loadResidenceContractors(residenceId: Int) { viewModelScope.launch { _selectedResidenceId.value = residenceId _isLoading.value = true _loadError.value = null _loadError.value = (APILayer.getContractorsByResidence(residenceId) as? ApiResult.Error)?.message _isLoading.value = false } } fun resetResidenceTasksState() { _selectedResidenceId.value = null } fun resetResidenceContractorsState() { _selectedResidenceId.value = null } // ---------- Mutations ---------- fun createResidence(request: ResidenceCreateRequest) { viewModelScope.launch { _createResidenceState.value = ApiResult.Loading _createResidenceState.value = APILayer.createResidence(request) } } fun updateResidence(residenceId: Int, request: ResidenceCreateRequest) { viewModelScope.launch { _updateResidenceState.value = ApiResult.Loading _updateResidenceState.value = APILayer.updateResidence(residenceId, request) } } fun resetCreateState() { _createResidenceState.value = ApiResult.Idle } fun resetUpdateState() { _updateResidenceState.value = ApiResult.Idle } fun cancelTask(taskId: Int) { viewModelScope.launch { _cancelTaskState.value = ApiResult.Loading _cancelTaskState.value = APILayer.cancelTask(taskId) } } fun uncancelTask(taskId: Int) { viewModelScope.launch { _uncancelTaskState.value = ApiResult.Loading _uncancelTaskState.value = APILayer.uncancelTask(taskId) } } fun updateTask(taskId: Int, request: com.tt.honeyDue.models.TaskCreateRequest) { viewModelScope.launch { _updateTaskState.value = ApiResult.Loading _updateTaskState.value = APILayer.updateTask(taskId, request) } } fun resetCancelTaskState() { _cancelTaskState.value = ApiResult.Idle } fun resetUncancelTaskState() { _uncancelTaskState.value = ApiResult.Idle } fun resetUpdateTaskState() { _updateTaskState.value = ApiResult.Idle } fun generateTasksReport(residenceId: Int, email: String? = null) { viewModelScope.launch { _generateReportState.value = ApiResult.Loading _generateReportState.value = APILayer.generateTasksReport(residenceId, email) } } fun resetGenerateReportState() { _generateReportState.value = ApiResult.Idle } fun deleteResidence(residenceId: Int) { viewModelScope.launch { _deleteResidenceState.value = ApiResult.Loading _deleteResidenceState.value = APILayer.deleteResidence(residenceId) } } fun resetDeleteResidenceState() { _deleteResidenceState.value = ApiResult.Idle } fun joinWithCode(code: String) { viewModelScope.launch { _joinResidenceState.value = ApiResult.Loading _joinResidenceState.value = APILayer.joinWithCode(code) } } fun resetJoinResidenceState() { _joinResidenceState.value = ApiResult.Idle } }