package com.correx.core.kernel.orchestration

import com.correx.core.context.model.ContextEntry
import com.correx.core.context.model.ContextLayer
import com.correx.core.context.model.EntryRole
import com.correx.core.events.events.ConceptPromotedEvent
import com.correx.core.events.types.ContextEntryId
import com.correx.core.kernel.concept.ConceptCompilerProjection
import com.correx.core.transitions.graph.StageConfig
import java.util.UUID

/**
 * Deterministic delivery of promoted capability-accretion concepts into a stage's context
 * (design 2026-07-12-acr-concept-compiler.md §"Delivery" — replaces the L3 top-k lottery as the
 * PRIMARY channel; L3 remains an instance-recall fallback). A [ConceptPromotedEvent] is the
 * authoritative record of a validated failure→fix class; injecting it as an L0/system block tells a
 * stage about a known resolution BEFORE it re-derives the same fix.
 *
 * Relevance filter (avoids the broadcast the design's verdict #1 rejected): only inject a concept a
 * stage could actually act on — a concept whose gate requires a capability ([GATE_REQUIRED_CAPABILITY])
 * the stage does not hold is skipped (no point telling a write-less verifier about a file_write fix).
 * Gates with no required capability (lint/review) are broadly relevant. Bounded to [limit] by
 * occurrences so context stays small. Pure over the promoted set → same input, same injection.
 */
internal fun selectPromotedConcepts(
    promoted: List<ConceptPromotedEvent>,
    allowedTools: Set<String>,
    contradicted: Set<String> = emptySet(),
    limit: Int = MAX_PROMOTED_CONCEPTS,
): List<ConceptPromotedEvent> =
    promoted
        // Honor a contradiction observed AFTER promotion (design 2026-07-12 §"Decay/contradiction"):
        // a concept whose class later failed to hold must not keep being delivered as a known fix.
        .filter { it.classKey.ifBlank { it.fingerprint } !in contradicted }
        .filter { concept -> GATE_REQUIRED_CAPABILITY[concept.gate]?.let { it in allowedTools } ?: true }
        .distinctBy { it.classKey.ifBlank { it.fingerprint } }
        .sortedByDescending { it.occurrences }
        .take(limit)

/** L0/system context entries for the concepts relevant to [stageConfig], read from the whole log. */
internal suspend fun SessionOrchestrator.promotedConceptEntries(stageConfig: StageConfig): List<ContextEntry> {
    val events = eventStore.allEvents().toList()
    val promoted = events.mapNotNull { it.payload as? ConceptPromotedEvent }
    if (promoted.isEmpty()) return emptyList()
    // Fold the same projection the compiler uses to find classes contradicted since promotion.
    val projection = ConceptCompilerProjection()
    val state = events.fold(projection.initial()) { s, e -> projection.apply(s, e) }
    val contradicted = state.clusters.values.filter { it.contradicted }.map { it.classKey }.toSet()
    return selectPromotedConcepts(promoted, stageConfig.effectiveAllowedTools, contradicted).map { concept ->
        // Deliver the FIX, not just a nag (design §"carry the fix"): when the accreted concept carries a
        // validated-fix ref, point the stage at the file that resolved this class before, so it applies a
        // known resolution instead of re-deriving one.
        val fixPointer = concept.fixPath?.let { " A prior validated fix modified: $it — mirror that resolution." }
            ?: ""
        val content = "Known ${concept.gate} failure class (accreted from prior validated runs). " +
            concept.conceptText + fixPointer
        ContextEntry(
            id = ContextEntryId(UUID.randomUUID().toString()),
            layer = ContextLayer.L0,
            content = content,
            sourceType = "promotedConcept",
            sourceId = concept.classKey.ifBlank { concept.fingerprint },
            tokenEstimate = estimateTokens(content),
            role = EntryRole.SYSTEM,
        )
    }
}

private const val MAX_PROMOTED_CONCEPTS = 3
