package com.correx.core.kernel.orchestration

import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.JournalCompactedEvent
import com.correx.core.events.types.SessionId
import com.correx.core.journal.model.DecisionJournalState
import com.correx.core.journal.model.Salience

class JournalCompactionService(
    private val artifactStore: ArtifactStore,
    private val summarize: suspend (String) -> String,
    // Supplier (not a constant) so the threshold can track live config edits without rebuilding
    // the orchestrator: it is read fresh on each compaction check.
    private val tokenThreshold: () -> Int = { DEFAULT_TOKEN_THRESHOLD },
) {
    companion object {
        private const val DEFAULT_TOKEN_THRESHOLD = 2000
    }

    suspend fun compactIfNeeded(
        sessionId: SessionId,
        state: DecisionJournalState,
        renderedTokenEstimate: Int,
        emit: suspend (EventPayload) -> Unit,
    ): Boolean {
        if (renderedTokenEstimate < tokenThreshold() || state.records.isEmpty()) return false

        val throughSequence = state.records.maxOf { it.sequence }
        val highRecords = state.records.filter { it.kind.salience() == Salience.HIGH }
        val lowCount = state.records.count { it.kind.salience() == Salience.LOW }

        val summaryText = if (highRecords.isEmpty()) {
            "(no high-salience decisions to summarize)"
        } else {
            val prompt = buildString {
                appendLine("Summarize the following key decisions concisely (≤200 words).")
                appendLine("Preserve all user intent, approvals, steering, and failures.")
                appendLine()
                highRecords.forEach { appendLine("- [${it.kind}] ${it.summary}") }
            }
            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. Only surfaces on long runs, which
        // are the ones that cross the compaction threshold.
        emit(
            JournalCompactedEvent(
                sessionId = sessionId,
                coversThroughSequence = throughSequence,
                summaryArtifactId = summaryArtifactId,
                lowSalienceOmittedCount = lowCount,
            ),
        )
        return true
    }
}
