package com.correx.apps.server.protocol

import com.correx.core.approvals.GrantScope
import com.correx.core.approvals.Tier
import com.correx.core.events.types.ApprovalRequestId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.router.ChatMode
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable

@Serializable
enum class ApprovalDecision {
    APPROVE,
    REJECT,
}

@Serializable
enum class GrantScopeDto {
    SESSION,
    STAGE,
}

@Serializable
sealed class ClientMessage {
    @Serializable
    data class StartSession(
        val workflowId: String,
        val config: SessionConfigDto?,
        // Optional freeform request for intent-driven pipelines. Seeded into the decision
        // journal so every stage sees it. Fixed-task workflows (e.g. healthcheck) omit it.
        val input: String? = null,
    ) : ClientMessage()

    @Serializable
    data class ResumeSession(val sessionId: SessionId) : ClientMessage()

    @Serializable
    data class CancelSession(val sessionId: SessionId) : ClientMessage()

    @Serializable
    data class ApprovalResponse(
        val requestId: ApprovalRequestId,
        val decision: ApprovalDecision,
        val steeringNote: String?,
    ) : ClientMessage()

    @Serializable
    data class CreateGrant(
        val sessionId: SessionId,
        val scope: GrantScopeDto,
        val stageId: StageId? = null,
        val permittedTiers: List<Tier>,
        val reason: String,
        val expiresAt: Instant? = null,
        // Required for SESSION scope: binds this grant to a specific tool name.
        val toolName: String? = null,
    ) : ClientMessage()

    @Serializable
    data class Ping(val timestamp: Long) : ClientMessage()

    @Serializable
    data class StartChatSession(
        val sessionId: SessionId,
        val text: String,
    ) : ClientMessage()

    @Serializable
    data class ChatInput(val sessionId: SessionId, val text: String, val mode: ChatMode) : ClientMessage()

    /** Operator request to make [modelId] resident and pin it (beats per-stage selection). */
    @Serializable
    data class SwapModel(val modelId: String) : ClientMessage()

    /** Operator request to clear the manual-swap pin; per-stage selection resumes. */
    @Serializable
    data object ClearModelPin : ClientMessage()

    /**
     * First frame sent by the client on every (re)connect. Carries the client's
     * working directory so the server can bind a workspace for subsequent sessions.
     * Handled before any session starts; ignored (falls back to boot default) if absent.
     */
    @Serializable
    data class Hello(val workingDir: String) : ClientMessage()
}
