// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Provider-neutral decision questions and answers, separate from LLM messages.
//!
//! Enums use snake-case `type` tags and a `data` payload in serialized form.
//! Fields are public; providers and callers are responsible for valid values.

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{ModelId, Usage};

/// Answer probability on a `[0, 1]` scale.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Probability(pub f64);

/// Position in the request's rubric, including fractional positions.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ScoreValue(pub f64);

/// Provider confidence; its scale and meaning are provider-specific.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ProviderConfidence(pub f64);

/// Shared context evaluated against independent, named questions.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DecisionRequest {
    /// Optional until a target is selected.
    pub model: Option<ModelId>,
    /// Conversation, application state, or other material to evaluate.
    pub context: Value,
    /// Independent questions keyed by their IDs.
    pub questions: BTreeMap<String, DecisionQuestion>,
}

/// Instructions and the expected answer shape for one question.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DecisionQuestion {
    /// Structured or textual instructions shared with the provider.
    pub instructions: Value,
    /// Expected answer shape.
    pub kind: DecisionKind,
}

/// The answer shape and any options or ordered rubric levels.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum DecisionKind {
    /// A Boolean judgment or probability of true.
    Boolean {
        /// Meaning of a true answer, when needed.
        true_description: Option<Value>,
        /// Meaning of a false answer, when needed.
        false_description: Option<Value>,
    },
    /// Select one of the declared options.
    Choice {
        /// Nonempty options with unique IDs, preserving caller order.
        options: Vec<ChoiceOption>,
    },
    /// A position on an ordered rubric, not an arbitrary numeric measurement.
    Score {
        /// At least two levels, ordered low to high and indexed from zero.
        levels: Vec<Value>,
    },
}

/// An identified choice with an optional structured description.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ChoiceOption {
    /// Stable identifier used by choice answers and distributions.
    pub id: String,
    /// Meaning of this option, when its ID alone is insufficient.
    pub description: Option<Value>,
}

/// Answers keyed by the matching request's question IDs.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DecisionResponse {
    /// Provider-reported response identifier.
    pub id: Option<String>,
    /// Provider-reported model identifier.
    pub model: Option<ModelId>,
    /// Typed answers corresponding to the request's questions.
    pub answers: BTreeMap<String, DecisionAnswer>,
    /// Available token counts; absent counts remain unknown.
    #[serde(default)]
    pub usage: Usage,
}

/// An answer and separate, optional provider confidence.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DecisionAnswer {
    /// The estimate for the matching question.
    pub value: DecisionValue,
    /// Provider-specific confidence, distinct from answer probabilities.
    pub provider_confidence: Option<ProviderConfidence>,
}

/// A typed estimate; missing distributions remain unknown.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum DecisionValue {
    /// A Boolean judgment or probability, without an implicit threshold.
    Boolean(BooleanEstimate),
    /// One selected option with an optional complete distribution.
    Choice {
        /// Must name an option in the matching question.
        selected: String,
        /// Maps every declared option ID to its probability when available.
        probabilities: Option<BTreeMap<String, Probability>>,
    },
    /// A fractional position in the matching request's rubric.
    Score {
        /// Must lie in `0..=N-1` for the request's N levels.
        value: ScoreValue,
        /// Follows the request's level order. Retain that request to interpret it.
        probabilities: Option<Vec<Probability>>,
    },
}

/// Preserves Boolean-only answers without inventing probability or certainty.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum BooleanEstimate {
    /// A Boolean judgment with no probability supplied.
    Value(bool),
    /// Probability of true; algorithms choose their own thresholds.
    ProbabilityTrue(Probability),
}