package com.swoosh.microblog.ui.feed

import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.swoosh.microblog.data.AccountManager
import com.swoosh.microblog.data.CredentialsManager
import com.swoosh.microblog.data.FeedPreferences
import com.swoosh.microblog.data.HashtagParser
import com.swoosh.microblog.data.TagsPreferences
import com.swoosh.microblog.data.api.ApiClient
import com.swoosh.microblog.data.db.Converters
import com.swoosh.microblog.data.model.*
import com.swoosh.microblog.data.repository.PostRepository
import com.swoosh.microblog.data.repository.TagRepository
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.time.Instant
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import javax.net.ssl.SSLException

data class SnackbarEvent(
    val message: String,
    val actionLabel: String? = null,
    val onAction: (() -> Unit)? = null
)

class FeedViewModel(application: Application) : AndroidViewModel(application) {

    private val gson = Gson()
    private val accountManager = AccountManager(application)
    private var repository = PostRepository(application)
    private var tagRepository = TagRepository(application)
    private val feedPreferences = FeedPreferences(application)
    private val tagsPreferences = TagsPreferences(application)
    private val searchHistoryManager = SearchHistoryManager(application)

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

    private val _snackbarEvent = MutableSharedFlow<SnackbarEvent>(extraBufferCapacity = 1)
    val snackbarEvent: SharedFlow<SnackbarEvent> = _snackbarEvent.asSharedFlow()

    private val _activeFilter = MutableStateFlow(feedPreferences.filter)
    val activeFilter: StateFlow<PostFilter> = _activeFilter.asStateFlow()

    private val _sortOrder = MutableStateFlow(feedPreferences.sortOrder)
    val sortOrder: StateFlow<SortOrder> = _sortOrder.asStateFlow()

    private val _searchQuery = MutableStateFlow("")
    val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()

    private val _searchResults = MutableStateFlow<List<FeedPost>>(emptyList())
    val searchResults: StateFlow<List<FeedPost>> = _searchResults.asStateFlow()

    private val _isSearchActive = MutableStateFlow(false)
    val isSearchActive: StateFlow<Boolean> = _isSearchActive.asStateFlow()

    private val _isSearching = MutableStateFlow(false)
    val isSearching: StateFlow<Boolean> = _isSearching.asStateFlow()

    private val _searchResultCount = MutableStateFlow(0)
    val searchResultCount: StateFlow<Int> = _searchResultCount.asStateFlow()

    private val _recentSearches = MutableStateFlow<List<String>>(emptyList())
    val recentSearches: StateFlow<List<String>> = _recentSearches.asStateFlow()

    private val _popularTags = MutableStateFlow<List<GhostTagFull>>(emptyList())
    val popularTags: StateFlow<List<GhostTagFull>> = _popularTags.asStateFlow()

    private val _tagsEnabled = MutableStateFlow(tagsPreferences.isTagsEnabled())
    val tagsEnabled: StateFlow<Boolean> = _tagsEnabled.asStateFlow()

    private val _accounts = MutableStateFlow<List<GhostAccount>>(emptyList())
    val accounts: StateFlow<List<GhostAccount>> = _accounts.asStateFlow()

    private val _activeAccount = MutableStateFlow<GhostAccount?>(null)
    val activeAccount: StateFlow<GhostAccount?> = _activeAccount.asStateFlow()

    private var currentPage = 1
    private var hasMorePages = true
    private var remotePosts = listOf<FeedPost>()
    private var localPostsJob: Job? = null
    private var searchJob: Job? = null

    init {
        refreshAccountsList()
        observeLocalPosts()
        observeSearchQuery()
        if (CredentialsManager(getApplication()).isConfigured) {
            refresh()
        }
        _recentSearches.value = searchHistoryManager.getRecentSearches()
    }

    @OptIn(FlowPreview::class)
    private fun observeSearchQuery() {
        viewModelScope.launch {
            _searchQuery
                .debounce(300)
                .distinctUntilChanged()
                .collect { query ->
                    if (query.isBlank()) {
                        _searchResults.value = emptyList()
                        _searchResultCount.value = 0
                        _isSearching.value = false
                    } else {
                        performSearch(query)
                    }
                }
        }
    }

    fun refreshAccountsList() {
        _accounts.value = accountManager.getAccounts()
        _activeAccount.value = accountManager.getActiveAccount()
    }

    /** Extract avatar from post authors and save to account if missing */
    private fun tryUpdateAvatarFromPosts(posts: List<GhostPost>) {
        val active = _activeAccount.value ?: return
        if (active.avatarUrl != null) return
        val avatarUrl = posts.firstNotNullOfOrNull { post ->
            post.authors?.firstOrNull()?.profile_image
        } ?: return
        accountManager.updateAccount(id = active.id, avatarUrl = avatarUrl)
        _activeAccount.value = accountManager.getActiveAccount()
        _accounts.value = accountManager.getAccounts()
    }

    private fun observeLocalPosts() {
        localPostsJob?.cancel()
        localPostsJob = viewModelScope.launch {
            val filter = _activeFilter.value
            val sort = _sortOrder.value
            repository.getLocalPosts(filter, sort).collect { localPosts ->
                val localFeedPosts = localPosts
                    .filter { it.queueStatus != QueueStatus.NONE || it.status == PostStatus.DRAFT }
                    .map { it.toFeedPost() }
                mergePosts(localFeedPosts)
            }
        }
    }

    fun setFilter(filter: PostFilter) {
        if (filter == _activeFilter.value) return
        _activeFilter.value = filter
        feedPreferences.filter = filter
        observeLocalPosts()
        refresh()
    }

    fun setSortOrder(order: SortOrder) {
        if (order == _sortOrder.value) return
        _sortOrder.value = order
        feedPreferences.sortOrder = order
        observeLocalPosts()
        refresh()
    }

    fun onSearchQueryChange(query: String) {
        _searchQuery.value = query
    }

    fun activateSearch() {
        _isSearchActive.value = true
        _recentSearches.value = searchHistoryManager.getRecentSearches()
    }

    fun deactivateSearch() {
        _isSearchActive.value = false
        _searchQuery.value = ""
        _searchResults.value = emptyList()
        _searchResultCount.value = 0
        _isSearching.value = false
    }

    fun clearSearchQuery() {
        _searchQuery.value = ""
        _searchResults.value = emptyList()
        _searchResultCount.value = 0
        _isSearching.value = false
    }

    fun onRecentSearchTap(query: String) {
        _searchQuery.value = query
    }

    fun removeRecentSearch(query: String) {
        searchHistoryManager.removeSearch(query)
        _recentSearches.value = searchHistoryManager.getRecentSearches()
    }

    private fun performSearch(query: String) {
        searchJob?.cancel()
        searchJob = viewModelScope.launch {
            _isSearching.value = true

            try {
                // Search local posts
                val localResults = repository.searchLocalPosts(query)
                    .map { it.toFeedPost() }

                // Search remote posts
                val remoteResults = try {
                    repository.searchRemotePosts(query)
                        .getOrDefault(emptyList())
                        .map { it.toFeedPost() }
                } catch (e: Exception) {
                    emptyList()
                }

                // Deduplicate: prefer remote version when ghostId matches
                val deduplicatedResults = deduplicateResults(localResults, remoteResults)

                _searchResults.value = deduplicatedResults
                _searchResultCount.value = deduplicatedResults.size
                _isSearching.value = false

                // Save to recent searches
                searchHistoryManager.addSearch(query)
                _recentSearches.value = searchHistoryManager.getRecentSearches()
            } catch (e: Exception) {
                _isSearching.value = false
                _searchResults.value = emptyList()
                _searchResultCount.value = 0
            }
        }
    }

    fun switchAccount(accountId: String) {
        if (accountId == _activeAccount.value?.id) return

        viewModelScope.launch {
            _uiState.update { it.copy(isSwitchingAccount = true) }

            accountManager.setActiveAccount(accountId)
            ApiClient.reset()

            // Re-create repositories to pick up new account
            repository = PostRepository(getApplication())
            tagRepository = TagRepository(getApplication())

            refreshAccountsList()

            // Clear current state
            remotePosts = emptyList()
            currentPage = 1
            hasMorePages = true
            _uiState.update {
                it.copy(
                    posts = emptyList(),
                    error = null,
                    isConnectionError = false,
                    isSwitchingAccount = false
                )
            }

            // Re-observe local posts for new account
            observeLocalPosts()

            // Refresh from new account's API
            refresh()
        }
    }

    fun removeAccount(accountId: String) {
        val wasActive = _activeAccount.value?.id == accountId
        accountManager.removeAccount(accountId)
        refreshAccountsList()

        if (wasActive) {
            val nextAccount = accountManager.getActiveAccount()
            if (nextAccount != null) {
                switchAccount(nextAccount.id)
            } else {
                // No accounts left
                _uiState.update {
                    it.copy(posts = emptyList(), error = null, isConnectionError = false)
                }
            }
        }
    }

    fun renameAccount(accountId: String, newName: String) {
        accountManager.updateAccount(accountId, name = newName)
        refreshAccountsList()
    }

    fun refresh() {
        viewModelScope.launch {
            _uiState.update { it.copy(isRefreshing = true, error = null, isConnectionError = false) }
            currentPage = 1
            hasMorePages = true

            val filter = _activeFilter.value
            val sort = _sortOrder.value
            val tagFilter = _uiState.value.activeTagFilter

            // Fetch popular tags in parallel (only when tags feature is enabled)
            val tagsOn = tagsPreferences.isTagsEnabled()
            _tagsEnabled.value = tagsOn
            if (tagsOn) {
                launch {
                    tagRepository.fetchTags().fold(
                        onSuccess = { tags ->
                            _popularTags.value = tags
                                .sortedByDescending { it.count?.posts ?: 0 }
                                .take(10)
                        },
                        onFailure = { /* silently ignore tag fetch failures */ }
                    )
                }
            } else {
                _popularTags.value = emptyList()
                _uiState.update { it.copy(activeTagFilter = null) }
            }

            repository.fetchPosts(page = 1, filter = filter, sortOrder = sort, tagFilter = tagFilter).fold(
                onSuccess = { response ->
                    remotePosts = response.posts.map { it.toFeedPost() }
                    hasMorePages = response.meta?.pagination?.next != null
                    tryUpdateAvatarFromPosts(response.posts)
                    mergePosts()
                    _uiState.update { it.copy(isRefreshing = false) }
                },
                onFailure = { e ->
                    val (message, isConn) = classifyError(e)
                    _uiState.update { it.copy(isRefreshing = false, error = message, isConnectionError = isConn) }
                }
            )
        }
    }

    fun refreshTagsEnabled() {
        val enabled = tagsPreferences.isTagsEnabled()
        _tagsEnabled.value = enabled
        if (!enabled) {
            _popularTags.value = emptyList()
            _uiState.update { it.copy(activeTagFilter = null) }
        }
    }

    fun filterByTag(tag: String) {
        _uiState.update { it.copy(activeTagFilter = tag) }
        refresh()
    }

    fun clearTagFilter() {
        _uiState.update { it.copy(activeTagFilter = null) }
        refresh()
    }

    private fun classifyError(e: Throwable): Pair<String, Boolean> {
        val cause = e.cause ?: e
        return when {
            cause is UnknownHostException || cause is ConnectException ||
            cause is SocketTimeoutException || cause is SSLException ||
            e is UnknownHostException || e is ConnectException ||
            e is SocketTimeoutException || e is SSLException ->
                "Could not connect to your Ghost blog. Check your internet connection and try again." to true

            e.message?.contains("401") == true || e.message?.contains("403") == true ->
                "Authentication failed. Your API key may be invalid. Check Settings \u2192 Integrations in your Ghost admin." to true

            e.message?.contains("404") == true ->
                "Ghost API not found at this URL. Verify your blog address in Settings." to true

            else -> (e.message ?: "An unknown error occurred") to false
        }
    }

    fun loadMore() {
        if (!hasMorePages || _uiState.value.isLoadingMore) return

        viewModelScope.launch {
            _uiState.update { it.copy(isLoadingMore = true) }
            currentPage++

            val filter = _activeFilter.value
            val sort = _sortOrder.value
            val tagFilter = _uiState.value.activeTagFilter

            repository.fetchPosts(page = currentPage, filter = filter, sortOrder = sort, tagFilter = tagFilter).fold(
                onSuccess = { response ->
                    val newPosts = response.posts.map { it.toFeedPost() }
                    remotePosts = remotePosts + newPosts
                    hasMorePages = response.meta?.pagination?.next != null
                    mergePosts()
                    _uiState.update { it.copy(isLoadingMore = false) }
                },
                onFailure = {
                    currentPage--
                    _uiState.update { it.copy(isLoadingMore = false) }
                }
            )
        }
    }

    fun deletePost(post: FeedPost) {
        viewModelScope.launch {
            if (post.isLocal && post.localId != null) {
                repository.deleteLocalPost(post.localId)
            }
            if (post.ghostId != null) {
                repository.deletePost(post.ghostId).fold(
                    onSuccess = { refresh() },
                    onFailure = { e ->
                        _uiState.update { it.copy(error = "Delete failed: ${e.message}") }
                    }
                )
            }
        }
    }

    /**
     * Delete a post with undo support.
     * For local posts: deletes from Room and offers undo (re-insert).
     * For remote posts: deletes via API (no undo since Ghost API has no undelete).
     */
    fun deletePostWithUndo(post: FeedPost) {
        viewModelScope.launch {
            if (post.isLocal && post.localId != null) {
                // For local posts, we can support undo by saving the data before deleting
                val localPost = repository.getLocalPostById(post.localId)
                repository.deleteLocalPost(post.localId)

                if (localPost != null) {
                    _snackbarEvent.emit(
                        SnackbarEvent(
                            message = "Post deleted",
                            actionLabel = "Undo",
                            onAction = {
                                viewModelScope.launch {
                                    repository.saveLocalPost(localPost)
                                }
                            }
                        )
                    )
                } else {
                    _snackbarEvent.emit(SnackbarEvent(message = "Post deleted"))
                }
            } else if (post.ghostId != null) {
                // For remote posts, delete via API (no undo available)
                repository.deletePost(post.ghostId).fold(
                    onSuccess = {
                        // Remove from local remote posts list immediately
                        remotePosts = remotePosts.filter { it.ghostId != post.ghostId }
                        mergePosts()
                        _snackbarEvent.emit(SnackbarEvent(message = "Post deleted"))
                    },
                    onFailure = { e ->
                        _uiState.update { it.copy(error = "Delete failed: ${e.message}") }
                    }
                )
            }
        }
    }

    fun cancelQueuedPost(post: FeedPost) {
        viewModelScope.launch {
            post.localId?.let { repository.deleteLocalPost(it) }
        }
    }

    fun toggleFeatured(post: FeedPost) {
        viewModelScope.launch {
            val newFeatured = !post.featured
            val ghostId = post.ghostId
            val updatedAt = post.updatedAt

            if (ghostId != null && updatedAt != null) {
                // Optimistically update local state
                remotePosts = remotePosts.map {
                    if (it.ghostId == ghostId) it.copy(featured = newFeatured) else it
                }
                mergePosts()

                repository.toggleFeatured(ghostId, newFeatured, updatedAt).fold(
                    onSuccess = { updatedGhostPost ->
                        // Update with server response
                        remotePosts = remotePosts.map {
                            if (it.ghostId == ghostId) it.copy(
                                featured = updatedGhostPost.featured ?: false,
                                updatedAt = updatedGhostPost.updated_at
                            ) else it
                        }
                        mergePosts()
                        val message = if (newFeatured) "Post pinned" else "Post unpinned"
                        _uiState.update { it.copy(snackbarMessage = message) }
                    },
                    onFailure = { e ->
                        // Revert optimistic update
                        remotePosts = remotePosts.map {
                            if (it.ghostId == ghostId) it.copy(featured = !newFeatured) else it
                        }
                        mergePosts()
                        _uiState.update { it.copy(error = "Failed to ${if (newFeatured) "pin" else "unpin"} post: ${e.message}") }
                    }
                )
            } else if (post.isLocal && post.localId != null) {
                // Local-only post: just update the local DB
                repository.updateLocalFeatured(post.localId, newFeatured)
                val message = if (newFeatured) "Post pinned" else "Post unpinned"
                _uiState.update { it.copy(snackbarMessage = message) }
            }
        }
    }

    fun clearSnackbar() {
        _uiState.update { it.copy(snackbarMessage = null) }
    }

    fun clearError() {
        _uiState.update { it.copy(error = null) }
    }

    private fun mergePosts(queuedPosts: List<FeedPost>? = null) {
        val tagFilter = _uiState.value.activeTagFilter
        val queued = queuedPosts ?: _uiState.value.posts.filter { it.isLocal }
        // Filter local posts by tag if a tag filter is active
        val filteredQueued = if (tagFilter != null) {
            queued.filter { post ->
                post.tags.any { it.equals(tagFilter, ignoreCase = true) }
            }
        } else queued
        val allPosts = filteredQueued + remotePosts
        // Sort: featured/pinned posts first, then chronological
        val sorted = allPosts.sortedWith(
            compareByDescending<FeedPost> { it.featured }
                .thenByDescending { it.isLocal } // local queued posts after pinned but before regular
                .thenByDescending { it.publishedAt ?: it.createdAt ?: "" }
        )
        _uiState.update { it.copy(posts = sorted) }
    }

    /**
     * Parsed mobiledoc card data. Single-parse result for all card types.
     */
    private data class MobiledocCards(
        val imageUrls: List<String> = emptyList(),
        val videoUrl: String? = null,
        val audioUrl: String? = null,
        val fileUrl: String? = null,
        val fileName: String? = null
    )

    /**
     * Parses mobiledoc JSON once and extracts all card data (images, video, audio, file).
     */
    private fun parseMobiledocCards(mobiledoc: String?): MobiledocCards {
        if (mobiledoc == null) return MobiledocCards()
        return try {
            val json = com.google.gson.JsonParser.parseString(mobiledoc).asJsonObject
            val cards = json.getAsJsonArray("cards") ?: return MobiledocCards()
            val imageUrls = mutableListOf<String>()
            var videoUrl: String? = null
            var audioUrl: String? = null
            var fileUrl: String? = null
            var fileName: String? = null
            for (card in cards) {
                val cardArray = card.asJsonArray
                if (cardArray.size() < 2) continue
                val cardData = cardArray[1].asJsonObject
                when (cardArray[0].asString) {
                    "image" -> cardData.get("src")?.asString?.let { imageUrls.add(it) }
                    "video" -> if (videoUrl == null) videoUrl = cardData.get("src")?.asString
                    "audio" -> if (audioUrl == null) audioUrl = cardData.get("src")?.asString
                    "file" -> if (fileUrl == null) {
                        fileUrl = cardData.get("src")?.asString
                        fileName = cardData.get("fileName")?.asString ?: "file"
                    }
                }
            }
            MobiledocCards(imageUrls, videoUrl, audioUrl, fileUrl, fileName)
        } catch (e: Exception) {
            MobiledocCards()
        }
    }

    private val imgSrcRegex = Regex("""<img[^>]+src\s*=\s*["']([^"']+)["']""", RegexOption.IGNORE_CASE)

    private fun GhostPost.toFeedPost(): FeedPost {
        val mobiledocCards = parseMobiledocCards(mobiledoc)
        // Use feature_image as primary, then add mobiledoc images, then HTML images (avoiding duplicates)
        val allImages = mutableListOf<String>()
        if (feature_image != null) {
            allImages.add(feature_image)
        }
        for (url in mobiledocCards.imageUrls) {
            if (url !in allImages) {
                allImages.add(url)
            }
        }
        // Fallback: extract images from HTML content
        if (allImages.isEmpty() && html != null) {
            imgSrcRegex.findAll(html).forEach { match ->
                val url = match.groupValues[1]
                if (url !in allImages) {
                    allImages.add(url)
                }
            }
        }
        val isEmailOnly = status == "sent" || email_only == true
        return FeedPost(
            ghostId = id,
            slug = slug,
            url = url,
            title = title ?: "",
            textContent = plaintext ?: html?.replace(Regex("<[^>]*>"), "") ?: "",
            htmlContent = html,
            imageUrl = allImages.firstOrNull(),
            imageAlt = feature_image_alt,
            imageUrls = allImages,
            videoUrl = mobiledocCards.videoUrl,
            audioUrl = mobiledocCards.audioUrl,
            linkUrl = null,
            linkTitle = null,
            linkDescription = null,
            linkImageUrl = null,
            tags = tags?.map { it.name } ?: emptyList(),
            status = status ?: "draft",
            featured = featured ?: false,
            publishedAt = published_at,
            createdAt = created_at,
            updatedAt = updated_at,
            isLocal = false,
            fileUrl = mobiledocCards.fileUrl,
            fileName = mobiledocCards.fileName,
            emailOnly = isEmailOnly
        )
    }

    private fun LocalPost.toFeedPost(): FeedPost {
        val tagNames: List<String> = try {
            gson.fromJson(tags, object : TypeToken<List<String>>() {}.type) ?: emptyList()
        } catch (e: Exception) {
            emptyList()
        }
        val uploadedUrls = Converters.jsonToStringList(uploadedImageUrls)
        val localUris = Converters.jsonToStringList(imageUris)
        val allImageUrls = when {
            uploadedUrls.isNotEmpty() -> uploadedUrls
            localUris.isNotEmpty() -> localUris
            else -> if (uploadedImageUrl != null) listOf(uploadedImageUrl)
                    else if (imageUri != null) listOf(imageUri)
                    else emptyList()
        }
        return FeedPost(
            localId = localId,
            ghostId = ghostId,
            title = title,
            textContent = content,
            htmlContent = htmlContent,
            imageUrl = allImageUrls.firstOrNull(),
            imageAlt = imageAlt,
            imageUrls = allImageUrls,
            videoUrl = uploadedVideoUrl ?: videoUri,
            audioUrl = uploadedAudioUrl ?: audioUri,
            linkUrl = linkUrl,
            linkTitle = linkTitle,
            linkDescription = linkDescription,
            linkImageUrl = linkImageUrl,
            tags = tagNames,
            status = status.name.lowercase(),
            featured = featured,
            publishedAt = null,
            createdAt = null,
            updatedAt = null,
            isLocal = true,
            queueStatus = queueStatus,
            fileUrl = uploadedFileUrl ?: fileUri,
            fileName = fileName,
            emailOnly = emailOnly
        )
    }

    companion object {
        /**
         * Deduplicates local and remote search results.
         * When a local post has a ghostId matching a remote post, the remote version is preferred.
         * Local-only posts (no ghostId) are always included.
         */
        fun deduplicateResults(
            localResults: List<FeedPost>,
            remoteResults: List<FeedPost>
        ): List<FeedPost> {
            val remoteGhostIds = remoteResults.mapNotNull { it.ghostId }.toSet()

            // Include local posts that don't have a matching remote post
            val uniqueLocalPosts = localResults.filter { localPost ->
                localPost.ghostId == null || localPost.ghostId !in remoteGhostIds
            }

            return (uniqueLocalPosts + remoteResults).sortedByDescending { post ->
                post.publishedAt ?: post.createdAt ?: ""
            }
        }
    }
}

data class FeedUiState(
    val posts: List<FeedPost> = emptyList(),
    val isRefreshing: Boolean = false,
    val isLoadingMore: Boolean = false,
    val isSwitchingAccount: Boolean = false,
    val error: String? = null,
    val isConnectionError: Boolean = false,
    val snackbarMessage: String? = null,
    val activeTagFilter: String? = null
)

/**
 * Manages recent search history stored in SharedPreferences.
 * Stores up to 5 most recent search queries.
 */
class SearchHistoryManager(context: Context) {

    private val prefs: SharedPreferences = context.getSharedPreferences(
        PREFS_NAME, Context.MODE_PRIVATE
    )

    fun getRecentSearches(): List<String> {
        val raw = prefs.getString(KEY_RECENT_SEARCHES, null) ?: return emptyList()
        return raw.split(SEPARATOR).filter { it.isNotBlank() }
    }

    fun addSearch(query: String) {
        val trimmed = query.trim()
        if (trimmed.isBlank()) return

        val current = getRecentSearches().toMutableList()
        current.remove(trimmed) // Remove if already exists
        current.add(0, trimmed) // Add to front

        // Keep only last MAX_HISTORY items
        val limited = current.take(MAX_HISTORY)
        prefs.edit().putString(KEY_RECENT_SEARCHES, limited.joinToString(SEPARATOR)).apply()
    }

    fun removeSearch(query: String) {
        val current = getRecentSearches().toMutableList()
        current.remove(query.trim())
        prefs.edit().putString(KEY_RECENT_SEARCHES, current.joinToString(SEPARATOR)).apply()
    }

    fun clearAll() {
        prefs.edit().remove(KEY_RECENT_SEARCHES).apply()
    }

    companion object {
        const val PREFS_NAME = "swoosh_search_history"
        const val KEY_RECENT_SEARCHES = "recent_searches"
        const val SEPARATOR = "\u001F" // Unit separator character
        const val MAX_HISTORY = 5
    }
}

fun formatRelativeTime(isoString: String?): String {
    if (isoString == null) return ""
    return try {
        val instant = try {
            ZonedDateTime.parse(isoString).toInstant()
        } catch (e: Exception) {
            Instant.parse(isoString)
        }
        val now = Instant.now()
        val minutes = ChronoUnit.MINUTES.between(instant, now)
        val hours = ChronoUnit.HOURS.between(instant, now)
        val days = ChronoUnit.DAYS.between(instant, now)

        when {
            minutes < 1 -> "now"
            minutes < 60 -> "${minutes}m ago"
            hours < 24 -> "${hours}h ago"
            days < 7 -> "${days}d ago"
            else -> DateTimeFormatter.ofPattern("MMM d").format(ZonedDateTime.ofInstant(instant, java.time.ZoneId.systemDefault()))
        }
    } catch (e: Exception) {
        ""
    }
}
