package com.correx.apps.server.protocol

import com.correx.core.approvals.GrantScope
import com.correx.core.approvals.Tier
import com.correx.core.events.events.ClarificationAnswer
import com.correx.core.events.types.ApprovalRequestId
import com.correx.core.events.types.ClarificationRequestId
import com.correx.core.events.types.GrantId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.talkie.ChatMode
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable

@Serializable
enum class ApprovalDecision {
    APPROVE,
    REJECT,
}

@Serializable
enum class GrantScopeDto {
    SESSION,
    STAGE,
    // Cross-session scopes (BACKLOG §grants). PROJECT auto-approves the bound tool for any session
    // on the same workspace; GLOBAL for every session on this machine. Both are tool-bound and live
    // in the cross-session grant ledger rather than a single session's stream.
    PROJECT,
    GLOBAL,
}

@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()

    /** Operator request for the artifacts produced by a session, with their content. */
    @Serializable
    data class ListArtifacts(val sessionId: SessionId) : ClientMessage()

    /** Operator request for the session workspace's file paths (the TUI `@` file-ref picker). */
    @Serializable
    data class ListFiles(val sessionId: SessionId) : ClientMessage()

    /** Operator request for a session's derived metrics (replied to with a SessionStats). */
    @Serializable
    data class GetSessionStats(val sessionId: SessionId) : ClientMessage()

    /** Operator request for the system health checks (replied to with a HealthChecks). */
    @Serializable
    data object GetHealthChecks : ClientMessage()

    /** Operator request for the cross-session idea board (replied to with an IdeaList). */
    @Serializable
    data object ListIdeas : ClientMessage()

    /** Operator request to remove an idea from the board (tombstones it; reply is a fresh IdeaList). */
    @Serializable
    data class DiscardIdea(val ideaId: String) : ClientMessage()

    /**
     * Operator request to promote an idea into the bound session's curated project profile (added as
     * a `conventions` entry in `<workspaceRoot>/.correx/project.toml`). Tombstones the idea like a
     * discard; reply is a fresh IdeaList.
     */
    @Serializable
    data class PromoteIdea(val ideaId: String) : ClientMessage()

    /** Operator request for the current editable config (replied to with a ConfigSnapshot). */
    @Serializable
    data object GetConfig : ClientMessage()

    /** Operator request to apply a config patch (key → string value) and persist it. */
    @Serializable
    data class UpdateConfig(val patch: Map<String, String>) : ClientMessage()

    /** Operator request for the ProjectProfile bound to [sessionId]'s workspace. */
    @Serializable
    data class GetProjectProfile(val sessionId: SessionId) : ClientMessage()

    /** Operator request to overwrite the ProjectProfile at [sessionId]'s workspace root. */
    @Serializable
    data class UpdateProjectProfile(
        val sessionId: SessionId,
        val about: String,
        val conventions: List<String>,
        val commands: Map<String, String>,
    ) : ClientMessage()

    /** Operator request for the current OperatorProfile (home-scoped, not session-bound). */
    @Serializable
    data object GetOperatorProfile : ClientMessage()

    /** Operator request to overwrite the OperatorProfile. */
    @Serializable
    data class UpdateOperatorProfile(
        val about: String,
        val approvalMode: String,
        val preferredModels: List<String>,
        val conventions: List<String>,
    ) : ClientMessage()

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

    /** The operator's answers to a stage's open questions (see ServerMessage.ClarificationRequired). */
    @Serializable
    data class ClarificationResponse(
        val sessionId: SessionId,
        val stageId: StageId,
        val requestId: ClarificationRequestId,
        val answers: List<ClarificationAnswer>,
    ) : ClientMessage()

    /**
     * Free-form operator steering for a session that is mid-stage, independent of any approval
     * gate. Unlike [ApprovalResponse.steeringNote] (only deliverable while parked on an approval)
     * this can be sent at any time; the orchestrator records it and folds it into the stage's next
     * context turn as advisory input (never a state mutation — see SessionOrchestrator.submitSteering).
     */
    @Serializable
    data class SteerSession(
        val sessionId: SessionId,
        val text: 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/PROJECT/GLOBAL scope: binds this grant to a specific tool name.
        // The projectId for a PROJECT grant is derived server-side from the session's bound
        // workspace, never trusted from the client.
        val toolName: String? = null,
    ) : ClientMessage()

    /** Operator request to revoke a standing (PROJECT/GLOBAL) grant by id; reply is a fresh GrantList. */
    @Serializable
    data class RevokeGrant(val grantId: GrantId) : ClientMessage()

    /** Operator request for the active cross-session grants (replied to with a GrantList). */
    @Serializable
    data object ListGrants : 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()
}
