uqa-api 0.1.11

Fluent QueryBuilder API
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

use super::SQLError;

pub(super) fn validate_retrieval_signals(
    name: &str,
    signals: &[&str],
    minimum: usize,
) -> Result<(), SQLError> {
    if signals.len() < minimum {
        return Err(SQLError::BadArity {
            name: name.to_string(),
            expected: format!(">={minimum} signals"),
            actual: signals.len(),
        });
    }
    if signals.iter().any(|signal| signal.trim().is_empty()) {
        return Err(SQLError::TypeMismatch(format!(
            "{name} signal expressions cannot be empty"
        )));
    }
    Ok(())
}

pub(super) fn validate_stage_count(count: usize) -> Result<(), SQLError> {
    if count == 0 {
        return Err(SQLError::BadArity {
            name: "staged_retrieval".into(),
            expected: ">=1 stage".into(),
            actual: 0,
        });
    }
    Ok(())
}

pub(super) fn validate_stage_cutoffs(
    cutoffs: impl IntoIterator<Item = usize>,
) -> Result<(), SQLError> {
    for cutoff in cutoffs {
        validate_positive_sql_usize("staged_retrieval top_k", cutoff)?;
    }
    Ok(())
}

pub(super) fn validate_vector_query(
    name: &str,
    field: &str,
    vector: &[f32],
    k: usize,
) -> Result<(), SQLError> {
    validate_field_name(name, field)?;
    if vector.is_empty() || vector.iter().any(|component| !component.is_finite()) {
        return Err(SQLError::TypeMismatch(format!(
            "{name} requires a non-empty finite query vector"
        )));
    }
    validate_positive_sql_usize(&format!("{name} k"), k)
}

pub(super) fn validate_field_name(name: &str, field: &str) -> Result<(), SQLError> {
    if field.trim().is_empty() {
        return Err(SQLError::TypeMismatch(format!(
            "{name} field name cannot be empty"
        )));
    }
    Ok(())
}

fn validate_positive_sql_usize(label: &str, value: usize) -> Result<(), SQLError> {
    if value == 0 || i64::try_from(value).is_err() {
        return Err(SQLError::TypeMismatch(format!(
            "{label} must be positive and fit in a SQL BIGINT, got {value}"
        )));
    }
    Ok(())
}

pub(super) fn validate_fusion_alpha(name: &str, alpha: f64) -> Result<(), SQLError> {
    if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
        return Err(SQLError::TypeMismatch(format!(
            "{name} alpha must be finite and in [0, 1], got {alpha:?}"
        )));
    }
    Ok(())
}

pub(super) fn validate_probability_threshold(name: &str, threshold: f64) -> Result<(), SQLError> {
    if !threshold.is_finite() || !(0.0..=1.0).contains(&threshold) {
        return Err(SQLError::TypeMismatch(format!(
            "{name} threshold must be finite and in [0, 1], got {threshold:?}"
        )));
    }
    Ok(())
}

pub(super) fn render_vector(query: &[f32]) -> String {
    query
        .iter()
        .map(f32::to_string)
        .collect::<Vec<_>>()
        .join(", ")
}