package app.closer.ui.home

import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.core.navigation.AppRoute
import app.closer.crypto.CoupleEncryptionManager
import app.closer.crypto.EncryptionStatus
import app.closer.crypto.SealedRevealManager
import app.closer.domain.model.GameType
import app.closer.data.remote.FirestoreAnswerDataSource
import app.closer.data.remote.FirestoreCapsuleDataSource
import app.closer.data.remote.FirestoreChallengeDataSource
import app.closer.domain.model.DatePlanStatus
import app.closer.domain.model.LocalAnswer
import app.closer.domain.model.Question
import app.closer.domain.model.QuestionCategory
import app.closer.domain.repository.AuthRepository
import app.closer.domain.repository.CoupleRepository
import app.closer.domain.repository.DatePlanRepository
import app.closer.domain.repository.LocalAnswerRepository
import app.closer.domain.repository.QuestionRepository
import app.closer.domain.repository.QuestionSessionRepository
import app.closer.domain.repository.UserRepository
import app.closer.domain.model.Outcome
import app.closer.domain.model.OutcomeDay
import app.closer.domain.model.OutcomeScores
import app.closer.domain.repository.OutcomeRepository
import app.closer.domain.repository.SettingsRepository
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.functions.FirebaseFunctions
import dagger.hilt.android.lifecycle.HiltViewModel
import app.closer.ui.home.HomePriorityEngine.Input as PriorityInput
import app.closer.ui.home.HomePriorityEngine.Priority
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.ZoneId
import javax.inject.Inject
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.first

data class HomeCategorySummary(
    val category: QuestionCategory,
    val questionCount: Int
)

data class HomeAnswerStats(
    val total: Int = 0,
    val revealed: Int = 0,
    val private: Int = 0,
    val latest: LocalAnswer? = null,
    val answeredQuestionIds: Set<String> = emptySet()
)

enum class HomeActionTarget {
    InvitePartner,
    DailyQuestion,
    AnswerHistory,
    QuestionPacks,
    Settings,
    AnswerReveal,
    Game,
    Challenge,
    DatePlan,
    MemoryCapsule
}

enum class HomeActionTone {
    Invite,
    Daily,
    Reflection,
    Ritual,
    Starter,
    Pack,
    Utility,
    Pending
}

data class HomeAction(
    val eyebrow: String,
    val title: String,
    val body: String,
    val cta: String,
    val target: HomeActionTarget,
    val tone: HomeActionTone,
    val metric: String? = null,
    val categoryId: String? = null,
    // For the "your partner is waiting to play" CTA: the specific game route to resume
    // (so "Play now" jumps into the actual waiting game, not the generic Play hub). B-002.
    val gameRoute: String? = null
)

data class PendingActionCard(
    val title: String,
    val subtitle: String?,
    val priority: Int,
    val target: HomeActionTarget
)

/**
 * The entry route that resumes an in-progress game of [gameType]. Each game screen
 * detects the couple's active session on open and joins it, so navigating here lets the
 * Home "Play now" CTA drop the user straight back into the waiting game (B-002).
 */
private fun gameRouteFor(gameType: String?): String? = when (gameType) {
    GameType.WHEEL -> AppRoute.SPIN_WHEEL_RANDOM
    GameType.THIS_OR_THAT -> AppRoute.THIS_OR_THAT
    GameType.HOW_WELL -> AppRoute.HOW_WELL
    GameType.DESIRE_SYNC -> AppRoute.DESIRE_SYNC
    else -> null
}

enum class DailyQuestionState {
    UNANSWERED,
    USER_ANSWERED_PARTNER_PENDING,
    PARTNER_ANSWERED_USER_PENDING,
    BOTH_ANSWERED,
    REVEALED
}

data class HomeUiState(
    val isLoading: Boolean = true,
    val error: String? = null,
    val dailyQuestion: Question? = null,
    val categories: List<HomeCategorySummary> = emptyList(),
    val answerStats: HomeAnswerStats = HomeAnswerStats(),
    val partnerName: String? = null,
    val streakCount: Int = 0,
    val isPaired: Boolean = false,
    val primaryAction: HomeAction? = null,
    val secondaryActions: List<HomeAction> = emptyList(),
    val partnerLeftEvent: Boolean = false,
    val needsRecovery: Boolean = false,
    val coupleId: String? = null,
    val dailyQuestionState: DailyQuestionState = DailyQuestionState.UNANSWERED,
    val hasPartnerAnsweredToday: Boolean = false,
    val partnerAnsweredQuestionId: String? = null,
    val hasRevealedToday: Boolean = false,
    val pendingActions: List<PendingActionCard> = emptyList(),
    // Retention signals — populated in loadHome() and observeAnswers()
    val hasWaitingGame: Boolean = false,
    // The route of the active game waiting for this user, so the Home "Play now" CTA
    // resumes that specific game instead of dumping on the generic Play hub (B-002).
    val waitingGameRoute: String? = null,
    val hasActiveChallenge: Boolean = false,
    val hasUpcomingDatePlan: Boolean = false,
    val hasUnlockedCapsule: Boolean = false,
    val weeklyRecapReady: Boolean = false,
    val reminderSentEvent: Boolean = false,
    // Outcome check-in state
    val outcomeSubmitSuccess: Boolean = false,
    val outcomeError: String? = null,
    val showOutcomeBaselineDialog: Boolean = false,
    val showOutcomeFollowUpDialog: Boolean = false,
    val outcomeFollowUpDay: OutcomeDay? = null,
    /** Non-null when a streak tier was just reached and should be celebrated. */
    val streakMilestone: Int? = null,
    val unreadActivityCount: Int = 0
)

@HiltViewModel
class HomeViewModel @Inject constructor(
    private val questionRepository: QuestionRepository,
    private val localAnswerRepository: LocalAnswerRepository,
    private val authRepository: AuthRepository,
    private val coupleRepository: CoupleRepository,
    private val userRepository: UserRepository,
    private val encryptionManager: CoupleEncryptionManager,
    private val db: FirebaseFirestore,
    private val functions: FirebaseFunctions,
    private val questionSessionRepository: QuestionSessionRepository,
    private val challengeDataSource: FirestoreChallengeDataSource,
    private val capsuleDataSource: FirestoreCapsuleDataSource,
    private val datePlanRepository: DatePlanRepository,
    private val sealedRevealManager: SealedRevealManager,
    private val outcomeRepository: OutcomeRepository,
    private val settingsRepository: SettingsRepository,
    private val activityDataSource: app.closer.data.remote.FirestoreActivityDataSource,
    private val dailyQuestionResolver: app.closer.domain.usecase.DailyQuestionResolver
) : ViewModel() {

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

    private var coupleStateListener: com.google.firebase.firestore.ListenerRegistration? = null
    private var partnerAnswerListener: com.google.firebase.firestore.ListenerRegistration? = null

    init {
        loadHome()
        observeAnswers()
        observeCoupleState()
        observeUnreadActivity()
    }

    private fun observeUnreadActivity() {
        val uid = authRepository.currentUserId ?: return
        viewModelScope.launch {
            activityDataSource.observeUnreadCount(uid).collect { count ->
                _uiState.update { it.copy(unreadActivityCount = count) }
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        coupleStateListener?.remove()
        coupleStateListener = null
        partnerAnswerListener?.remove()
        partnerAnswerListener = null
    }

    fun loadHome() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }
            try {
                // Resolve via the shared resolver so Home shows the SAME question as the
                // daily-question screen (assignment-backed), keeping answered/waiting state in sync.
                val dailyQuestion = dailyQuestionResolver.resolve().question
                val categories = questionRepository.getCategories()
                    .take(6)
                    .map { category ->
                        HomeCategorySummary(
                            category = category,
                            questionCount = questionRepository.getQuestionCountByCategory(category.id)
                        )
                    }
                val uid = authRepository.currentUserId
                uid?.let { launch { runCatching { sealedRevealManager.ensurePublicKeyPublished(it) } } }
                val couple = uid?.let { runCatching { coupleRepository.getCoupleForUser(it) }.getOrNull() }
                val partnerName = couple?.userIds?.firstOrNull { it != uid }?.let { partnerId ->
                    runCatching { userRepository.getUser(partnerId)?.displayName }
                        .onFailure { Log.w(TAG, "Could not load partner display name", it) }
                        .getOrNull()
                }
                val encryptionStatus = couple?.let(encryptionManager::checkStatus)
                val needsRecovery = encryptionStatus == EncryptionStatus.NEEDS_RECOVERY

                // Outcome check-in due-state calculation
                val appSettings = settingsRepository.settings.first()
                val outcomeBaselineShownAt = appSettings.outcomeBaselineShownAt

                // Streak milestone: celebrate each tier (7/30/100/365) exactly once.
                val streak = couple?.streakCount ?: 0
                val crossedMilestone = STREAK_MILESTONES
                    .lastOrNull { streak >= it && appSettings.lastCelebratedStreakMilestone < it }
                if (crossedMilestone != null) {
                    runCatching { settingsRepository.setLastCelebratedStreakMilestone(crossedMilestone) }
                }
                val outcomes = couple?.let {
                    runCatching { outcomeRepository.getOutcomes(it.id) }
                        .onFailure { Log.w(TAG, "Could not load outcomes", it) }
                        .getOrDefault(emptyList())
                } ?: emptyList()
                val baselineRecorded = outcomes.any { it.dayKey == OutcomeDay.BASELINE.key }
                val showBaselineDialog = couple != null && !baselineRecorded && outcomeBaselineShownAt == 0L
                val followUpDay = couple?.let { dueFollowUpDay(it.createdAt, outcomes) }

                // Retention signal fetches — run in parallel, failures silently default to false.
                var hasWaitingGame = false
                var waitingGameRoute: String? = null
                var hasActiveChallenge = false
                var hasUpcomingDatePlan = false
                var hasUnlockedCapsule = false
                val coupleId = couple?.id
                if (couple != null && coupleId != null && uid != null) {
                    coroutineScope {
                        val gameJob = async {
                            runCatching {
                                val session = questionSessionRepository.getActiveSessionForCouple(coupleId)
                                    ?.takeIf { uid !in it.completedByUsers }
                                session to gameRouteFor(session?.gameType)
                            }.getOrDefault(null to null)
                        }
                        val challengeJob = async {
                            runCatching {
                                challengeDataSource.getActiveChallengeId(coupleId) != null
                            }.getOrDefault(false)
                        }
                        val dateJob = async {
                            runCatching {
                                val now = System.currentTimeMillis()
                                val sevenDaysMs = 7L * 24 * 60 * 60 * 1000
                                datePlanRepository.getPlansByStatus(
                                    coupleId, DatePlanStatus.PLANNED.toFirestoreValue()
                                ).any { it.scheduledDate > now && it.scheduledDate - now <= sevenDaysMs }
                            }.getOrDefault(false)
                        }
                        val capsuleJob = async {
                            runCatching {
                                val now = System.currentTimeMillis()
                                capsuleDataSource.getCapsules(coupleId)
                                    .any { it.status == "sealed" && it.unlockAt in 1L..now }
                            }.getOrDefault(false)
                        }
                        val (waitingSession, waitingRoute) = gameJob.await()
                        hasWaitingGame = waitingSession != null
                        waitingGameRoute = waitingRoute
                        hasActiveChallenge = challengeJob.await()
                        hasUpcomingDatePlan = dateJob.await()
                        hasUnlockedCapsule = capsuleJob.await()
                    }
                }

                _uiState.update { current ->
                    current.copy(
                        isLoading = false,
                        dailyQuestion = dailyQuestion,
                        categories = categories,
                        partnerName = partnerName,
                        streakCount = couple?.streakCount ?: 0,
                        isPaired = couple != null,
                        coupleId = coupleId,
                        partnerLeftEvent = false,
                        needsRecovery = needsRecovery,
                        hasWaitingGame = hasWaitingGame,
                        waitingGameRoute = waitingGameRoute,
                        hasActiveChallenge = hasActiveChallenge,
                        hasUpcomingDatePlan = hasUpcomingDatePlan,
                        hasUnlockedCapsule = hasUnlockedCapsule,
                        showOutcomeBaselineDialog = showBaselineDialog,
                        showOutcomeFollowUpDialog = followUpDay != null,
                        outcomeFollowUpDay = followUpDay,
                        streakMilestone = crossedMilestone ?: current.streakMilestone
                    ).refreshDailyQuestionState().withHomeActions()
                }
                observePartnerAnswer(couple?.id, couple?.userIds, dailyQuestion?.id)
            } catch (e: Exception) {
                _uiState.update {
                    it.copy(
                        isLoading = false,
                        error = e.message ?: "Couldn't load your space right now."
                    ).withHomeActions()
                }
            }
        }
    }

    private fun observeCoupleState() {
        val uid = authRepository.currentUserId ?: return
        coupleStateListener?.remove()
        coupleStateListener = db.collection("users").document(uid)
            .addSnapshotListener(com.google.firebase.firestore.MetadataChanges.INCLUDE) { snapshot, error ->
                if (error != null || snapshot == null || !snapshot.exists()) {
                    return@addSnapshotListener
                }
                val newCoupleId = snapshot.getString("coupleId")
                val oldIsPaired = _uiState.value.isPaired
                val oldPartnerName = _uiState.value.partnerName
                val newIsPaired = !newCoupleId.isNullOrBlank()

                if (newIsPaired != oldIsPaired) {
                    // Capture partner name before reload so the banner can reference it.
                    val showPartnerLeftBanner = oldIsPaired && !newIsPaired
                    if (showPartnerLeftBanner) {
                        _uiState.update {
                            it.copy(
                                isPaired = false,
                                partnerName = null,
                                partnerLeftEvent = true
                            ).withHomeActions()
                        }
                    }
                    loadHome()
                }
            }
    }

    /**
     * Consumes the partner-left banner event. Call from the UI after showing it.
     */
    fun consumePartnerLeftEvent() {
        _uiState.update { it.copy(partnerLeftEvent = false) }
    }

    /** Called after the recovery flow completes so the banner goes away. */
    fun onRecoveryCompleted() {
        _uiState.update { it.copy(needsRecovery = false) }
        loadHome()
    }

    private fun dueFollowUpDay(createdAt: Long, outcomes: List<Outcome>): OutcomeDay? {
        val paired = java.time.Instant.ofEpochMilli(createdAt).atZone(ZoneId.systemDefault()).toLocalDate()
        val today = LocalDate.now()
        val ageDays = java.time.temporal.ChronoUnit.DAYS.between(paired, today)
        val due = listOf(
            OutcomeDay.DAY_30 to 30,
            OutcomeDay.DAY_60 to 60,
            OutcomeDay.DAY_90 to 90
        ).firstOrNull { (_, days) -> ageDays >= days } ?: return null
        return if (outcomes.any { it.dayKey == due.first.key }) null else due.first
    }

    fun submitOutcome(dayKey: String, scores: OutcomeScores) {
        viewModelScope.launch {
            _uiState.update { it.copy(outcomeSubmitSuccess = false, outcomeError = null) }
            try {
                val uid = authRepository.currentUserId
                    ?: throw IllegalStateException("Not signed in.")
                val couple = coupleRepository.getCoupleForUser(uid)
                    ?: throw IllegalStateException("Not paired.")
                outcomeRepository.submitOutcome(couple.id, dayKey, scores).getOrThrow()
                settingsRepository.setOutcomeLastPromptedDay(dayKey)
                if (dayKey == OutcomeDay.BASELINE.key) {
                    settingsRepository.markOutcomeBaselineShown()
                }
                _uiState.update {
                    it.copy(
                        outcomeSubmitSuccess = true,
                        showOutcomeBaselineDialog = false,
                        showOutcomeFollowUpDialog = false,
                        outcomeFollowUpDay = null
                    )
                }
                loadHome()
            } catch (e: Exception) {
                _uiState.update { it.copy(outcomeError = e.message ?: "Couldn’t save check-in.") }
            }
        }
    }

    fun markBaselineOutcomeShown() {
        viewModelScope.launch {
            settingsRepository.markOutcomeBaselineShown()
            _uiState.update { it.copy(showOutcomeBaselineDialog = false) }
        }
    }

    fun markFollowUpOutcomeShown(dayKey: String) {
        viewModelScope.launch {
            settingsRepository.setOutcomeLastPromptedDay(dayKey)
            _uiState.update { it.copy(showOutcomeFollowUpDialog = false, outcomeFollowUpDay = null) }
        }
    }

    fun consumeOutcomeSuccess() = _uiState.update { it.copy(outcomeSubmitSuccess = false) }
    fun consumeOutcomeError() = _uiState.update { it.copy(outcomeError = null) }

    /**
     * Sends a gentle reminder to the partner that the daily question is waiting.
     * Notification wiring is intentionally a no-op until Batch 6.
     */
    fun sendGentleReminder() {
        // Only meaningful when we have answered but partner hasn't.
        val state = _uiState.value
        if (state.dailyQuestionState != DailyQuestionState.USER_ANSWERED_PARTNER_PENDING) return
        viewModelScope.launch {
            runCatching {
                functions.getHttpsCallable("sendGentleReminderCallable").call().await()
            }.onSuccess {
                _uiState.update { it.copy(reminderSentEvent = true) }
            }.onFailure { e ->
                Log.w(TAG, "Gentle reminder failed", e)
            }
        }
    }

    fun consumeReminderSentEvent() = _uiState.update { it.copy(reminderSentEvent = false) }

    fun consumeStreakMilestone() = _uiState.update { it.copy(streakMilestone = null) }

    private fun observeAnswers() {
        viewModelScope.launch {
            localAnswerRepository.observeAnswers().collect { answers ->
                val sorted = answers.sortedByDescending { it.updatedAt }
                val now = System.currentTimeMillis()
                val sevenDaysMs = 7L * 24 * 60 * 60 * 1000
                val isMonday = LocalDate.now().dayOfWeek == DayOfWeek.MONDAY
                val weeklyRecapReady = isMonday && answers.any { now - it.updatedAt <= sevenDaysMs }
                _uiState.update {
                    it.copy(
                        answerStats = HomeAnswerStats(
                            total = answers.size,
                            revealed = answers.count { answer -> answer.isRevealed },
                            private = answers.count { answer -> !answer.isRevealed },
                            latest = sorted.firstOrNull(),
                            answeredQuestionIds = answers.map { answer -> answer.questionId }.toSet()
                        ),
                        weeklyRecapReady = weeklyRecapReady
                    ).refreshDailyQuestionState().withHomeActions()
                }
            }
        }
    }

    private fun observePartnerAnswer(
        coupleId: String?,
        coupleUserIds: List<String>?,
        dailyQuestionId: String?
    ) {
        partnerAnswerListener?.remove()
        partnerAnswerListener = null
        val cId = coupleId ?: return
        val qId = dailyQuestionId ?: return
        val uid = authRepository.currentUserId ?: return
        val partnerId = coupleUserIds?.firstOrNull { it != uid } ?: return

        val today = FirestoreAnswerDataSource.todayLocalDateString()
        partnerAnswerListener = db.collection("couples")
            .document(cId)
            .collection("daily_question")
            .document(today)
            .collection("answers")
            .document(partnerId)
            .addSnapshotListener(com.google.firebase.firestore.MetadataChanges.INCLUDE) { snapshot, error ->
                if (error != null) {
                    Log.w(TAG, "Partner answer listener failed", error)
                    return@addSnapshotListener
                }
                val hasPartnerAnswer = snapshot?.exists() == true
                _uiState.update {
                    it.copy(
                        hasPartnerAnsweredToday = hasPartnerAnswer,
                        partnerAnsweredQuestionId = if (hasPartnerAnswer) qId else null
                    ).refreshDailyQuestionState().withHomeActions()
                }
            }
    }

    private fun HomeUiState.refreshDailyQuestionState(): HomeUiState {
        val questionId = dailyQuestion?.id
        val userAnswered = questionId != null && questionId in answerStats.answeredQuestionIds
        val userRevealed = questionId != null && answerStats.latest?.let { latest ->
            latest.questionId == questionId && latest.isRevealed
        } == true

        val state = when {
            questionId == null -> DailyQuestionState.UNANSWERED
            userRevealed -> DailyQuestionState.REVEALED
            userAnswered && hasPartnerAnsweredToday -> DailyQuestionState.BOTH_ANSWERED
            userAnswered -> DailyQuestionState.USER_ANSWERED_PARTNER_PENDING
            hasPartnerAnsweredToday -> DailyQuestionState.PARTNER_ANSWERED_USER_PENDING
            else -> DailyQuestionState.UNANSWERED
        }
        return copy(
            dailyQuestionState = state,
            hasRevealedToday = userRevealed
        )
    }

    private fun HomeUiState.withHomeActions(): HomeUiState {
        if (isLoading || error != null) {
            return copy(primaryAction = null, secondaryActions = emptyList(), pendingActions = emptyList())
        }

        val engineInput = PriorityInput(
            needsCriticalAction = needsRecovery,
            isPaired = isPaired,
            needsEncryptionUnlock = needsRecovery,
            revealReady = dailyQuestionState == DailyQuestionState.BOTH_ANSWERED,
            partnerAnsweredUserPending = dailyQuestionState == DailyQuestionState.PARTNER_ANSWERED_USER_PENDING,
            gameWaiting = hasWaitingGame(),
            challengeWaiting = hasIncompleteChallenge(),
            dailyQuestionUnanswered = dailyQuestionState == DailyQuestionState.UNANSWERED && dailyQuestion != null,
            weeklyRecapReady = weeklyRecapReady,
            capsuleUnlocked = hasUnlockedCapsule(),
            dateReminder = hasUpcomingDate(),
            suggestedPackAvailable = categories.isNotEmpty(),
            exploreGamesAvailable = categories.isNotEmpty()
        )
        val priorityOutput = HomePriorityEngine.compute(engineInput)

        val primary = priorityOutput.primary?.let { toHomeAction(it.priority) }
        val secondary = priorityOutput.secondary.mapNotNull { toHomeAction(it.priority) }
        val pending = buildPendingActions()

        return copy(
            primaryAction = primary,
            secondaryActions = secondary.take(3),
            pendingActions = pending.take(3)
        )
    }

    private fun HomeUiState.toHomeAction(priority: Priority): HomeAction? = when (priority) {
        Priority.CRITICAL_ACTION ->
            if (needsRecovery) HomeAction(
                eyebrow = "Account recovery",
                title = "Secure your answers before continuing.",
                body = "A privacy action needs your attention. Complete recovery to keep your shared space safe.",
                cta = "Start recovery",
                target = HomeActionTarget.Settings,
                tone = HomeActionTone.Utility
            ) else null

        Priority.PAIRING_NEEDED -> HomeAction(
            eyebrow = "1 of 2 connected",
            title = "A private space for two",
            body = "Invite your partner to unlock shared reveals, games, streaks, and answers you can both respond to.",
            cta = "Invite partner",
            target = HomeActionTarget.InvitePartner,
            tone = HomeActionTone.Invite
        )

        Priority.ENCRYPTION_UNLOCK_NEEDED -> HomeAction(
            eyebrow = "Encryption unlock",
            title = "Unlock your shared answers.",
            body = "Your couple's encryption needs to be restored. Complete recovery to keep accessing your answers.",
            cta = "Recover keys",
            target = HomeActionTarget.Settings,
            tone = HomeActionTone.Utility
        )

        Priority.REVEAL_READY -> buildDailyQuestionAction(
            title = "Reveal is ready.",
            body = "Both of you answered. Open it together when you are both in the right headspace.",
            cta = "Reveal together"
        )

        Priority.PARTNER_ANSWERED_USER_PENDING -> buildDailyQuestionAction(
            title = "Your partner answered. Your turn.",
            body = "Answer to unlock the reveal. Your response stays private until you are ready.",
            cta = "Answer to unlock reveal"
        )

        Priority.GAME_WAITING -> HomeAction(
            eyebrow = "Game waiting",
            title = "Your partner is waiting to play.",
            body = "A game is ready for the two of you. Jump back in and keep the ritual going.",
            cta = "Play now",
            target = HomeActionTarget.Game,
            tone = HomeActionTone.Ritual,
            gameRoute = waitingGameRoute
        )

        Priority.CHALLENGE_WAITING -> HomeAction(
            eyebrow = "Challenge waiting",
            title = "Today’s small step is ready.",
            body = "Your connection challenge is waiting for both of you. Show up together tonight.",
            cta = "View challenge",
            target = HomeActionTarget.Challenge,
            tone = HomeActionTone.Ritual
        )

        Priority.DAILY_QUESTION_UNANSWERED -> buildDailyQuestionAction(
            title = dailyQuestion?.text ?: "Tonight's question is ready.",
            body = "Start with one honest answer. You can keep it private or reveal it when the moment feels right.",
            cta = "Answer privately"
        )

        Priority.WEEKLY_RECAP_READY -> HomeAction(
            eyebrow = "Your week together",
            title = "Look back at what you built this week.",
            body = "Reveals, answers, and small rituals are summarized for just the two of you.",
            cta = "See recap",
            target = HomeActionTarget.AnswerHistory,
            tone = HomeActionTone.Reflection
        )

        Priority.CAPSULE_UNLOCKED -> HomeAction(
            eyebrow = "Memory capsule",
            title = "A saved memory is ready to open.",
            body = "One of your time capsules unlocked. Open it together and remember why you saved it.",
            cta = "Open capsule",
            target = HomeActionTarget.MemoryCapsule,
            tone = HomeActionTone.Reflection
        )

        Priority.DATE_REMINDER -> HomeAction(
            eyebrow = "Date coming up",
            title = "A planned moment is almost here.",
            body = "You saved a date idea together. Check the details before the night arrives.",
            cta = "View date",
            target = HomeActionTarget.DatePlan,
            tone = HomeActionTone.Ritual
        )

        Priority.SUGGESTED_PACK -> categories.firstOrNull()?.let { category ->
            HomeAction(
                eyebrow = "Suggested pack",
                title = category.category.displayName.ifBlank { "Question pack" },
                body = "${category.questionCount} prompts for when you want a different doorway into the conversation.",
                cta = "Open pack",
                target = HomeActionTarget.QuestionPacks,
                tone = HomeActionTone.Pack,
                categoryId = category.category.id
            )
        }

        Priority.EXPLORE_GAMES -> HomeAction(
            eyebrow = "Explore",
            title = "Try a game together.",
            body = "Playful ways to connect when you both want something light.",
            cta = "Browse games",
            target = HomeActionTarget.QuestionPacks,
            tone = HomeActionTone.Starter
        )
    }

    private fun HomeUiState.buildDailyQuestionAction(
        title: String,
        body: String,
        cta: String
    ): HomeAction = HomeAction(
        eyebrow = "Tonight's prompt",
        title = title,
        body = body,
        cta = cta,
        target = HomeActionTarget.DailyQuestion,
        tone = HomeActionTone.Daily,
        metric = dailyQuestion?.category?.takeIf { it.isNotBlank() }?.toHomeLabel()
    )

    private fun HomeUiState.buildPendingActions(): List<PendingActionCard> {
        if (!isPaired) return emptyList()

        val actions = mutableListOf<PendingActionCard>()

        if (dailyQuestionState == DailyQuestionState.BOTH_ANSWERED) {
            actions += PendingActionCard(
                title = "Reveal is ready",
                subtitle = "Both of you answered tonight. Open it together.",
                priority = 1,
                target = HomeActionTarget.AnswerReveal
            )
        }

        if (dailyQuestionState == DailyQuestionState.PARTNER_ANSWERED_USER_PENDING) {
            actions += PendingActionCard(
                title = "Your partner answered",
                subtitle = "Answer tonight’s question to unlock the reveal.",
                priority = 2,
                target = HomeActionTarget.DailyQuestion
            )
        }

        if (hasWaitingGame()) {
            actions += PendingActionCard(
                title = "Game waiting",
                subtitle = "Your turn to play a game together.",
                priority = 3,
                target = HomeActionTarget.Game
            )
        }

        if (hasIncompleteChallenge()) {
            actions += PendingActionCard(
                title = "Challenge waiting",
                subtitle = "Today’s small step is ready for both of you.",
                priority = 4,
                target = HomeActionTarget.Challenge
            )
        }

        if (hasUpcomingDate()) {
            actions += PendingActionCard(
                title = "Date coming up",
                subtitle = "A planned moment is almost here.",
                priority = 5,
                target = HomeActionTarget.DatePlan
            )
        }

        if (hasUnlockedCapsule()) {
            actions += PendingActionCard(
                title = "Capsule unlocked",
                subtitle = "A saved memory is ready to open together.",
                priority = 6,
                target = HomeActionTarget.MemoryCapsule
            )
        }

        return actions.sortedBy { it.priority }
    }

    private fun HomeUiState.hasWaitingGame(): Boolean = hasWaitingGame

    private fun HomeUiState.hasIncompleteChallenge(): Boolean = hasActiveChallenge

    private fun HomeUiState.hasUpcomingDate(): Boolean = hasUpcomingDatePlan

    private fun HomeUiState.hasUnlockedCapsule(): Boolean = hasUnlockedCapsule

    private fun String.toHomeLabel(): String =
        split("_", "-")
            .filter { part -> part.isNotBlank() }
            .joinToString(" ") { part -> part.replaceFirstChar { it.uppercaseChar() } }

    companion object {
        private const val TAG = "HomeViewModel"
        private val STREAK_MILESTONES = listOf(7, 30, 100, 365)
    }
}
