//! LLM provider implementations (Anthropic, OpenAI, Mistral, Ollama, Google).
//!
//! Each module is a self-contained `LlmProvider` impl. A crate-private
//! `retry` helper holds the retry loop so each provider only has to
//! describe its own request and response shapes.

pub mod anthropic;
pub mod google;
pub mod mistral;
pub mod ollama;
pub mod openai;
pub(crate) mod retry;

use serde_json::Value;

use crate::eval::error::ProviderError;

/// Extract an embedding vector from a provider response at the given JSON
/// Pointer. Shared so the OpenAI/Mistral/Ollama/Google embed paths read the
/// `[f32]` array the same way and report a uniform malformed-response error
/// when the field is absent or not a numeric array.
pub(crate) fn parse_embedding(response: &Value, pointer: &str) -> Result<Vec<f32>, ProviderError> {
    let array = response
        .pointer(pointer)
        .and_then(Value::as_array)
        .ok_or_else(|| {
            ProviderError::MalformedResponse(format!("embedding response missing `{pointer}`"))
        })?;
    let mut out = Vec::with_capacity(array.len());
    for value in array {
        let n = value.as_f64().ok_or_else(|| {
            ProviderError::MalformedResponse("embedding vector held a non-numeric element".into())
        })?;
        out.push(n as f32);
    }
    if out.is_empty() {
        return Err(ProviderError::MalformedResponse(
            "embedding vector was empty".into(),
        ));
    }
    Ok(out)
}

pub use anthropic::{AnthropicConfig, AnthropicProvider};
pub use google::{GoogleConfig, GoogleProvider};
pub use mistral::{MistralConfig, MistralProvider};
pub use ollama::{OllamaConfig, OllamaProvider};
pub use openai::{OpenAiConfig, OpenAiProvider};

#[cfg(test)]
mod tests {
    use super::parse_embedding;
    use crate::eval::error::ProviderError;
    use serde_json::json;

    #[test]
    fn parse_embedding_reads_numeric_array_at_pointer() {
        let v = json!({ "data": [{ "embedding": [0.5, -1.0, 2.0] }] });
        let out = parse_embedding(&v, "/data/0/embedding").unwrap();
        assert_eq!(out, vec![0.5f32, -1.0, 2.0]);
    }

    #[test]
    fn parse_embedding_errors_when_field_missing() {
        let v = json!({ "data": [] });
        let err = parse_embedding(&v, "/data/0/embedding").unwrap_err();
        assert!(matches!(err, ProviderError::MalformedResponse(_)));
    }

    #[test]
    fn parse_embedding_errors_on_empty_vector() {
        let v = json!({ "embedding": [] });
        let err = parse_embedding(&v, "/embedding").unwrap_err();
        assert!(matches!(err, ProviderError::MalformedResponse(_)));
    }

    #[test]
    fn parse_embedding_errors_on_non_numeric_element() {
        let v = json!({ "embedding": [0.1, "nope"] });
        let err = parse_embedding(&v, "/embedding").unwrap_err();
        assert!(matches!(err, ProviderError::MalformedResponse(_)));
    }
}
