package com.correx.core.talkie

import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.IdeaCapturedEvent
import com.correx.core.events.events.IdeaDiscardedEvent
import com.correx.core.events.events.IdeaPromotedEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.SessionId
import com.correx.core.talkie.model.Idea
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap

/**
 * Holds the operator's idea board in memory. Cross-session by design: it folds over the whole event
 * log (not a single session's replay), so an idea captured in one session shows on the board (and
 * feeds the router) in every session. A captured idea is dropped once a matching [IdeaDiscardedEvent]
 * (or [IdeaPromotedEvent]) tombstones it (invariant #1 — the capture stays in the log).
 *
 * The board is folded ONCE at construction from [EventStore.allEvents] (a full, eager table load) and
 * then kept current incrementally via [EventStore.subscribeAll]. This replaces the previous
 * fold-the-entire-log-per-call design, which ran a full deserializing scan on every CHAT context
 * build. The fold is idempotent (map put / set add), so the seed⇄live overlap needs no dedupe.
 */
class IdeaReader(
    eventStore: EventStore,
    scope: CoroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob()),
) {
    // id -> its capture event (source for text/sessionId); tombstoned = discarded/promoted ids.
    private val captures = ConcurrentHashMap<String, IdeaCapturedEvent>()
    private val tombstoned: MutableSet<String> = ConcurrentHashMap.newKeySet()

    init {
        eventStore.allEvents().forEach { apply(it.payload) }
        scope.launch { eventStore.subscribeAll().collect { apply(it.payload) } }
    }

    private fun apply(payload: EventPayload) {
        when (payload) {
            is IdeaDiscardedEvent -> tombstoned += payload.ideaId
            is IdeaPromotedEvent -> tombstoned += payload.ideaId
            is IdeaCapturedEvent -> captures[payload.ideaId] = payload
            else -> Unit
        }
    }

    /** Active ideas (captured, not later discarded or promoted) across all sessions, newest first. */
    fun activeIdeas(): List<Idea> =
        captures.values
            .filterNot { it.ideaId in tombstoned }
            .map { Idea(it.ideaId, it.text, it.timestampMs) }
            .sortedByDescending { it.capturedAtMs }

    /** The session that captured [ideaId], so a tombstone lands alongside its capture. */
    fun sessionOf(ideaId: String): SessionId? = captures[ideaId]?.sessionId

    /** The captured text of [ideaId] (so a promotion can write it into the project profile), or null. */
    fun textOf(ideaId: String): String? = captures[ideaId]?.text
}
