use std::sync::Arc;
use axum::extract::{Path, State};
use axum::http::HeaderMap;
use axum::response::IntoResponse;
use crate::bridge::envelope::PhysicalPlan;
use crate::bridge::physical_plan::VectorOp;
use crate::control::server::http::auth::{ApiError, AppState, resolve_identity};
use super::document::dispatch_plan;
pub async fn vector_search(
headers: HeaderMap,
State(state): State<AppState>,
Path(collection): Path<String>,
axum::Json(body): axum::Json<serde_json::Value>,
) -> Result<impl IntoResponse, ApiError> {
let identity = resolve_identity(&headers, &state, "http")?;
state
.shared
.check_tenant_quota(identity.tenant_id)
.map_err(|e| ApiError::BadRequest(e.to_string()))?;
let vector_arr = body
.get("vector")
.and_then(|v| v.as_array())
.ok_or_else(|| ApiError::BadRequest("missing 'vector' array field".into()))?;
let query_vector: Vec<f32> = vector_arr
.iter()
.enumerate()
.map(|(i, v)| {
v.as_f64()
.map(|f| f as f32)
.ok_or_else(|| ApiError::BadRequest(format!("vector[{i}] is not a number")))
})
.collect::<Result<Vec<_>, _>>()?;
if query_vector.is_empty() {
return Err(ApiError::BadRequest("vector must not be empty".into()));
}
let top_k = body.get("top_k").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
if top_k == 0 || top_k > 10_000 {
return Err(ApiError::BadRequest(
"top_k must be between 1 and 10000".into(),
));
}
let plan = PhysicalPlan::Vector(VectorOp::Search {
collection: collection.clone(),
query_vector: Arc::from(query_vector.as_slice()),
top_k,
ef_search: 0,
filter_bitmap: None,
field_name: String::new(),
rls_filters: Vec::new(),
});
state.shared.tenant_request_start(identity.tenant_id);
let result = dispatch_plan(&state, identity.tenant_id, &collection, plan).await;
state.shared.tenant_request_end(identity.tenant_id);
let payload = result?;
let results: serde_json::Value = if payload.is_empty() {
serde_json::json!([])
} else {
serde_json::from_slice(&payload).unwrap_or_else(|_| {
serde_json::Value::String(String::from_utf8_lossy(&payload).into_owned())
})
};
Ok(axum::Json(serde_json::json!({
"status": "ok",
"collection": collection,
"results": results,
})))
}