package com.shaarit.presentation.add import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.shaarit.domain.model.ShaarliTag import com.shaarit.domain.repository.AddLinkResult import com.shaarit.domain.repository.LinkRepository import dagger.hilt.android.lifecycle.HiltViewModel import java.net.URLDecoder import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @HiltViewModel class AddLinkViewModel @Inject constructor(private val linkRepository: LinkRepository, savedStateHandle: SavedStateHandle) : ViewModel() { // Pre-fill from usage arguments (e.g. from Share Intent via NavGraph) private val initialUrl: String? = savedStateHandle["url"] private val initialTitle: String? = savedStateHandle["title"] private val _uiState = MutableStateFlow(AddLinkUiState.Idle) val uiState = _uiState.asStateFlow() var url = MutableStateFlow(decodeUrlParam(initialUrl) ?: "") var title = MutableStateFlow(decodeUrlParam(initialTitle) ?: "") var description = MutableStateFlow("") var isPrivate = MutableStateFlow(false) // New tag management private val _selectedTags = MutableStateFlow>(emptyList()) val selectedTags = _selectedTags.asStateFlow() private val _newTagInput = MutableStateFlow("") val newTagInput = _newTagInput.asStateFlow() private val _availableTags = MutableStateFlow>(emptyList()) val availableTags = _availableTags.asStateFlow() private val _tagSuggestions = MutableStateFlow>(emptyList()) val tagSuggestions = _tagSuggestions.asStateFlow() // For conflict handling private var conflictLinkId: Int? = null init { loadAvailableTags() } /** Decodes URL-encoded parameters, handling both + signs and %20 for spaces */ private fun decodeUrlParam(param: String?): String? { if (param.isNullOrBlank()) return null return try { // First decode URL encoding, then replace + with spaces // The + signs appear because URLEncoder uses + for spaces URLDecoder.decode(param, "UTF-8").replace("+", " ").trim() } catch (e: Exception) { // If decoding fails, just replace + with spaces param.replace("+", " ").trim() } } private fun loadAvailableTags() { viewModelScope.launch { linkRepository .getTags() .fold( onSuccess = { tags -> _availableTags.value = tags.sortedByDescending { it.occurrences } }, onFailure = { // Silently fail - tags are optional } ) } } fun onNewTagInputChanged(input: String) { _newTagInput.value = input updateTagSuggestions(input) } private fun updateTagSuggestions(query: String) { if (query.isBlank()) { _tagSuggestions.value = emptyList() return } val queryLower = query.lowercase() _tagSuggestions.value = _availableTags .value .filter { it.name.lowercase().contains(queryLower) && it.name !in _selectedTags.value } .take(10) } fun addTag(tag: String) { val cleanTag = tag.trim().lowercase() if (cleanTag.isNotBlank() && cleanTag !in _selectedTags.value) { _selectedTags.value = _selectedTags.value + cleanTag _newTagInput.value = "" _tagSuggestions.value = emptyList() } } fun addNewTag() { addTag(_newTagInput.value) } fun removeTag(tag: String) { _selectedTags.value = _selectedTags.value - tag } fun addLink() { viewModelScope.launch { _uiState.value = AddLinkUiState.Loading // Basic validation val currentUrl = url.value if (currentUrl.isBlank()) { _uiState.value = AddLinkUiState.Error("URL is required") return@launch } val result = linkRepository.addOrUpdateLink( url = currentUrl, title = title.value.ifBlank { null }, description = description.value.ifBlank { null }, tags = _selectedTags.value.ifEmpty { null }, isPrivate = isPrivate.value, forceUpdate = false, existingLinkId = null ) when (result) { is AddLinkResult.Success -> { _uiState.value = AddLinkUiState.Success } is AddLinkResult.Conflict -> { conflictLinkId = result.existingLinkId _uiState.value = AddLinkUiState.Conflict( existingLinkId = result.existingLinkId, existingTitle = result.existingTitle ) } is AddLinkResult.Error -> { _uiState.value = AddLinkUiState.Error(result.message) } } } } fun forceUpdateExistingLink() { val linkId = conflictLinkId ?: return viewModelScope.launch { _uiState.value = AddLinkUiState.Loading val result = linkRepository.addOrUpdateLink( url = url.value, title = title.value.ifBlank { null }, description = description.value.ifBlank { null }, tags = _selectedTags.value.ifEmpty { null }, isPrivate = isPrivate.value, forceUpdate = true, existingLinkId = linkId ) when (result) { is AddLinkResult.Success -> { _uiState.value = AddLinkUiState.Success } is AddLinkResult.Error -> { _uiState.value = AddLinkUiState.Error(result.message) } else -> { _uiState.value = AddLinkUiState.Error("Unexpected error") } } } } fun dismissConflict() { conflictLinkId = null _uiState.value = AddLinkUiState.Idle } // Legacy compatibility for old comma-separated tags input @Deprecated("Use selectedTags instead") var tags = MutableStateFlow("") } sealed class AddLinkUiState { object Idle : AddLinkUiState() object Loading : AddLinkUiState() object Success : AddLinkUiState() data class Error(val message: String) : AddLinkUiState() data class Conflict(val existingLinkId: Int, val existingTitle: String?) : AddLinkUiState() }