use js_sys::{Array, Float32Array, Object, Reflect};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryConfig {
pub embedding: Vec<f32>,
pub k: usize,
pub filter: Option<serde_json::Value>,
pub ef_search: Option<usize>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CausalConeType {
Past,
Future,
LightCone,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CausalQueryConfig {
pub query: QueryConfig,
pub reference_time: f64,
pub cone_type: CausalConeType,
pub velocity: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TopologicalQuery {
PersistentHomology {
dimension: usize,
epsilon_min: f32,
epsilon_max: f32,
},
BettiNumbers { max_dimension: usize },
SheafConsistency { section_ids: Vec<String> },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CausalResult {
pub id: String,
pub score: f32,
pub causal_distance: Option<usize>,
pub temporal_distance: f64,
pub pattern: Option<serde_json::Value>,
}
pub fn js_array_to_vec_f32(arr: &Array) -> Result<Vec<f32>, JsValue> {
let mut vec = Vec::with_capacity(arr.length() as usize);
for i in 0..arr.length() {
let val = arr.get(i);
if let Some(num) = val.as_f64() {
vec.push(num as f32);
} else {
return Err(JsValue::from_str(&format!(
"Array element at index {} is not a number",
i
)));
}
}
Ok(vec)
}
pub fn vec_f32_to_js_array(vec: &[f32]) -> Float32Array {
Float32Array::from(vec)
}
pub fn js_object_to_json(obj: &JsValue) -> Result<serde_json::Value, JsValue> {
serde_wasm_bindgen::from_value(obj.clone())
.map_err(|e| JsValue::from_str(&format!("Failed to convert to JSON: {}", e)))
}
pub fn json_to_js_object(value: &serde_json::Value) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(value)
.map_err(|e| JsValue::from_str(&format!("Failed to convert from JSON: {}", e)))
}
pub fn create_js_error(message: &str, kind: &str) -> JsValue {
let obj = Object::new();
Reflect::set(&obj, &"message".into(), &message.into()).unwrap();
Reflect::set(&obj, &"kind".into(), &kind.into()).unwrap();
Reflect::set(&obj, &"name".into(), &"ExoError".into()).unwrap();
obj.into()
}
pub fn validate_dimensions(vec: &[f32], expected: usize) -> Result<(), JsValue> {
if vec.len() != expected {
return Err(create_js_error(
&format!(
"Dimension mismatch: expected {}, got {}",
expected,
vec.len()
),
"DimensionError",
));
}
Ok(())
}
pub fn validate_not_empty(vec: &[f32]) -> Result<(), JsValue> {
if vec.is_empty() {
return Err(create_js_error("Vector cannot be empty", "ValidationError"));
}
Ok(())
}
pub fn validate_k(k: usize) -> Result<(), JsValue> {
if k == 0 {
return Err(create_js_error(
"k must be greater than 0",
"ValidationError",
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_causal_cone_type_serialization() {
let cone = CausalConeType::Past;
let json = serde_json::to_string(&cone).unwrap();
assert_eq!(json, "\"past\"");
}
#[test]
fn test_topological_query_serialization() {
let query = TopologicalQuery::PersistentHomology {
dimension: 2,
epsilon_min: 0.1,
epsilon_max: 1.0,
};
let json = serde_json::to_value(&query).unwrap();
assert_eq!(json["type"], "persistent_homology");
}
}