package com.correx.infrastructure.inference.openai

import com.correx.core.inference.ToolCallRequest
import com.correx.core.inference.ToolDefinition
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
 * Wire models for the OpenAI-compatible `/chat/completions` endpoint (NVIDIA NIM, OpenAI,
 * vLLM, etc.). Unlike the llama.cpp models there is no `grammar` field — structured output
 * on these backends is `response_format`/tools, not GBNF. JSON artifacts rely on the
 * orchestrator's validate-after-retry path (invariant #7).
 */
@Serializable
data class OpenAiChatCompletionRequest(
    val model: String,
    val messages: List<OpenAiChatMessage>,
    val temperature: Double,
    @SerialName("top_p") val topP: Double,
    @SerialName("max_tokens") val maxTokens: Int,
    @SerialName("stop") val stopSequences: List<String>? = null,
    val seed: Long? = null,
    // Non-standard OpenAI params accepted by local backends (vLLM, llama.cpp --api). EncodeDefault.NEVER
    // omits them when unset so a strict endpoint never sees an unknown key unless the operator opts in.
    @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("top_k") val topK: Int? = null,
    @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("min_p") val minP: Double? = null,
    @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("repeat_penalty") val repeatPenalty: Double? = null,
    val stream: Boolean = false,
    val tools: List<ToolDefinition>? = null,
)

@Serializable
data class OpenAiChatMessage(
    val role: String,
    val content: String? = null,
    @SerialName("reasoning_content") val reasoningContent: String? = null,
    @SerialName("tool_calls") val toolCalls: List<ToolCallRequest> = emptyList(),
    @SerialName("tool_call_id") val toolCallId: String? = null,
)

@Serializable
data class OpenAiChatCompletionResponse(
    val id: String? = null,
    val choices: List<OpenAiChoice> = emptyList(),
    val usage: OpenAiUsage? = null,
)

@Serializable
data class OpenAiChoice(
    val message: OpenAiChatMessage,
    @SerialName("finish_reason") val finishReason: String? = null,
)

@Serializable
data class OpenAiUsage(
    @SerialName("prompt_tokens") val promptTokens: Int = 0,
    @SerialName("completion_tokens") val completionTokens: Int = 0,
    @SerialName("total_tokens") val totalTokens: Int = 0,
)
