package com.correx.core.kernel.orchestration

import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.context.model.ContextEntry
import com.correx.core.events.events.ContextSummarizedEvent
import com.correx.core.events.events.EventPayload
import com.correx.core.events.types.SessionId

/**
 * Stage 5 (RECURSIVE_SUMMARIZE) for freeform context — the tier the decision-journal
 * compaction path does NOT cover: long tool outputs, docs and retrieved text. Mirrors
 * [JournalCompactionService]: summarize via injected inference, store the summary in the
 * artifact store, emit [ContextSummarizedEvent] referencing it. Replay reads the recorded
 * artifact and never re-calls the LLM (invariants #6/#8).
 */
class TierContextSummarizer(
    private val artifactStore: ArtifactStore,
    private val summarize: suspend (String) -> String,
    private val tokenThreshold: () -> Int = { DEFAULT_TOKEN_THRESHOLD },
) {
    companion object {
        private const val DEFAULT_TOKEN_THRESHOLD = 2000
        private const val CHARS_PER_TOKEN = 4
    }

    /**
     * Summarize [freeformEntries] (tier 2/3 candidates) when their combined size crosses the
     * threshold. Returns true when a summary event was emitted. [tier] selects light summary
     * (2) vs summary-of-summaries (3); protected spans are already preserved in the fact sheet,
     * so the prompt only asks to keep intent and load-bearing facts.
     */
    suspend fun summarizeIfNeeded(
        sessionId: SessionId,
        freeformEntries: List<ContextEntry>,
        coversThroughSequence: Long,
        tier: Int,
        emit: suspend (EventPayload) -> Unit,
    ): Boolean {
        if (freeformEntries.isEmpty()) return false
        val tokenEstimate = freeformEntries.sumOf { it.content.length } / CHARS_PER_TOKEN
        if (tokenEstimate < tokenThreshold()) return false

        val prompt = buildString {
            appendLine("Summarize the following context concisely (≤200 words).")
            appendLine("Preserve user intent, decisions, constraints and every load-bearing fact.")
            appendLine()
            freeformEntries.forEach { appendLine("- ${it.content}") }
        }
        val summaryText = summarize(prompt)
        val summaryArtifactId = artifactStore.put(summaryText.toByteArray(Charsets.UTF_8))
        // Emit directly, NOT inside flushBefore { }: append() already fsyncs artifacts before it
        // persists the event, so wrapping the emit here re-acquires the (non-reentrant) artifact
        // Mutex that flushBefore already holds → self-deadlock (same bug as JournalCompactionService).
        emit(
            ContextSummarizedEvent(
                sessionId = sessionId,
                coversThroughSequence = coversThroughSequence,
                summaryArtifactId = summaryArtifactId,
                tier = tier,
                entriesOmittedCount = freeformEntries.size,
            ),
        )
        return true
    }
}
