use std::sync::Arc;
use axum::{
extract::{Path, Query, State},
response::Json,
};
use serde::Deserialize;
use crate::core::facts::new_fact;
use crate::service::events::{AnalyzerAppState, AnalyzerEvent, ApiError};
#[derive(Deserialize)]
pub struct FactQueryParams {
pub subject: Option<String>,
pub predicate: Option<String>,
pub object: Option<String>,
}
pub async fn list_facts(
State(state): State<Arc<AnalyzerAppState>>,
Query(p): Query<FactQueryParams>,
) -> Result<Json<serde_json::Value>, ApiError> {
let facts = state.facts.clone();
let hits = tokio::task::spawn_blocking(move || {
facts.query(
p.subject.as_deref(),
p.predicate.as_deref(),
p.object.as_deref(),
)
})
.await
.map_err(|e| ApiError::internal(format!("query facts task panicked: {e}")))?
.map_err(|e| ApiError::internal(format!("query facts: {e:#}")))?;
let count = hits.len();
Ok(Json(serde_json::json!({ "facts": hits, "count": count })))
}
#[derive(Deserialize)]
pub struct UpsertFactRequest {
pub subject: String,
pub predicate: String,
pub object: String,
pub index_id: String,
#[serde(default = "default_confidence")]
pub confidence: f32,
#[serde(default)]
pub provenance: Vec<String>,
}
fn default_confidence() -> f32 {
1.0
}
pub async fn upsert_fact(
State(state): State<Arc<AnalyzerAppState>>,
Json(req): Json<UpsertFactRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
let subject = req.subject.clone();
let predicate = req.predicate.clone();
let mut fact = new_fact(req.subject, req.predicate, req.object, req.index_id);
fact.confidence = req.confidence.clamp(0.0, 1.0);
fact.provenance = req.provenance;
let id = fact.id;
let facts = state.facts.clone();
tokio::task::spawn_blocking(move || facts.upsert(fact))
.await
.map_err(|e| ApiError::internal(format!("upsert fact task panicked: {e}")))?
.map_err(|e| ApiError::internal(format!("upsert fact: {e:#}")))?;
state.emit(AnalyzerEvent::FactUpserted { subject, predicate });
Ok(Json(serde_json::json!({ "id": id, "upserted": true })))
}
pub async fn delete_fact(
State(state): State<Arc<AnalyzerAppState>>,
Path(id): Path<u64>,
) -> Result<Json<serde_json::Value>, ApiError> {
let facts = state.facts.clone();
let removed = tokio::task::spawn_blocking(move || facts.delete(id))
.await
.map_err(|e| ApiError::internal(format!("delete fact task panicked: {e}")))?
.map_err(|e| ApiError::internal(format!("delete fact: {e:#}")))?;
if removed {
state.emit(AnalyzerEvent::FactDeleted { id: id.to_string() });
}
Ok(Json(serde_json::json!({ "id": id, "removed": removed })))
}