package com.correx.apps.server.concept

import com.correx.core.events.events.ConceptPromotedEvent
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.inference.Embedder
import com.correx.core.kernel.concept.ConceptCluster
import com.correx.core.kernel.concept.ConceptCompilerProjection
import com.correx.core.kernel.concept.DEFAULT_PROMOTION_THRESHOLD
import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.talkie.l3.L3MemoryStore
import kotlinx.coroutines.CancellationException
import kotlinx.datetime.Clock
import org.slf4j.LoggerFactory
import java.util.UUID

private val log = LoggerFactory.getLogger(ConceptCompilerService::class.java)

/**
 * The write-side of the heuristic concept compiler (design 2026-07-12-acr-concept-compiler.md).
 * [ConceptCompilerProjection] does the deterministic clustering; this service is the single "write":
 * it folds the whole cross-session log, and for every fingerprint that has just become promotable it
 * appends a [ConceptPromotedEvent] (the authoritative record, idempotent under replay) and best-effort
 * injects the concept into L3 so it is retrieved on demand into future stage context — the same
 * pull/top-k path RepoKnowledge already uses, no broadcast. The L3 write is non-authoritative
 * (invariant #6); the event is truth, so an embed/store failure is logged and swallowed.
 *
 * Deterministic rule firing → no assessor gate needed (invariant #3/#7 hold trivially).
 *
 * ponytail: [runOnce] re-folds `allEvents()` on each call — O(n) in log size. Fine at local-first
 * scale and called on stage completions, not per-event. Switch to an incremental in-memory fold over
 * `subscribeAll()` if the log outgrows a full rescan.
 */
class ConceptCompilerService(
    private val eventStore: EventStore,
    private val embedder: Embedder,
    private val l3MemoryStore: L3MemoryStore,
    private val threshold: Int = DEFAULT_PROMOTION_THRESHOLD,
) {
    private val projection = ConceptCompilerProjection()

    /** Fold the full log, promote every newly-promotable fingerprint. Safe to call repeatedly. */
    suspend fun runOnce() {
        val state = eventStore.allEvents().fold(projection.initial(), projection::apply)
        state.promotable(threshold).forEach { promote(it) }
    }

    private suspend fun promote(cluster: ConceptCluster) {
        val text = conceptText(cluster)
        eventStore.append(
            NewEvent(
                metadata = metadata(),
                payload = ConceptPromotedEvent(
                    sessionId = SYSTEM_SESSION,
                    fingerprint = cluster.fingerprint,
                    classKey = cluster.classKey,
                    gate = cluster.gate,
                    conceptText = text,
                    occurrences = cluster.validatedFixes,
                    fixPath = cluster.fixPath,
                    fixHash = cluster.fixHash,
                ),
            ),
        )
        injectToL3(cluster.fingerprint, text)
        log.info("promoted concept {} ({}x, gate={})", cluster.fingerprint, cluster.validatedFixes, cluster.gate)
    }

    private suspend fun injectToL3(fingerprint: String, text: String) {
        runCatching {
            val now = Clock.System.now().toEpochMilliseconds()
            l3MemoryStore.store(
                L3MemoryEntry(
                    id = "concept:$fingerprint",
                    sessionId = SYSTEM_SESSION,
                    turnId = "concept:$fingerprint",
                    text = text,
                    vector = embedder.embed(text),
                    timestampMs = now,
                ),
            )
        }.exceptionOrNull()?.let { e ->
            if (e is CancellationException) throw e
            log.warn("L3 inject failed for concept {}: {}", fingerprint, e.message)
        }
    }

    private fun conceptText(c: ConceptCluster): String {
        val resolution = c.fixPath?.let { path ->
            " Validated resolution in `$path`" + (c.fixHash?.let { "@$it" } ?: "") + " — inspect it before re-deriving."
        } ?: " This failure class has a known resolution — check prior fixes before re-deriving."
        return "Recurring ${c.gate} failure (validated-fixed ${c.validatedFixes}× across sessions): " +
            "${c.signature}.$resolution"
    }

    private fun metadata() = EventMetadata(
        eventId = EventId(UUID.randomUUID().toString()),
        sessionId = SYSTEM_SESSION,
        timestamp = Clock.System.now(),
        schemaVersion = 1,
        causationId = null,
        correlationId = null,
    )

    companion object {
        /** Cross-session concepts live on their own stream, out of any user session's replay. */
        val SYSTEM_SESSION = SessionId("__concept_compiler__")
    }
}
