package com.correx.apps.server.protocol

import com.correx.core.events.risk.RiskSignal
import com.correx.core.events.risk.RiskSummary
import kotlinx.serialization.Serializable

@Serializable
data class RiskSummaryDto(
    val level: String,
    val factors: List<String>,
    val recommendedAction: String,
    val rationale: List<String> = emptyList(),
)

@Serializable
data class AssessedIssueDto(
    val code: String,
    val message: String,
    val severity: String,
)

@Serializable
data class ReviewFindingDto(
    val severity: String,
    val confidence: Double,
    val category: String,
    val target: String,
    val message: String,
    val suggestedFix: String? = null,
    val correctness: Boolean = false,
)

@Serializable
data class ProviderHealthDto(
    val providerId: String,
    val status: String,
    val loadPercent: Int?,
)

@Serializable
data class SessionConfigDto(
    val timeoutMs: Long?,
    val retryPolicy: String?,
)

@Serializable
data class SessionStateDto(
    val status: String,
    val currentStageId: String?,
    val pauseReason: String?,
)

@Serializable
data class ApprovalDto(
    val requestId: String,
    val tier: String,
    val toolName: String? = null,
    val preview: String? = null,
)

@Serializable
enum class PauseReason {
    APPROVAL_PENDING,
    CLARIFICATION_PENDING,
    ABANDONED_STALE,
    USER_REQUESTED,
}

@Serializable
data class StageToolDecl(
    val stageId: String,
    val tools: List<ToolDecl>,
    val tokenBudget: Int? = null,
)

@Serializable
data class ToolDecl(
    val name: String,
    val tier: Int,
)

@Serializable
data class EventEntryDto(
    val timestamp: Long,
    val type: String,
    val detail: String,
)

/**
 * One raw OUTPUT-transcript source row for a reopened session. The server ships the *content*
 * (chat text, narration, reasoning, tool diff/params/summary) with no formatting; the client folds
 * these into its transcript rows in onSnapshot reusing its own diff/param/action formatters, so the
 * row-rendering logic stays in one place (the Go client) and a restart restores the full transcript
 * instead of only the thin EVENTS markers. [kind] is chat | narration | thinking | tool.
 */
@Serializable
data class TranscriptRowDto(
    val kind: String,
    val role: String = "",
    val content: String = "",
    val toolName: String = "",
    val status: String = "",
    val diff: String? = null,
    val params: List<String> = emptyList(),
    val summary: String = "",
    val affectedEntities: List<String> = emptyList(),
    val latencyMs: Long? = null,
    val totalTokens: Int? = null,
)

@Serializable
data class ToolRecordDto(
    val name: String,
    val tier: Int,
    val status: String,
    val diff: String? = null,
)

@Serializable
data class WorkflowDto(
    val workflowId: String,
    val description: String,
)

/**
 * One editable config field for the TUI editor. [type] is an [com.correx.core.config.ConfigFieldType]
 * name; [value] is the current rendered value; [options] is non-empty only for ENUM fields;
 * [restartRequired] flags fields that persist now but only take effect after a server restart.
 */
@Serializable
data class ConfigFieldDto(
    val key: String,
    val type: String,
    val value: String,
    val options: List<String> = emptyList(),
    val restartRequired: Boolean = false,
)

/**
 * A single artifact in a session listing. [content] is the UTF-8 decoding of the stored
 * bytes, or null when no content was stored / the bytes are not valid UTF-8 (binary).
 */
@Serializable
data class ArtifactSummaryDto(
    val artifactId: String,
    val stageId: String,
    val schemaVersion: Int,
    val phase: String,
    val content: String?,
)

/** One idea on the cross-session board (a captured, not-yet-discarded note). */
@Serializable
data class IdeaDto(
    val ideaId: String,
    val text: String,
    val capturedAtMs: Long,
)

/**
 * One standing cross-session grant for the TUI grants viewer. [scope] is "PROJECT" or "GLOBAL";
 * [tiers] are the tier names this grant auto-approves; [projectId] is the workspace path a PROJECT
 * grant is bound to (null for GLOBAL); [expiresAtMs] is the epoch-ms expiry, or null if it never
 * expires.
 */
@Serializable
data class GrantDto(
    val grantId: String,
    val scope: String,
    val toolName: String?,
    val projectId: String?,
    val tiers: List<String>,
    val reason: String,
    val expiresAtMs: Long?,
)

internal fun RiskSummary.toDto(): RiskSummaryDto = RiskSummaryDto(
    level = level.name,
    factors = signals.map { signal ->
        when (signal) {
            is RiskSignal.CycleWithoutExit -> "Cycle without exit: ${signal.cycleId}"
            is RiskSignal.RepeatedFailure -> "Repeated failure: ${signal.reason} (${signal.count}×)"
            is RiskSignal.ValidationErrors -> "Validation errors: ${signal.errorCount}"
            is RiskSignal.InferenceTimeout -> "Inference timeout: ${signal.elapsedMs}ms"
        }
    },
    recommendedAction = recommendedAction.name,
    rationale = rationale,
)
