//! SQL fragments for JSON row projections in gateway fetch paths.

/// PostgreSQL caps function arguments at 100; `jsonb_build_object` uses two per key/value pair.
const MAX_JSONB_BUILD_OBJECT_PAIRS: usize = 50;

/// SQL expression that builds one JSON object from pair fragments (key literal + column ref each).
///
/// Each `pairs` entry must be one comma-separated fragment such as `"'id', t.id"`.
/// Callers must pass a non-empty slice; an empty slice yields `jsonb_build_object()` as a safe fallback.
pub fn merge_jsonb_build_object_expr(pairs: &[String]) -> String {
    if pairs.is_empty() {
        return "jsonb_build_object()".to_string();
    }
    let chunks: Vec<String> = pairs
        .chunks(MAX_JSONB_BUILD_OBJECT_PAIRS)
        .map(|chunk| format!("jsonb_build_object({})", chunk.join(", ")))
        .collect();
    match chunks.len() {
        1 => chunks[0].clone(),
        _ => format!("({})", chunks.join(" || ")),
    }
}

#[cfg(test)]
mod tests {
    use super::merge_jsonb_build_object_expr;

    fn sample_pairs(n: usize) -> Vec<String> {
        (0..n).map(|i| format!("'c{i}', t.c{i}")).collect()
    }

    #[test]
    fn single_chunk_no_merge_operator() {
        let pairs = sample_pairs(50);
        let expr = merge_jsonb_build_object_expr(&pairs);
        assert!(expr.starts_with("jsonb_build_object("));
        assert!(!expr.contains("||"));
        assert_eq!(expr.matches("jsonb_build_object").count(), 1);
    }

    #[test]
    fn fifty_one_pairs_merge_two_objects() {
        let pairs = sample_pairs(51);
        let expr = merge_jsonb_build_object_expr(&pairs);
        assert!(expr.starts_with('('));
        assert!(expr.contains("||"));
        assert_eq!(expr.matches("jsonb_build_object").count(), 2);
    }

    #[test]
    fn one_pair_single_call() {
        let pairs = sample_pairs(1);
        let expr = merge_jsonb_build_object_expr(&pairs);
        assert_eq!(expr, "jsonb_build_object('c0', t.c0)");
    }
}
