package com.correx.core.kernel.orchestration

import com.correx.core.events.events.PreemptRedirectBlockedEvent
import com.correx.core.events.events.PreemptRedirectEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.TransitionId
import com.correx.core.transitions.graph.WorkflowGraph
import com.correx.core.transitions.resolution.TransitionDecision

/**
 * Pure decision logic for freestyle graph re-routing (the override half). Given the durable event
 * log, it decides whether an operator-confirmed [PreemptRedirectEvent] should override the
 * resolver's edge. Reads only events + the graph + an artifact-availability predicate, so the
 * orchestrator reaches the identical decision on replay (invariant #8). Side effects (emitting the
 * block event, executing the move) stay in the orchestrator.
 */
internal object PreemptRedirect {

    sealed interface Outcome {
        /** Override the resolver: jump to the confirmed target. */
        data class Override(val move: TransitionDecision.Move) : Outcome
        /** Target is unusable (unknown/unsatisfied needs): record this and keep the normal edge. */
        data class Block(val event: PreemptRedirectBlockedEvent) : Outcome
        /** No pending redirect — use the resolver's decision unchanged. */
        data object None : Outcome
    }

    /**
     * @param artifactAvailable true if the given artifact has usable (non-blank) content for this
     *   session — used to gate the target stage's `needs`.
     */
    fun decide(
        events: List<StoredEvent>,
        graph: WorkflowGraph,
        sessionId: SessionId,
        artifactAvailable: (ArtifactId) -> Boolean,
        nowMs: Long,
    ): Outcome {
        val consumed = events.mapNotNull {
            when (val p = it.payload) {
                is TransitionExecutedEvent -> p.redirectId
                is PreemptRedirectBlockedEvent -> p.redirectId
                else -> null
            }
        }.toSet()
        val pending = events
            .mapNotNull { it.payload as? PreemptRedirectEvent }
            .lastOrNull { it.redirectId !in consumed }
            ?: return Outcome.None

        val reason = blockReason(graph, pending.toStageId, artifactAvailable)
        return if (reason != null) {
            Outcome.Block(
                PreemptRedirectBlockedEvent(
                    redirectId = pending.redirectId,
                    sessionId = sessionId,
                    toStageId = pending.toStageId,
                    reason = reason,
                    timestampMs = nowMs,
                ),
            )
        } else {
            Outcome.Override(
                TransitionDecision.Move(
                    transitionId = TransitionId("redirect:${pending.redirectId}"),
                    to = pending.toStageId,
                    redirectId = pending.redirectId,
                ),
            )
        }
    }

    /** Human-readable reason the target is unusable, or null if the jump is valid. */
    private fun blockReason(
        graph: WorkflowGraph,
        target: StageId,
        artifactAvailable: (ArtifactId) -> Boolean,
    ): String? {
        val stage = graph.stages[target] ?: return "unknown stage '${target.value}'"
        val unmet = stage.needs.filterNot(artifactAvailable)
        return if (unmet.isEmpty()) null
        else "target '${target.value}' has unsatisfied needs: " + unmet.joinToString(", ") { it.value }
    }
}
