package com.correx.apps.server.protocol

import com.correx.apps.server.health.HealthReport
import com.correx.apps.server.metrics.MetricsReport
import com.correx.core.approvals.Tier
import com.correx.core.events.events.ClarificationQuestion
import com.correx.core.events.events.ProposedWorkflow
import com.correx.core.events.types.ApprovalRequestId
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.ClarificationRequestId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
sealed interface ServerMessage {
    /** Global monotonic sequence cursor. Non-null for event-derived messages. */
    val sequence: Long?

    /** Per-session monotonic sequence cursor. Non-null for event-derived messages. */
    val sessionSequence: Long?

    /** Marker for event-derived messages — cursors must be non-null. */
    @Serializable
    sealed interface SessionMessage : ServerMessage {
        override val sequence: Long
        override val sessionSequence: Long
    }

    /** Marker for control / infra messages — cursors are always null. */
    @Serializable
    sealed interface NonEventMessage : ServerMessage {
        override val sequence: Long? get() = null
        override val sessionSequence: Long? get() = null
    }

    // -- Session lifecycle --

    /**
     * Event-derived session announcement: carries the session's [workflowId] so the client
     * can name it. Derived from WorkflowStartedEvent / ChatSessionStartedEvent. The client
     * applies it through the same auto-vivify path as any other event (no special-casing).
     */
    @Serializable
    @SerialName("session.announced")
    data class SessionAnnounced(
        val sessionId: SessionId,
        val workflowId: String,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    /** A human-readable title derived from the session's intent, from SessionNamedEvent — the
     *  client shows this in place of the opaque workflow id. */
    @Serializable
    @SerialName("session.renamed")
    data class SessionRenamed(
        val sessionId: SessionId,
        val name: String,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    /** The workspace root a session was bound to, from SessionWorkspaceBoundEvent — shown as cwd. */
    @Serializable
    @SerialName("session.workspace_bound")
    data class SessionWorkspaceBound(
        val sessionId: SessionId,
        val workspaceRoot: String,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    /**
     * Event-derived conversation turn (USER or ROUTER), from ChatTurnEvent. The client
     * rebuilds the chat transcript from these, ordered by [sessionSequence].
     */
    @Serializable
    @SerialName("chat.turn")
    data class ChatTurn(
        val sessionId: SessionId,
        val turnId: String,
        val role: String,
        val content: String,
        val latencyMs: Long? = null,
        val totalTokens: Int? = null,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("session.paused")
    data class SessionPaused(
        val sessionId: SessionId,
        val reason: PauseReason,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("session.resumed")
    data class SessionResumed(
        val sessionId: SessionId,
        val stageId: StageId? = null,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("session.completed")
    data class SessionCompleted(
        val sessionId: SessionId,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("session.failed")
    data class SessionFailed(
        val sessionId: SessionId,
        val reason: String,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    // -- State snapshot --

    @Serializable
    @SerialName("session_snapshot")
    data class SessionSnapshot(
        val sessionId: SessionId,
        val workflowId: String,
        val state: SessionStateDto,
        val pendingApprovals: List<ApprovalDto>,
        val tools: List<ToolRecordDto> = emptyList(),
        val recentEvents: List<EventEntryDto> = emptyList(),
        // Raw source for the OUTPUT transcript (chat / narration / thinking / tool rows); the client
        // folds these into its transcript on reopen so a restart restores the full conversation.
        val transcript: List<TranscriptRowDto> = emptyList(),
        // Last stage's inference output, resolved from CAS, so a reopened session restores its
        // output panel instead of showing blank until new activity arrives.
        val lastOutput: String = "",
        val lastResponse: String = "",
        // The session's bound workspace cwd, so a reopened session restores its workspace label
        // (otherwise blank until a new workspace_bound, which only fires on a fresh start).
        val workspaceRoot: String = "",
        val lastSequence: Long,
        val lastSessionSequence: Long,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    // -- Stage lifecycle --

    @Serializable
    @SerialName("stage.started")
    data class StageStarted(
        val sessionId: SessionId,
        val stageId: StageId,
        val occurredAt: Long,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("stage.completed")
    data class StageCompleted(
        val sessionId: SessionId,
        val stageId: StageId,
        val occurredAt: Long,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("stage.failed")
    data class StageFailed(
        val sessionId: SessionId,
        val stageId: StageId,
        val reason: String,
        val occurredAt: Long,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    // -- Inference --

    @Serializable
    @SerialName("inference.started")
    data class InferenceStarted(
        val sessionId: SessionId,
        val stageId: StageId,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("inference.completed")
    data class InferenceCompleted(
        val sessionId: SessionId,
        val stageId: StageId,
        val outputSummary: String,
        val responseText: String = "",
        // The model's reasoning/thinking trace (llama `reasoning_content`), when the provider
        // captured one. Sent so the client can offer a collapsed, opt-in "thinking" view; empty
        // for models that don't emit a separate reasoning channel.
        val reasoning: String = "",
        val occurredAt: Long,
        val totalTokens: Int? = null,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("inference.timed_out")
    data class InferenceTimedOut(
        val sessionId: SessionId,
        val stageId: StageId,
        val elapsedMs: Long,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("inference.failed")
    data class InferenceFailed(
        val sessionId: SessionId,
        val stageId: StageId,
        val reason: String,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("inference.retry")
    data class RetryAttempted(
        val sessionId: SessionId,
        val stageId: StageId,
        val attemptNumber: Int,
        val maxAttempts: Int,
        val failureReason: String,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    // -- Tool execution --

    @Serializable
    @SerialName("tool.started")
    data class ToolStarted(
        val sessionId: SessionId,
        val toolName: String,
        val tier: Tier,
        // Pretty-formatted "key=value" call arguments (path=…, command="…", query=…) so the client
        // can show what the tool was actually invoked with, not just its name. Capped + truncated
        // upstream (see prettyToolParams) to keep the stream small.
        val params: List<String> = emptyList(),
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("tool.completed")
    data class ToolCompleted(
        val sessionId: SessionId,
        val toolName: String,
        val outputSummary: String,
        val occurredAt: Long,
        val diff: String? = null,
        val affectedEntities: List<String> = emptyList(),
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("tool.failed")
    data class ToolFailed(
        val sessionId: SessionId,
        val toolName: String,
        val reason: String,
        val occurredAt: Long,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("tool.rejected")
    data class ToolRejected(
        val sessionId: SessionId,
        val toolName: String,
        val reason: String,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("tool.assessed")
    data class ToolAssessed(
        val sessionId: SessionId,
        val stageId: StageId,
        val toolName: String,
        val disposition: String,
        val issues: List<AssessedIssueDto>,
        val occurredAt: Long,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("review.findings")
    data class ReviewFindings(
        val sessionId: SessionId,
        val stageId: StageId,
        val verdict: String,
        val findings: List<ReviewFindingDto>,
        val blocked: Boolean,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    // -- Approval --

    @Serializable
    @SerialName("approval.required")
    data class ApprovalRequired(
        val sessionId: SessionId,
        val requestId: ApprovalRequestId,
        val tier: Tier,
        val riskSummary: RiskSummaryDto,
        val toolName: String?,
        val preview: String?,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("approval.resolved")
    data class ApprovalResolved(
        val sessionId: SessionId,
        val requestId: ApprovalRequestId,
        val outcome: String,
        val reason: String?,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    /**
     * A stage raised open questions (from a ClarificationRequestedEvent) and the run is parked until
     * the operator answers. The client renders [questions] as an interactive form and replies with a
     * ClientMessage.ClarificationResponse carrying [requestId] and [stageId].
     */
    @Serializable
    @SerialName("clarification.required")
    data class ClarificationRequired(
        val sessionId: SessionId,
        val requestId: ClarificationRequestId,
        val stageId: StageId,
        val questions: List<ClarificationQuestion>,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    /**
     * The router triaged a chat request to one or more candidate workflows (from a
     * WorkflowProposedEvent). The client renders [candidates] as a choice panel; picking one launches
     * that workflow via ClientMessage.StartSession seeded with [originalRequest], while the panel's
     * manual-answer slot continues the conversation via ClientMessage.ChatInput. Non-authoritative:
     * nothing runs until the operator picks.
     */
    @Serializable
    @SerialName("workflow.proposed")
    data class WorkflowProposed(
        val sessionId: SessionId,
        val proposalId: String,
        val prompt: String,
        val candidates: List<ProposedWorkflow>,
        val originalRequest: String,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    // -- System / infra --

    @Serializable
    @SerialName("stage.tool_manifest")
    data class StageToolManifest(
        val sessionId: SessionId,
        val workflowId: String,
        val stages: List<StageToolDecl>,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    @Serializable
    @SerialName("provider.status_changed")
    data class ProviderStatusChanged(
        val providerId: String,
        val status: ProviderHealthDto,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * The resident local model changed (load or unload). Derived from `ModelLoadedEvent` /
     * `ModelUnloadedEvent`; a global control message (no session cursors). `loaded = false`
     * signals the model was unloaded.
     */
    @Serializable
    @SerialName("model.changed")
    data class ModelChanged(
        val modelId: String,
        val providerId: String,
        val loaded: Boolean,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * The configured local models and which one is currently resident. Sent once in the initial
     * snapshot when correx manages the model process, so clients can offer a swap picker.
     */
    @Serializable
    @SerialName("model.list")
    data class ModelList(
        val models: List<String>,
        val current: String?,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * Live resource gauge pushed periodically on the global stream (not event-derived). All fields
     * are nullable: GPU fields are null on a non-NVIDIA host, `processRssMb` is null when no model
     * is resident.
     */
    @Serializable
    @SerialName("resource.status")
    data class ResourceStatus(
        val gpuMemoryUsedMb: Long?,
        val gpuMemoryTotalMb: Long?,
        val gpuUtilizationPct: Int?,
        val processRssMb: Long?,
        val systemRamUsedMb: Long? = null,
        val systemRamTotalMb: Long? = null,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    @Serializable
    @SerialName("protocol_error")
    data class ProtocolError(
        val message: String,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    // -- Artifacts --

    @Serializable
    @SerialName("artifact.created")
    data class ArtifactCreated(
        val sessionId: SessionId,
        val stageId: StageId,
        val artifactId: ArtifactId,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("artifact.validated")
    data class ArtifactValidated(
        val sessionId: SessionId,
        val stageId: StageId,
        val artifactId: ArtifactId,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    /** A freestyle execution plan was locked; phase 2 runs [stageIds] as workflow [workflowId]. */
    @Serializable
    @SerialName("plan.locked")
    data class PlanLocked(
        val sessionId: SessionId,
        val workflowId: String,
        val stageIds: List<String>,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    /**
     * Full artifact listing for a session, returned in response to a ListArtifacts request.
     * Not event-derived (it is a snapshot read), so cursors are null.
     */
    @Serializable
    @SerialName("artifact.list")
    data class ArtifactList(
        val sessionId: SessionId,
        val artifacts: List<ArtifactSummaryDto>,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * The session workspace's file paths (reply to ListFiles), for the TUI `@` file-ref picker.
     * A snapshot directory walk, not event-derived, so cursors are null.
     */
    @Serializable
    @SerialName("file.list")
    data class FileList(
        val sessionId: SessionId,
        val paths: List<String>,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * The cross-session idea board (reply to ListIdeas / DiscardIdea). Not event-derived (a
     * snapshot read folded from the whole event log), so cursors are null.
     */
    @Serializable
    @SerialName("idea.list")
    data class IdeaList(
        val ideas: List<IdeaDto>,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * The active cross-session (PROJECT/GLOBAL) grants (reply to ListGrants / CreateGrant /
     * RevokeGrant). Not event-derived (a snapshot folded from the grant ledger), so cursors are null.
     */
    @Serializable
    @SerialName("grant.list")
    data class GrantList(
        val grants: List<GrantDto>,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * A session's derived metrics, returned in response to a GetSessionStats request. Not
     * event-derived (it is a projection-replay snapshot), so cursors are null. [stats] is the same
     * [MetricsReport] the REST `/sessions/{id}/metrics` endpoint serves — one definition, two wires.
     */
    @Serializable
    @SerialName("session.stats")
    data class SessionStats(
        val sessionId: SessionId,
        val stats: MetricsReport,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * The system health-check report, returned in response to a GetHealthChecks request. Not
     * event-derived (a projection-replay snapshot read), so cursors are null. [health] is the same
     * [HealthReport] the REST `GET /health/checks` endpoint serves — one definition, two wires.
     * When health monitoring is disabled ([health] holds an empty/disabled report) the message is
     * still sent; the client renders a "health monitoring disabled" fallback instead of going blank.
     */
    @Serializable
    @SerialName("health.checks")
    data class HealthChecks(
        val health: HealthReport,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /**
     * The current editable config (reply to GetConfig / UpdateConfig). [restartRequired] lists the
     * keys in a just-applied patch that need a server restart to take effect; [error] is set (and
     * fields hold the unchanged current values) when an update was rejected.
     */
    @Serializable
    @SerialName("config.snapshot")
    data class ConfigSnapshot(
        val fields: List<ConfigFieldDto>,
        val restartRequired: List<String> = emptyList(),
        val error: String? = null,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /** The ProjectProfile bound to a session's workspace (reply to GetProjectProfile / UpdateProjectProfile). */
    @Serializable
    @SerialName("project_profile.snapshot")
    data class ProjectProfileSnapshot(
        val sessionId: SessionId,
        val about: String,
        val conventions: List<String>,
        val commands: Map<String, String>,
        val error: String? = null,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    /** The operator's OperatorProfile (reply to GetOperatorProfile / UpdateOperatorProfile). */
    @Serializable
    @SerialName("operator_profile.snapshot")
    data class OperatorProfileSnapshot(
        val about: String,
        val approvalMode: String,
        val preferredModels: List<String>,
        val conventions: List<String>,
        val proposedAdaptation: String? = null,
        val error: String? = null,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    // -- Router --

    @Serializable
    @SerialName("router.narration")
    data class Narration(
        val sessionId: SessionId,
        val content: String,
        val stageId: String? = null,
        val latencyMs: Long? = null,
        val totalTokens: Int? = null,
        override val sequence: Long,
        override val sessionSequence: Long,
    ) : ServerMessage, SessionMessage

    @Serializable
    @SerialName("router.response")
    data class TalkieResponseMessage(
        val sessionId: SessionId,
        val content: String,
        val steeringEmitted: Boolean,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage

    // -- Snapshot phase markers --

    /** Marker indicating the snapshot phase has completed. No cursor fields. */
    @Serializable
    @SerialName("snapshot_complete")
    data object SnapshotComplete : ServerMessage, NonEventMessage {
        override val sequence: Long? get() = null
        override val sessionSequence: Long? get() = null
    }

    // -- Workflow discovery --

    @Serializable
    @SerialName("workflow.list")
    data class WorkflowList(
        val workflows: List<WorkflowDto>,
        override val sequence: Long? = null,
        override val sessionSequence: Long? = null,
    ) : ServerMessage, NonEventMessage
}
