package com.correx.core.kernel.concept

import com.correx.core.events.events.ConceptPromotedEvent
import com.correx.core.events.events.FileWrittenEvent
import com.correx.core.events.events.RetryAttemptedEvent
import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StageFailedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.WorkflowFailedEvent
import com.correx.core.sessions.projections.Projection

/** Default validated-fix count a fingerprint must reach before promotion (design §"Promotion rule"). */
const val DEFAULT_PROMOTION_THRESHOLD = 3

/**
 * A recurring failure and its resolution tally, keyed by [classKey] (design 2026-07-12
 * §"Promote to a class key"). The class key is `gate + normalized(signature)`, so near-identical
 * failures that differ only in volatile tokens (paths, line numbers, hashes) aggregate into one
 * reusable concept instead of a fresh cluster per exact fingerprint. The exact [fingerprint] is
 * retained for the last-seen instance (retrieval-friendly, and the L3 instance-recall id).
 */
data class ConceptCluster(
    val classKey: String,
    val fingerprint: String,
    val gate: String,
    // First line of the recurring failure reason — the retrieval-friendly signature.
    val signature: String,
    // Count of times a stage retrying this class went on to complete = validated failure→fix.
    val validatedFixes: Int = 0,
    // Any StageFailed / WorkflowFailed observed while this class was open: the fix did NOT hold.
    val contradicted: Boolean = false,
    // The validated fix: CAS path + post-image hash of the last file the resolving stage wrote before
    // completing. Null until a fix→complete pair carries a file write. First captured ref wins.
    val fixPath: String? = null,
    val fixHash: String? = null,
)

/** A failure currently being fought by a stage — resolved by the next StageCompleted/Failed. */
data class OpenFailure(val classKey: String, val fingerprint: String, val gate: String, val signature: String)

/**
 * Normalize a failure signature into a reusable class dimension: lowercase, collapse digit runs and
 * quoted/pathish tokens to placeholders so `src/App.tsx:12` and `src/Nav.tsx:88` map to one class.
 * Deterministic (replay-safe) — pure string transform, no environment read.
 */
fun conceptClassKey(gate: String, signature: String): String {
    val normalized = signature.lowercase()
        .replace(Regex("""['"`][^'"`]*['"`]"""), "<str>")
        .replace(Regex("""\b[\w./\\-]+\.[a-z0-9]{1,5}\b"""), "<path>")
        .replace(Regex("""\d+"""), "#")
        .replace(Regex("""\s+"""), " ")
        .trim()
    return "$gate:$normalized"
}

data class ConceptCompilerState(
    // Keyed by classKey (design §"Promote to a class key").
    val clusters: Map<String, ConceptCluster> = emptyMap(),
    // classKeys already emitted as ConceptPromotedEvent — folded back so replay never re-promotes.
    val promoted: Set<String> = emptySet(),
    // Per-stage open failure the stage is retrying; keyed by "sessionId/stageId" so concurrent sessions
    // in one cross-session fold don't collide.
    val open: Map<String, OpenFailure> = emptyMap(),
    // Last file write observed per session — the candidate validated-fix ref attached when the session's
    // open failure resolves as fixed. Keyed by sessionId; execution is sequential so one stage writes at
    // a time. Cleared on resolve so a later stage's writes don't back-attribute to an earlier fix.
    val lastWrite: Map<String, Pair<String, String>> = emptyMap(),
) {
    /**
     * Clusters that have earned promotion but haven't been emitted yet: validated ≥ [threshold], never
     * contradicted, not already promoted. Pure — the caller (a service) appends the ConceptPromotedEvent.
     */
    fun promotable(threshold: Int = DEFAULT_PROMOTION_THRESHOLD): List<ConceptCluster> =
        clusters.values.filter {
            it.classKey !in promoted && !it.contradicted && it.validatedFixes >= threshold
        }
}

/**
 * Read-only projection over the event log that clusters validated failure→fix pairs by
 * [RetryAttemptedEvent.fingerprint] (gate-agnostic — contract/review/plan-compile/lint/static all
 * flow through the same retry event) and tracks which fingerprints are promotable. Fed the full
 * cross-session stream by its driver; the fold is deterministic and replayable (invariant #8).
 *
 * Signal: a stage emits RetryAttemptedEvent(fingerprint=fp) then StageCompleted ⇒ fp was validated-fixed.
 * A StageFailed/WorkflowFailed while fp is still open ⇒ the fix didn't hold (contradiction).
 */
class ConceptCompilerProjection : Projection<ConceptCompilerState> {

    override fun initial() = ConceptCompilerState()

    override fun apply(state: ConceptCompilerState, event: StoredEvent): ConceptCompilerState =
        when (val p = event.payload) {
            is RetryAttemptedEvent -> {
                val key = "${p.sessionId.value}/${p.stageId.value}"
                val sig = p.failureReason.lineSequence().firstOrNull()?.take(SIGNATURE_MAX)?.trim().orEmpty()
                val classKey = conceptClassKey(p.gate, sig)
                state.copy(open = state.open + (key to OpenFailure(classKey, p.fingerprint, p.gate, sig)))
            }
            is FileWrittenEvent -> p.postImageHash?.let {
                state.copy(lastWrite = state.lastWrite + (p.sessionId.value to (p.path to it)))
            } ?: state
            is StageCompletedEvent -> resolve(state, "${p.sessionId.value}/${p.stageId.value}", fixed = true)
            is StageFailedEvent -> resolve(state, "${p.sessionId.value}/${p.stageId.value}", fixed = false)
            is WorkflowFailedEvent -> failAllInSession(state, p.sessionId.value)
            is ConceptPromotedEvent -> state.copy(promoted = state.promoted + p.classKey)
            else -> state
        }

    private fun resolve(state: ConceptCompilerState, key: String, fixed: Boolean): ConceptCompilerState {
        val failure = state.open[key] ?: return state
        val sessionId = key.substringBefore('/')
        val existing = state.clusters[failure.classKey]
            ?: ConceptCluster(failure.classKey, failure.fingerprint, failure.gate, failure.signature)
        val updated = if (fixed) {
            val fix = state.lastWrite[sessionId]
            existing.copy(
                // Keep the latest instance's fingerprint/signature for recall.
                fingerprint = failure.fingerprint,
                signature = failure.signature,
                validatedFixes = existing.validatedFixes + 1,
                // First captured ref wins — keep the earliest validated fix as the canonical resolution.
                fixPath = existing.fixPath ?: fix?.first,
                fixHash = existing.fixHash ?: fix?.second,
            )
        } else {
            existing.copy(contradicted = true)
        }
        return state.copy(
            clusters = state.clusters + (failure.classKey to updated),
            open = state.open - key,
            lastWrite = state.lastWrite - sessionId,
        )
    }

    // A workflow that failed terminally contradicts every fingerprint still open in that session.
    private fun failAllInSession(state: ConceptCompilerState, sessionId: String): ConceptCompilerState {
        val prefix = "$sessionId/"
        var acc = state
        state.open.keys.filter { it.startsWith(prefix) }.forEach { acc = resolve(acc, it, fixed = false) }
        return acc
    }

    private companion object {
        const val SIGNATURE_MAX = 200
    }
}
