package com.correx.core.events.events

import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
 * The architect's validated execution_plan was compiled into a runnable WorkflowGraph and
 * locked as the baseline for phase 2. [planArtifactId] points at the validated plan JSON in
 * CAS (referenced, not inlined); [stageIds]/[startStageId] pin the compiled shape so replay
 * rebuilds the identical graph. Soft lock: later high-priority steering can still preempt.
 */
@Serializable
@SerialName("ExecutionPlanLocked")
data class ExecutionPlanLockedEvent(
    val sessionId: SessionId,
    val planArtifactId: ArtifactId,
    val workflowId: String,
    val stageIds: List<String>,
    val startStageId: String,
) : EventPayload

/**
 * The architect's execution_plan could not become the phase-2 graph: content missing,
 * compile/validation failure, or operator rejection at the plan-review gate. Terminal for
 * the freestyle run unless re-planned; recorded so replay explains why phase 2 never ran.
 */
@Serializable
@SerialName("ExecutionPlanRejected")
data class ExecutionPlanRejectedEvent(
    val sessionId: SessionId,
    val reason: String,
    /** "compile" | "missing_content" | "operator" */
    val source: String,
) : EventPayload

/**
 * Environment observation (invariant #9): the outcome of compiling a stage's produced execution_plan
 * as a post-stage gate. Recorded at the moment the architect stage completes so replay reads the
 * recorded verdict and never re-compiles. A non-null [error] fails the producing stage retryably,
 * feeding the compiler message back so the architect self-corrects via the retry-feedback loop
 * (rather than parking the session in ACTIVE forever, as a post-planning compile failure did).
 */
@Serializable
@SerialName("PlanCompileChecked")
data class PlanCompileCheckedEvent(
    val sessionId: SessionId,
    val stageId: StageId,
    val passed: Boolean,
    /** Compiler error when [passed] is false; null on success. Kept bounded. */
    val error: String? = null,
) : EventPayload

/**
 * Capability-gap detector finding (Vikunja #30 part 1): a stage's declared intent implies a tool
 * capability ([impliedCapability]) it was not granted, per the deterministic verb map in
 * [com.correx.infrastructure.workflow.CapabilityGapDetector]. [evidence] is the triggering phrase
 * found in the stage brief.
 *
 * Advisory only — recorded alongside the plan-compile gate's other findings but does NOT fail the
 * gate and does NOT grant the tool itself (invariants #3 LLM-proposes/core-decides, #4
 * policy-absolute, #5 tiers-declared-not-inferred). A future reflection rung (part 2, an LLM
 * "are you sure?" pass) consumes these events; this ticket stops at detection + recording.
 */
@Serializable
@SerialName("CapabilityGapDetected")
data class CapabilityGapDetectedEvent(
    val sessionId: SessionId,
    val stageId: StageId,
    val impliedCapability: String,
    val evidence: String,
    val timestampMs: Long,
) : EventPayload

/** Verdict of the deterministic plan-grounding phase (design 2026-07-15 seam 1). */
enum class PlanGroundingVerdict { PASS, RETURN_TO_ARCHITECT, CLARIFICATION_REQUIRED }

/**
 * The compiled plan was evaluated against recorded workspace facts BEFORE lock (design 2026-07-15
 * §"Seam 1: plan grounding before lock"). Deterministic, pure over the plan graph plus the session's
 * recorded [RepoMapComputedEvent] and [ProjectProfileBoundEvent] — no inference, no live FS read, so
 * replay reads the recorded verdict back (invariants #8/#9). A non-[PlanGroundingVerdict.PASS] verdict
 * blocks the lock and returns compact [findings] to the architect, so a doomed scaffold fails at stage
 * 0 rather than after downstream files accumulate.
 *
 * v1 grounds build prerequisites: a stage that will run a build/typecheck/test command against a
 * prerequisite (build manifest) that neither exists in the recorded repo map nor is produced by any
 * plan stage is ungrounded. Path/symbol-reference grounding (spec rows 2/3) needs a grounding-grade
 * symbol index and is deferred — those assumptions resolve to `unknown`, never `present`.
 *
 * [stateKey] pins the workspace snapshot (repo-map hash) the grounding was evaluated against.
 */
@Serializable
@SerialName("PlanGroundingEvaluated")
data class PlanGroundingEvaluatedEvent(
    val sessionId: SessionId,
    val planId: String,
    val stateKey: String,
    val verdict: PlanGroundingVerdict,
    val findings: List<String> = emptyList(),
) : EventPayload

/** Outcome of the bounded LLM "are you sure?" reflection pass over a [CapabilityGapDetectedEvent]. */
enum class CapabilityGapVerdict { RESOLVED, NEEDS_TOOL }

/**
 * Vikunja #30 part 2: records the single-pass LLM reflection consulted on a capability gap before
 * escalating to the operator. RESOLVED means the model self-corrected onto an already-granted
 * capability ([detail] carries its revised intent, advisory only — never mutates the locked plan).
 * NEEDS_TOOL means the model insists it genuinely needs [impliedCapability] ([detail] carries its
 * justification) — this is the only case that should reach the human approval seam; the tool is
 * never auto-granted (invariants #4/#5).
 *
 * The reflection itself is nondeterministic (LLM-backed, invariant #9): recorded here so replay
 * reads this event back and never re-invokes inference (invariant #8).
 */
@Serializable
@SerialName("CapabilityGapReflected")
data class CapabilityGapReflectedEvent(
    val sessionId: SessionId,
    val stageId: StageId,
    val impliedCapability: String,
    val verdict: CapabilityGapVerdict,
    val detail: String,
    val timestampMs: Long,
) : EventPayload
