@file:Suppress("HardCodedStringLiteral", "unused")

package eu.gaudian.translator.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.DictionaryEntry
import eu.gaudian.translator.model.EntryPart
import eu.gaudian.translator.model.EtymologyData
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.communication.FileInfo
import eu.gaudian.translator.model.communication.RetrofitClient
import eu.gaudian.translator.model.grammar.DictionaryEntryData
import eu.gaudian.translator.model.repository.DictionaryFileRepository
import eu.gaudian.translator.model.repository.DictionaryJsonService
import eu.gaudian.translator.model.repository.DictionaryLookupRepository
import eu.gaudian.translator.model.repository.DictionaryRepository
import eu.gaudian.translator.model.repository.DictionaryWordEntry
import eu.gaudian.translator.model.repository.LanguageRepository
import eu.gaudian.translator.model.repository.SettingsRepository
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.StatusMessageService
import eu.gaudian.translator.utils.dictionary.DictionaryService
import eu.gaudian.translator.utils.dictionary.LocalAlternativeForm
import eu.gaudian.translator.utils.dictionary.LocalDictionaryAccess
import eu.gaudian.translator.utils.dictionary.LocalDictionaryWordInfo
import eu.gaudian.translator.utils.dictionary.LocalRelatedWordInfo
import eu.gaudian.translator.utils.dictionary.LocalTranslationInfo
import eu.gaudian.translator.utils.parseDefinitionsFromHtml
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonPrimitive
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject

@HiltViewModel
class DictionaryViewModel @Inject constructor(
    application: Application,
    private val dictionaryService: DictionaryService,
    private val dictionaryRepository: DictionaryRepository,
    private val languageRepository: LanguageRepository,
    private val settingsRepository: SettingsRepository,
    private val dictionaryFileRepository: DictionaryFileRepository,
    private val dictionaryLookupRepository: DictionaryLookupRepository,
    private val dictionaryJsonService: DictionaryJsonService,
    private val statusMessageService: StatusMessageService
) : AndroidViewModel(application) {

    private val idCounter = AtomicInteger(0)

    private val _uiState = MutableStateFlow(DictionaryUiState())
    val uiState: StateFlow<DictionaryUiState> = _uiState.asStateFlow()

    // Wrapper class for UI display of dictionary items with pre-calculated values
    data class DictionaryUiItem(
        val fileInfo: FileInfo,
        val isDownloaded: Boolean,
        val hasUpdate: Boolean,
        val size: Long
    )

    // StateFlow exposing local dictionary entries for a given word/language
    private val _localEntries = MutableStateFlow<List<DictionaryWordEntry>>(emptyList())
    val localEntries: StateFlow<List<DictionaryWordEntry>> = _localEntries.asStateFlow()

    // StateFlow exposing current entry data with language info
    private val _currentEntryData = MutableStateFlow<EntryData?>(null)
    val currentEntryData: StateFlow<EntryData?> = _currentEntryData.asStateFlow()

    // StateFlow exposing TTS availability for current entry
    private val _isTtsAvailable = MutableStateFlow(false)
    val isTtsAvailable: StateFlow<Boolean> = _isTtsAvailable.asStateFlow()

    // StateFlow exposing local dictionary availability for selected language
    private val _isLocalDictAvailable = MutableStateFlow(false)
    val isLocalDictAvailable: StateFlow<Boolean> = _isLocalDictAvailable.asStateFlow()

    // StateFlow for suggestions with debounce support
    private val _suggestions = MutableStateFlow<List<String>>(emptyList())
    val suggestions: StateFlow<List<String>> = _suggestions.asStateFlow()

    private val _searchHistoryEntries = MutableStateFlow<List<DictionaryEntry>>(emptyList())
    val searchHistoryEntries = _searchHistoryEntries.asStateFlow()

    private val _currentEntryId = MutableStateFlow<Int?>(null)

    private val _navigateToEntry = MutableStateFlow<DictionaryEntry?>(null)
    val navigateToEntry: StateFlow<DictionaryEntry?> = _navigateToEntry.asStateFlow()

    private val _generatingEntryIds = MutableStateFlow<Set<Int>>(emptySet())
    val generatingEntryIds: StateFlow<Set<Int>> = _generatingEntryIds.asStateFlow()

    private val _breadcrumbs = MutableStateFlow<List<DictionaryEntry>>(emptyList())
    val breadcrumbs: StateFlow<List<DictionaryEntry>> = _breadcrumbs.asStateFlow()

    private var _pendingBreadcrumbReset = false
    private var _isNavigatingFromDictionaryResult = false
    
    // Public StateFlow to expose navigation source
    private val _isNavigatingFromDictionaryResultFlow = MutableStateFlow(false)
    val isNavigatingFromDictionaryResultFlow: StateFlow<Boolean> = _isNavigatingFromDictionaryResultFlow.asStateFlow()

    // Developer-only: currently displayed entry when cycling through local dictionary
    private val _developerCurrentEntry = MutableStateFlow<DictionaryEntry?>(null)
    val developerCurrentEntry: StateFlow<DictionaryEntry?> = _developerCurrentEntry.asStateFlow()

    private val _wordOfTheDay = MutableStateFlow<DictionaryEntry?>(null)
    val wordOfTheDay: StateFlow<DictionaryEntry?> = _wordOfTheDay.asStateFlow()


    init {
        loadSavedEntries()
        viewModelScope.launch {
            dictionaryRepository.loadDictionaryEntry().collect { entries -> _searchHistoryEntries.value = entries }
        }
        loadWordOfTheDay(forceRefresh = false)
    }

    private suspend fun fetchWiktionaryDefinitions(word: String, language: Language): List<String>? {
        val normalized = word.trim().ifEmpty { return null }
        val pageName = normalized.replace(' ', '_')
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "[Wikt] start fetch for word='$normalized' (page='$pageName')")
        return try {
            // First attempt with given page
            var response = RetrofitClient.api.getPageHtml(page = pageName)
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "[Wikt] raw response: $response")
            var parse = response.parse
            if (parse == null) {
                // Retry with capitalized first letter (common on Wiktionary)
                val cap = pageName.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
                if (cap != pageName) {
                    @Suppress("HardCodedStringLiteral")
                    Log.d("DictionaryViewModel", "[Wikt] retrying with capitalized page='$cap'")
                    response = RetrofitClient.api.getPageHtml(page = cap)
                    parse = response.parse
                }
            }
            if (parse == null) {
                @Suppress("HardCodedStringLiteral")
                Log.w("DictionaryViewModel", "[Wikt] response.parse is null for word='$normalized'")
                return null
            }
            val htmlContent = parse.text?.html
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "[Wikt] parse.title='${parse.title}', html length='${htmlContent?.length ?: 0}'")
            if (htmlContent != null) {
                val defs = parseDefinitionsFromHtml(htmlContent, language.name)
                @Suppress("HardCodedStringLiteral")
                Log.d("DictionaryViewModel", "[Wikt] parsed definitions count=${defs.size}")
                defs
            } else {
                @Suppress("HardCodedStringLiteral")
                Log.w("DictionaryViewModel", "[Wikt] htmlContent (parse.text[\"*\"]) is null — can't parse definitions")
                null
            }
        } catch (e: Exception) {
            @Suppress("HardCodedStringLiteral")
            Log.e("DictionaryViewModel", "[Wikt] exception: ${e::class.java.simpleName}: ${e.message}")
            null
        }
    }


    fun refreshWordOfTheDay() {
        loadWordOfTheDay(forceRefresh = true)
    }

    private fun loadWordOfTheDay(forceRefresh: Boolean) {
        viewModelScope.launch {
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "loadWordOfTheDay(forceRefresh=$forceRefresh)")
            _uiState.value = _uiState.value.copy(isLoading = true, error = null)
            val selectedLanguage = languageRepository.loadSelectedDictionaryLanguage().first()

            if (selectedLanguage == null) {
                _uiState.value = _uiState.value.copy(
                    isLoading = false,
                    error = getApplication<Application>().getString(R.string.text_please_select_a_dictionary_language_first)
                )
                return@launch
            }

            val wordOfTheDay = dictionaryService.getWordOfTheDay(selectedLanguage, forceRefresh)?.copy(
                id = idCounter.incrementAndGet()
            )

            if (wordOfTheDay != null) {
                val updatedEntries = _searchHistoryEntries.value.toMutableList()
                updatedEntries.add(0, wordOfTheDay)
                _searchHistoryEntries.value = updatedEntries
                _wordOfTheDay.value = wordOfTheDay

                try {
                    dictionaryRepository.saveDictionaryEntry(wordOfTheDay)
                } catch (e: Exception) {
                    _uiState.value = _uiState.value.copy(
                        error = getApplication<Application>().getString(
                            R.string.text_error_saving_entry,
                            e.localizedMessage
                        )
                    )
                }
            } else {
                _uiState.value = _uiState.value.copy(error = getApplication<Application>().getString(
                    R.string.text_could_not_fetch_a_new_word
                ))
            }
            _uiState.value = _uiState.value.copy(isLoading = false)
        }
    }


    private fun loadSavedEntries() {
        viewModelScope.launch {
            try {
                val entries = dictionaryRepository.loadDictionaryEntry().first()
                _searchHistoryEntries.value = entries
                val maxId = entries.maxOfOrNull { it.id } ?: 0
                idCounter.set(maxId)
                @Suppress("HardCodedStringLiteral")
                Log.d("DictionaryViewModel", "Loaded ${entries.size} saved entries; maxId=$maxId")
            } catch (e: Exception) {
                @Suppress("HardCodedStringLiteral")
                Log.e("DictionaryViewModel", "Failed to load stored values: ${e.message}")
                _uiState.value = _uiState.value.copy(
                    error = getApplication<Application>().getString(
                        R.string.text_error_loading_stored_values,
                        e.localizedMessage
                    )
                )
            }
        }
    }

    fun performSearch(query: String, language: Language, regenerate: Boolean = false, useDownloaded: Boolean = false, isDrillDown: Boolean = false) {
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "performSearch(query='$query', lang='${language.englishName}', regen=$regenerate, useDownloaded=$useDownloaded, isDrillDown=$isDrillDown)")
        
        if (!isDrillDown && !regenerate) {
            _pendingBreadcrumbReset = true
        }

        // 1. Check for existing entry in history
        val existingEntry = _searchHistoryEntries.value.find {
            it.word.equals(query, ignoreCase = true) && it.languageCode == language.nameResId
        }

        // 2. Prepare the entry to navigate to
        val entryToUse = if (existingEntry != null && !regenerate) {
            // Use existing entry
            existingEntry
        } else {
            // Create a new entry (or update existing if regenerating)
            val id = if (regenerate && existingEntry != null) existingEntry.id else idCounter.incrementAndGet()
            DictionaryEntry(
                id = id,
                word = query,
                definition = emptyList(), // Start empty, will be populated by AI/Wiktionary
                languageCode = language.nameResId,
                languageName = language.name
            )
        }

        // 3. Save/Update initial state in repository
        viewModelScope.launch {
            if (regenerate && existingEntry != null) {
                // If regenerating, we might want to clear old definitions first? 
                // For now, we keep them until new ones arrive, or we could clear them.
                // Let's keep the object as is for now, but we will trigger the loading state.
                // Actually, if we want to show "Generating...", we should probably signal that.
                // But we don't want to wipe the screen blank if we have old data.
                // The UI will show a loading indicator overlay or similar.
            } else if (existingEntry == null) {
                dictionaryRepository.saveDictionaryEntry(entryToUse)
            }
        }

        // 4. Navigate immediately
        _navigateToEntry.value = entryToUse

        // 5. If we are in "Offline Mode" (useDownloaded = true), we stop here.
        // The ResultScreen will show local entries.
        if (useDownloaded) {
            return
        }

        // 6. If Online, trigger AI/Wiktionary fetch in background
        val entryId = entryToUse.id
        _generatingEntryIds.value += entryId

        viewModelScope.launch {
            try {
                // A. Try Wiktionary first if enabled
                val tryWiktionary = settingsRepository.tryWiktionaryFirst.flow.first()
                if (tryWiktionary) {
                    @Suppress("HardCodedStringLiteral")
                    Log.d("DictionaryViewModel", "Attempting Wiktionary for '$query'")
                    val wiktionaryDefs = withContext(Dispatchers.IO) { fetchWiktionaryDefinitions(query, language) }
                    
                    if (!wiktionaryDefs.isNullOrEmpty()) {
                        @Suppress("HardCodedStringLiteral")
                        val parts = listOf(
                            EntryPart(
                                title = "Wiktionary",
                                content = JsonArray(wiktionaryDefs.map { JsonPrimitive(it) })
                            )
                        )
                        updateEntryDefinitions(entryId, parts)
                        _generatingEntryIds.value -= entryId
                        return@launch
                    }
                }

                // B. Fallback to AI Service
                dictionaryService.searchDefinition(query, language)
                    .onSuccess { entryFromServer ->
                        @Suppress("HardCodedStringLiteral")
                        Log.d("DictionaryViewModel", "AI service returned entry for '$query'")
                        updateEntryDefinitions(entryId, entryFromServer.definition)
                    }
                    .onFailure { exception ->
                        @Suppress("HardCodedStringLiteral")
                        Log.e("DictionaryViewModel", "AI service failed for '$query': ${exception.message}")
                        // Optionally update entry with error part or just show toast
                        // For now, we just stop loading.
                    }

            } catch (e: Exception) {
                Log.e("DictionaryViewModel", "Error during search: ${e.message}")
            } finally {
                _generatingEntryIds.value -= entryId
            }
        }
    }

    private suspend fun updateEntryDefinitions(entryId: Int, newDefinitions: List<EntryPart>) {
        val currentList = _searchHistoryEntries.value
        val entryIndex = currentList.indexOfFirst { it.id == entryId }
        if (entryIndex != -1) {
            val updatedEntry = currentList[entryIndex].copy(definition = newDefinitions)
            dictionaryRepository.updateDictionaryEntry(updatedEntry)
        }
    }

    fun onNavigationDone() {
        _navigateToEntry.value = null
    }

    fun getDictionaryEntryById(id: Int): DictionaryEntry? {
        // Use the already loaded searchHistory - don't call loadSavedEntries() here
        // as it would create a race condition
        return _searchHistoryEntries.value.find { it.id == id }
    }



    fun clearCurrentEntryId() {
        _currentEntryId.value = null
    }

    /**
     * Clears all breadcrumbs when leaving DictionaryResultScreen
     */
    fun clearBreadcrumbs() {
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "[Breadcrumb] CLEARING ALL BREADCRUMBS - user is leaving DictionaryResultScreen")
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "[Breadcrumb] Current breadcrumbs before clearing: ${_breadcrumbs.value.map { it.word }}")
        _breadcrumbs.value = emptyList()
        _pendingBreadcrumbReset = false
        _isNavigatingFromDictionaryResult = false
        // Also reset the StateFlow for consistency
        _isNavigatingFromDictionaryResultFlow.value = false
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "[Breadcrumb] Breadcrumbs cleared successfully")
    }

    /**
     * Called when leaving DictionaryResultScreen.
     * Only clears breadcrumbs if this is truly leaving (not internal navigation).
     */
    fun onLeavingDictionaryResultScreen() {
        // If we're not navigating internally, clear everything
        if (!_isNavigatingFromDictionaryResult) {
            clearBreadcrumbs()
        }
        // Reset the internal navigation flag
        _isNavigatingFromDictionaryResult = false
    }

    /**
     * Sets the flag indicating navigation is coming from DictionaryResultScreen
     */
    fun setNavigatingFromDictionaryResult(isFromDictionaryResult: Boolean) {
        _isNavigatingFromDictionaryResult = isFromDictionaryResult
    }

    /**
     * Sets the flag to true when internal navigation happens within DictionaryResultScreen
     */
    fun setInternalNavigation() {
        _isNavigatingFromDictionaryResult = true
    }

    /**
     * Enhanced breadcrumb update logic that respects navigation source
     */
    fun updateBreadcrumbs(entry: DictionaryEntry) {
        val current = _breadcrumbs.value
        
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "[Breadcrumb] updateBreadcrumbsEnhanced called for entry: '${entry.word}' (id: ${entry.id})")
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "[Breadcrumb] Current breadcrumbs: ${current.map { it.word }}")
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "[Breadcrumb] Pending reset: $_pendingBreadcrumbReset, Is from DictionaryResult: $_isNavigatingFromDictionaryResult")
        
        // If pending reset is true, start fresh regardless of navigation source
        if (_pendingBreadcrumbReset) {
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "[Breadcrumb] ACTION: Resetting breadcrumbs due to pending reset flag")
            _breadcrumbs.value = listOf(entry)
            _pendingBreadcrumbReset = false
            _isNavigatingFromDictionaryResult = true // Set flag for future internal navigations
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "[Breadcrumb] New breadcrumbs: ${_breadcrumbs.value.map { it.word }}")
            return
        }
        
        // If we're not navigating from DictionaryResultScreen, clear breadcrumbs and start fresh
        if (!_isNavigatingFromDictionaryResult) {
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "[Breadcrumb] ACTION: Clearing breadcrumbs - external navigation detected")
            _breadcrumbs.value = listOf(entry)
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "[Breadcrumb] New breadcrumbs: ${_breadcrumbs.value.map { it.word }}")
            // Don't set the flag to true here - let it be set when internal navigation happens
            return
        }
        
        // Normal breadcrumb logic: append or truncate
        val index = current.indexOfFirst { it.id == entry.id }
        if (index != -1) {
            // Truncate after this entry
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "[Breadcrumb] ACTION: Truncating breadcrumbs at index $index")
            _breadcrumbs.value = current.take(index + 1)
        } else {
            // Append
            @Suppress("HardCodedStringLiteral")
            Log.d("DictionaryViewModel", "[Breadcrumb] ACTION: Appending entry to breadcrumbs")
            _breadcrumbs.value = current + entry
        }
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "[Breadcrumb] Final breadcrumbs: ${_breadcrumbs.value.map { it.word }}")
    }

    data class DictionaryUiState(
        val isLoading: Boolean = false,
        val error: String? = null,
    )

    private val _etymologyData = MutableStateFlow<EtymologyData?>(null)
    val etymologyData: StateFlow<EtymologyData?> = _etymologyData.asStateFlow()

    private val _manifest = MutableStateFlow<eu.gaudian.translator.model.communication.ManifestResponse?>(null)
    val manifest: StateFlow<eu.gaudian.translator.model.communication.ManifestResponse?> = _manifest.asStateFlow()

    private val _downloadedDictionaries = MutableStateFlow<List<FileInfo>>(emptyList())
    val downloadedDictionaries = _downloadedDictionaries.asStateFlow()

    private val _orphanedFiles = MutableStateFlow<List<FileInfo>>(emptyList())
    val orphanedFiles = _orphanedFiles.asStateFlow()

    private val _downloadProgress = MutableStateFlow<Float?>(null)
    val downloadProgress = _downloadProgress.asStateFlow()

    private val _isDownloading = MutableStateFlow(false)
    val isDownloading = _isDownloading.asStateFlow()


    private val _errorMessage = MutableStateFlow<String?>(null)
    val errorMessage = _errorMessage.asStateFlow()

    init {
        viewModelScope.launch {
            dictionaryFileRepository.manifest.collect { _manifest.value = it }
        }
        viewModelScope.launch {
            dictionaryFileRepository.downloadedDictionaries.collect { _downloadedDictionaries.value = it }
        }
        viewModelScope.launch {
            dictionaryFileRepository.orphanedFiles.collect { _orphanedFiles.value = it }
        }
    }

    fun fetchEtymology(query: String, language: Language) {
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(isLoading = true, error = null)
            dictionaryService.getEtymology(query, language)
                .onSuccess {
                    _etymologyData.value = it
                    _uiState.value = _uiState.value.copy(isLoading = false)
                }
                .onFailure { exception ->
                    _uiState.value = _uiState.value.copy(
                        isLoading = false,
                        error = exception.message ?: getApplication<Application>().getString(R.string.text_failed_to_fetch_etymology)
                    )
                }
        }
    }

    /**
     * Fetches the manifest from the server.
     */
    fun fetchManifest() {
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(isLoading = true, error = null)
            try {
                dictionaryFileRepository.fetchManifest()
                _uiState.value = _uiState.value.copy(isLoading = false)
            } catch (e: Exception) {
                _uiState.value = _uiState.value.copy(isLoading = false)
                showErrorMessage(
                    getApplication<Application>().getString(
                        R.string.text_failed_to_fetch_manifest,
                     //   e.message Let's not include that part here, since it would show the server IP to the user
                    ))
            }
        }
    }

    /**
     * Downloads a dictionary file.
     */
    fun downloadDictionary(fileInfo: FileInfo) {
        viewModelScope.launch {
            _isDownloading.value = true
            try {
                val success = dictionaryFileRepository.downloadDictionary(fileInfo) { progress ->
                    _downloadProgress.value = progress
                }
                if (success) {
                    _uiState.value = _uiState.value.copy(isLoading = false, error = null)
                    showSuccessToast(getApplication<Application>().getString(R.string.text_dictionary_downloaded_successfully))
                } else {
                    showErrorMessage(
                        getApplication<Application>().getString(
                            R.string.text_failed_to_download_dictionary,
                            fileInfo.name
                        ))
                }
            } catch (e: Exception) {
                showErrorMessage(
                    getApplication<Application>().getString(
                        R.string.text_error_downloading_dictionary,
                        e.message
                    ))
            } finally {
                _downloadProgress.value = null
                _isDownloading.value = false
            }
        }
    }

    /**
     * Deletes a downloaded dictionary.
     */
    fun deleteDictionary(fileInfo: FileInfo) {
        viewModelScope.launch {
            try {
                val success = dictionaryFileRepository.deleteDictionary(fileInfo)
                if (success) {
                    showSuccessToast(getApplication<Application>().getString(R.string.text_dictionary_deleted_successfully))
                } else {
                    showErrorMessage(
                        getApplication<Application>().getString(
                            R.string.text_failed_to_delete_dictionary,
                            fileInfo.name
                        ))
                }
            } catch (e: Exception) {
                showErrorMessage(
                    getApplication<Application>().getString(
                        R.string.text_error_deleting_dictionary,
                        e.message
                    ))
            }
        }
    }

    /**
     * Checks if a newer version is available.
     */
    fun isNewerVersionAvailable(fileInfo: FileInfo): Boolean {
        return dictionaryFileRepository.isNewerVersionAvailable(fileInfo)
    }

    /**
     * Gets the local version of a dictionary.
     */
    fun getLocalVersion(fileId: String): String {
        return dictionaryFileRepository.getLocalVersion(fileId)
    }

    /**
     * Gets the size of a downloaded dictionary.
     */
    fun getDictionarySize(fileInfo: FileInfo): Long {
        return dictionaryFileRepository.getDictionarySize(fileInfo)
    }

    /**
     * Deletes all downloaded dictionaries.
     */
    fun deleteAllDictionaries() {
        viewModelScope.launch {
            try {
                val success = dictionaryFileRepository.deleteAllDictionaries()
                if (success) {
                    showSuccessToast(getApplication<Application>().getString(R.string.text_all_dictionaries_deleted_successfully))
                } else {
                    showErrorMessage(getApplication<Application>().getString(R.string.text_failed_to_delete_some_dictionaries))
                }
            } catch (e: Exception) {
                showErrorMessage(
                    getApplication<Application>().getString(
                        R.string.text_error_deleting_dictionaries,
                        e.message
                    ))
            }
        }
    }

    /**
     * Deletes an orphaned file.
     */
    fun deleteOrphanedFile(fileInfo: FileInfo) {
        viewModelScope.launch {
            try {
                val success = dictionaryFileRepository.deleteOrphanedFile(fileInfo)
                if (success) {
                    showSuccessToast(getApplication<Application>().getString(R.string.text_orphaned_file_deleted_successfully))
                } else {
                    showErrorMessage(
                        getApplication<Application>().getString(
                            R.string.text_failed_to_delete_orphaned_file,
                            fileInfo.name
                        ))
                }
            } catch (e: Exception) {
                showErrorMessage(
                    getApplication<Application>().getString(
                        R.string.text_error_deleting_orphaned_file,
                        e.message
                    ))
            }
        }
    }

    /**
     * Searches for local dictionary entries for a word in a specific language.
     */
    fun getLocalDictionaryEntries(word: String, langCode: String): List<DictionaryWordEntry> {
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "Fetching local dictionary entries for word '$word' in language '$langCode'")
        val result = dictionaryLookupRepository.searchWord(word.trim(), langCode)
        @Suppress("HardCodedStringLiteral")
        Log.d("DictionaryViewModel", "Retrieved ${result.size} local dictionary entries")
        Log.d("DictionaryViewModel", "Result: $result")
        return result
    }

    /**
     * Convenience wrapper: parses the first local dictionary entry for a word into
     * a display-agnostic [LocalDictionaryWordInfo]. Returns null if no entry exists
     * or JSON cannot be parsed.
     */
    fun getLocalDictionaryWordInfo(word: String, langCode: String): LocalDictionaryWordInfo? {
        val entries = getLocalDictionaryEntries(word, langCode)
        val first = entries.firstOrNull() ?: return null
        return LocalDictionaryAccess.parseWordInfo(first)
    }

    /**
     * Retrieves all translations for a word in the given language from the local
     * dictionary. Returns an empty list if no local entry is available.
     */
    fun getLocalTranslations(word: String, langCode: String): List<LocalTranslationInfo> {
        return getLocalDictionaryWordInfo(word, langCode)?.translations.orEmpty()
    }

    /**
     * Retrieves all related words (synonyms, hyponyms, etc.) across all
     * relation types for a given word and language.
     */
    fun getLocalRelatedWords(word: String, langCode: String): List<LocalRelatedWordInfo> {
        return getLocalDictionaryWordInfo(word, langCode)?.relatedWords.orEmpty()
    }

    /**
     * Retrieves only synonyms for a word from the local dictionary.
     */
    fun getLocalSynonyms(word: String, langCode: String): List<LocalRelatedWordInfo> {
        return getLocalDictionaryWordInfo(word, langCode)?.synonyms.orEmpty()
    }

    /**
     * Retrieves only hyponyms for a word from the local dictionary.
     */
    fun getLocalHyponyms(word: String, langCode: String): List<LocalRelatedWordInfo> {
        return getLocalDictionaryWordInfo(word, langCode)?.hyponyms.orEmpty()
    }

    /**
     * Retrieves alternative forms (e.g. alternative spellings) for a word from
     * the local dictionary.
     */
    fun getLocalAlternativeForms(word: String, langCode: String): List<LocalAlternativeForm> {
        return getLocalDictionaryWordInfo(word, langCode)?.alternativeForms.orEmpty()
    }

    /**
     * Retrieves word suggestions based on prefix for a specific language.
     */
    fun getSuggestions(prefix: String, langCode: String, limit: Int): List<String> {
        return dictionaryLookupRepository.getSuggestions(prefix, langCode, limit)
    }

    /**
     * Checks if a dictionary is downloaded for the language.
     */
    fun hasDictionaryForLanguage(langCode: String): Boolean {
        Log.d("DictionaryViewModel", "Checking if dictionary exists for language: $langCode")
        return dictionaryLookupRepository.hasDictionaryForLanguage(langCode)
    }

    /**
     * Checks if a specific word exists in the dictionary for the given language code.
     * This performs an actual lookup to verify the word is available, not just that
     * the dictionary file exists.
     */
    fun hasWordInDictionary(word: String, langCode: String): Boolean {
        Log.d("DictionaryViewModel", "Checking word availability: word='$word', lang='$langCode'")
        val result = dictionaryLookupRepository.hasWordInDictionary(word, langCode)
        Log.d("DictionaryViewModel", "Word availability result: word='$word', lang='$langCode', available=$result")
        return result
    }

    /**
     * Developer-only helper: cycles through local dictionary entries for the given language
     * and navigates to each one in sequence. Intended for testing dictionary rendering.
     */
    @Suppress("HardCodedStringLiteral")
    fun developerCycleLocalDictionaryEntries(language: Language, maxEntriesToDisplay: Int = 200000, delayMs: Long = 2L) {
        viewModelScope.launch {
            val langCode = language.code.lowercase()
            Log.d("DictionaryViewModel", "[DEV] Starting cycle for local dictionary entries in '$langCode'")

            // Fetch a broad list of words from the dictionary (developer-focused; may be large)
            val allWords: List<String> = withContext(Dispatchers.IO) {
                dictionaryLookupRepository.getAllWords(langCode, limit = maxEntriesToDisplay * 5)
            }

            var shownCount = 0
            for (word in allWords) {
                if (shownCount >= maxEntriesToDisplay) break

                // Look up local entries for the word
                val localEntries: List<DictionaryWordEntry> = withContext(Dispatchers.IO) {
                    getLocalDictionaryEntries(word, langCode)
                }

                if (localEntries.isNotEmpty()) {
                    val parts = localEntries.map { entry: DictionaryWordEntry ->
                        EntryPart(
                            title = getApplication<Application>().getString(R.string.label_definitions),
                            content = JsonPrimitive(entry.json)
                        )
                    }

                    val entry = DictionaryEntry(
                        id = idCounter.incrementAndGet(),
                        word = word,
                        definition = parts,
                        languageCode = language.nameResId,
                        languageName = language.name
                    )

                    // Directly expose the current entry for the developer UI to render
                    _developerCurrentEntry.value = entry
                    shownCount++
                    delay(delayMs)
                }
            }

            Log.d("DictionaryViewModel", "[DEV] Finished cycling dictionary entries. Shown=$shownCount")
        }
    }

    /**
     * Shows an error toast message.
     */
    private fun showErrorMessage(message: String) {
        _errorMessage.value = message
        viewModelScope.launch {
            StatusMessageService.trigger(
                eu.gaudian.translator.utils.StatusAction.ShowMessage(
                    message,
                    MessageDisplayType.ERROR,
                    5
                )
            )
        }
    }

    /**
     * Shows a success toast message.
     */
    private fun showSuccessToast(message: String) {
        viewModelScope.launch {
            StatusMessageService.trigger(
                eu.gaudian.translator.utils.StatusAction.ShowMessage(
                    message,
                    MessageDisplayType.SUCCESS,
                    3
                )
            )
        }
    }

    /**
     * Gets structured dictionary data for a word entry using the new JSON service.
     * This provides clean access to parsed JSON data without UI coupling.
     */
    suspend fun getStructuredDictionaryData(entry: DictionaryWordEntry) = dictionaryJsonService.parseEntry(entry)
    
    /**
     * Gets phonetics data for a word entry.
     */
    suspend fun getPhoneticsData(entry: DictionaryWordEntry) = dictionaryJsonService.getPhonetics(entry)
    
    /**
     * Gets translations data for a word entry.
     */
    suspend fun getTranslationsData(entry: DictionaryWordEntry) = dictionaryJsonService.getTranslations(entry)
    
    /**
     * Gets etymology data for a word entry.
     */
    suspend fun getEtymologyData(entry: DictionaryWordEntry) = dictionaryJsonService.getEtymology(entry)
    
    /**
     * Gets relations data (synonyms, hyponyms, etc.) for a word entry.
     */
    suspend fun getRelationsData(entry: DictionaryWordEntry) = dictionaryJsonService.getAllRelatedWords(entry)
    
    /**
     * Gets senses/definitions data for a word entry.
     */
    suspend fun getSensesData(entry: DictionaryWordEntry) = dictionaryJsonService.getSenses(entry)
    
    /**
     * Gets inflection data for a word entry.
     */
    suspend fun getInflectionsData(entry: DictionaryWordEntry) = dictionaryJsonService.getInflections(entry)
    
    /**
     * Clears the JSON service cache. Useful for testing.
     */
    fun clearJsonServiceCache() = dictionaryJsonService.clearCache()

    // --- EntryData and State Management ---

    /**
     * Data class representing entry data with language information.
     * Used to pass entry and language info from ViewModel to UI.
     */
    data class EntryData(
        val entry: DictionaryEntry,
        val language: Language?
    )

    /**
     * Updates the current entry data with language info and TTS availability.
     * Called from UI layer when entry changes.
     */
    fun updateCurrentEntryData(entry: DictionaryEntry, language: Language?, isTtsAvailable: Boolean) {
        _currentEntryData.value = EntryData(entry, language)
        _isTtsAvailable.value = isTtsAvailable
    }

    /**
     * Loads local dictionary entries for a word and updates the StateFlow.
     * Should be called when an entry is displayed to fetch local data.
     */
    fun loadLocalEntriesForEntry(entry: DictionaryEntry, languageCode: String) {
        viewModelScope.launch {
            val entries = getLocalDictionaryEntries(entry.word, languageCode.lowercase())
            _localEntries.value = entries
        }
    }

    /**
     * Clears the local entries StateFlow.
     */
    fun clearLocalEntries() {
        _localEntries.value = emptyList()
    }

    /**
     * Updates the local dictionary availability for the given language.
     */
    fun updateLocalDictAvailability(langCode: String) {
        Log.d("DictionaryViewModel", "Local dictionary availability updated: $langCode")
        _isLocalDictAvailable.value = hasDictionaryForLanguage(langCode.lowercase())
    }

    /**
     * Fetches suggestions with debounce support and updates the StateFlow.
     */
    fun fetchSuggestions(query: String, langCode: String, limit: Int = 5, debounceMs: Long = 100) {
        viewModelScope.launch {
            delay(debounceMs)
            if (query.length >= 3) {
                _suggestions.value = getSuggestions(query, langCode.lowercase(), limit)
            } else {
                _suggestions.value = emptyList()
            }
        }
    }

    /**
     * Clears suggestions.
     */
    fun clearSuggestions() {
        _suggestions.value = emptyList()
    }

    /**
     * Creates a list of DictionaryUiItem for the manifest files with pre-calculated values.
     */
    fun getDictionaryUiItems(files: List<FileInfo>, downloadedDictionaries: List<FileInfo>): List<DictionaryUiItem> {
        return files.map { fileInfo ->
            val isDownloaded = downloadedDictionaries.any { it.id == fileInfo.id }
            val hasUpdate = isDownloaded && isNewerVersionAvailable(fileInfo)
            val size = if (isDownloaded) getDictionarySize(fileInfo) else fileInfo.assets.sumOf { it.sizeBytes }
            DictionaryUiItem(fileInfo, isDownloaded, hasUpdate, size)
        }
    }

    // --- Structured Data State Management ---

    /**
     * Internal cache for structured data results - uses mutableMapOf with explicit type
     */
    private val structuredDataCache = mutableMapOf<String, MutableStateFlow<DictionaryEntryData?>>()

    /**
     * Gets or creates a StateFlow for structured dictionary data.
     * This allows the UI to observe data changes for specific entries.
     * Loading state is indicated by null value in the flow.
     */
    fun getStructuredDictionaryDataState(entry: DictionaryWordEntry): StateFlow<DictionaryEntryData?> {
        val key = entry.word + "_" + entry.langCode
        val existing = structuredDataCache[key]
        if (existing != null) return existing
        
        val newFlow: MutableStateFlow<DictionaryEntryData?> = MutableStateFlow(null)
        structuredDataCache[key] = newFlow
        
        viewModelScope.launch {
            val result = dictionaryJsonService.parseEntry(entry)
            newFlow.value = result
        }
        
        return newFlow
    }

    /**
     * Gets or creates a loading StateFlow for structured data.
     * Returns true if data is still loading (null).
     */
    fun getStructuredDictionaryDataLoading(entry: DictionaryWordEntry): StateFlow<Boolean> {
        entry.word + "_" + entry.langCode
        // Create a derived flow that emits true when data is null
        val dataFlow = getStructuredDictionaryDataState(entry)
        val loadingFlow = MutableStateFlow(true)
        viewModelScope.launch {
            dataFlow.collect { data ->
                loadingFlow.value = (data == null)
            }
        }
        return loadingFlow
    }

    /**
     * Clears all caches for structured data.
     */
    fun clearStructuredDataCache() {
        structuredDataCache.clear()
    }
}
