package com.shaarit.presentation.edit import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.shaarit.domain.model.AiEnrichmentResult import com.shaarit.domain.model.ShaarliTag import com.shaarit.domain.repository.LinkRepository import com.shaarit.domain.usecase.AnalyzeUrlWithAiUseCase import com.shaarit.presentation.add.ContentType import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @HiltViewModel class EditLinkViewModel @Inject constructor( private val linkRepository: LinkRepository, private val analyzeUrlWithAiUseCase: AnalyzeUrlWithAiUseCase, savedStateHandle: SavedStateHandle ) : ViewModel() { private val linkId: Int = savedStateHandle["linkId"] ?: -1 private val _uiState = MutableStateFlow(EditLinkUiState.Loading) val uiState = _uiState.asStateFlow() var url = MutableStateFlow("") var title = MutableStateFlow("") var description = MutableStateFlow("") var isPrivate = MutableStateFlow(false) // Content type - détecté automatiquement ou choisi par l'utilisateur private val _contentType = MutableStateFlow(ContentType.BOOKMARK) val contentType = _contentType.asStateFlow() // Tags du lien original pour détecter si c'est une note private var originalTags: List = emptyList() 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() private val _aiEnrichmentState = MutableStateFlow(AiEnrichmentState.Idle) val aiEnrichmentState = _aiEnrichmentState.asStateFlow() private val _aiErrorMessage = MutableSharedFlow() val aiErrorMessage = _aiErrorMessage.asSharedFlow() init { loadLink() loadAvailableTags() } private fun loadLink() { viewModelScope.launch { _uiState.value = EditLinkUiState.Loading linkRepository.getLink(linkId).fold( onSuccess = { link -> url.value = link.url title.value = link.title description.value = link.description isPrivate.value = link.isPrivate _selectedTags.value = link.tags originalTags = link.tags // Détecter si c'est une note val isNote = link.tags.contains("note") || link.url.startsWith("note://") _contentType.value = if (isNote) ContentType.NOTE else ContentType.BOOKMARK _uiState.value = EditLinkUiState.Loaded }, onFailure = { error -> _uiState.value = EditLinkUiState.Error(error.message ?: "Failed to load link") } ) } } private fun loadAvailableTags() { viewModelScope.launch { linkRepository .getTags() .fold( onSuccess = { tags -> _availableTags.value = tags.sortedByDescending { it.occurrences } }, onFailure = { // Silently fail - tags are optional } ) } } /** * Change le type de contenu (Bookmark ou Note) */ fun setContentType(type: ContentType) { _contentType.value = type // Auto-add "note" tag when Note type is selected if (type == ContentType.NOTE && "note" !in _selectedTags.value) { addTag("note") } else if (type == ContentType.BOOKMARK && "note" in _selectedTags.value) { // Remove "note" tag when switching back to Bookmark removeTag("note") } } 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 analyzeUrlWithAi() { val currentUrl = url.value if (currentUrl.isBlank()) { viewModelScope.launch { _aiErrorMessage.emit("Veuillez d'abord entrer une URL") } return } if (!analyzeUrlWithAiUseCase.isApiKeyConfigured()) { viewModelScope.launch { _aiErrorMessage.emit("Clé API Gemini non configurée. Allez dans Paramètres.") } return } viewModelScope.launch { _aiEnrichmentState.value = AiEnrichmentState.Loading analyzeUrlWithAiUseCase(currentUrl) .onSuccess { result -> applyAiEnrichment(result) _aiEnrichmentState.value = AiEnrichmentState.Success } .onFailure { error -> _aiEnrichmentState.value = AiEnrichmentState.Idle _aiErrorMessage.emit(error.message ?: "Erreur lors de l'analyse IA") } } } private fun applyAiEnrichment(result: AiEnrichmentResult) { title.value = result.title description.value = result.description // Add AI-generated tags to existing tags (without duplicates) val currentTags = _selectedTags.value.toMutableSet() result.tags.forEach { tag -> val cleanTag = tag.trim().lowercase() if (cleanTag.isNotBlank()) { currentTags.add(cleanTag) } } _selectedTags.value = currentTags.toList() } fun resetAiEnrichmentState() { _aiEnrichmentState.value = AiEnrichmentState.Idle } fun removeTag(tag: String) { _selectedTags.value = _selectedTags.value - tag } fun updateLink() { viewModelScope.launch { _uiState.value = EditLinkUiState.Saving val currentUrl = url.value val currentTitle = title.value // Validation based on content type when (_contentType.value) { ContentType.BOOKMARK -> { if (currentUrl.isBlank()) { _uiState.value = EditLinkUiState.Error("URL is required for bookmarks") return@launch } } ContentType.NOTE -> { if (currentTitle.isBlank()) { _uiState.value = EditLinkUiState.Error("Title is required for notes") return@launch } } } linkRepository.updateLink( id = linkId, url = if (_contentType.value == ContentType.NOTE && currentUrl.isBlank()) "note://local/${System.currentTimeMillis()}" else currentUrl, title = currentTitle.ifBlank { null }, description = description.value.ifBlank { null }, tags = _selectedTags.value.ifEmpty { null }, isPrivate = isPrivate.value ).fold( onSuccess = { _uiState.value = EditLinkUiState.Success }, onFailure = { error -> _uiState.value = EditLinkUiState.Error(error.message ?: "Failed to update link") } ) } } } sealed class EditLinkUiState { object Loading : EditLinkUiState() object Loaded : EditLinkUiState() object Saving : EditLinkUiState() object Success : EditLinkUiState() data class Error(val message: String) : EditLinkUiState() } sealed class AiEnrichmentState { object Idle : AiEnrichmentState() object Loading : AiEnrichmentState() object Success : AiEnrichmentState() }