feagi_api/endpoints/
connectome.rs

1// Copyright 2025 Neuraville Inc.
2// Licensed under the Apache License, Version 2.0
3
4//! Connectome API Endpoints - Exact port from Python `/v1/connectome/*`
5
6// Removed - using crate::common::State instead
7use crate::common::ApiState;
8use crate::common::{ApiError, ApiResult, Json, Path, Query, State};
9use serde::Deserialize;
10use std::collections::HashMap;
11use tracing::warn;
12use utoipa::{IntoParams, ToSchema};
13
14/// GET /v1/connectome/cortical_areas/list/detailed
15#[utoipa::path(
16    get,
17    path = "/v1/connectome/cortical_areas/list/detailed",
18    tag = "connectome",
19    responses(
20        (status = 200, description = "Detailed cortical areas list", body = HashMap<String, serde_json::Value>),
21        (status = 500, description = "Internal server error")
22    )
23)]
24pub async fn get_cortical_areas_list_detailed(
25    State(state): State<ApiState>,
26) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
27    let connectome_service = state.connectome_service.as_ref();
28    match connectome_service.list_cortical_areas().await {
29        Ok(areas) => {
30            tracing::info!(target: "feagi-api",
31                "[DETAILED-LIST] Returning {} cortical areas", areas.len()
32            );
33
34            let detailed: HashMap<String, serde_json::Value> = areas
35                .into_iter()
36                .map(|area| {
37                    tracing::debug!(target: "feagi-api",
38                        "[DETAILED-LIST] Area {}: cortical_type='{}', is_mem_type={:?}",
39                        area.cortical_id, area.cortical_type,
40                        area.properties.get("is_mem_type")
41                    );
42
43                    let json_value = serde_json::to_value(&area).unwrap_or_default();
44
45                    tracing::debug!(target: "feagi-api",
46                        "[DETAILED-LIST] Serialized area {} has cortical_type: {}",
47                        area.cortical_id, json_value.get("cortical_type").is_some()
48                    );
49
50                    (area.cortical_id.clone(), json_value)
51                })
52                .collect();
53            Ok(Json(detailed))
54        }
55        Err(e) => Err(ApiError::internal(format!(
56            "Failed to get detailed list: {}",
57            e
58        ))),
59    }
60}
61
62/// GET /v1/connectome/properties/dimensions
63#[utoipa::path(get, path = "/v1/connectome/properties/dimensions", tag = "connectome")]
64pub async fn get_properties_dimensions(
65    State(_state): State<ApiState>,
66) -> ApiResult<Json<(usize, usize, usize)>> {
67    // Will use state when wired to NPU
68    // TODO: Get max dimensions from connectome manager
69    Ok(Json((0, 0, 0)))
70}
71
72/// GET /v1/connectome/properties/mappings
73#[utoipa::path(get, path = "/v1/connectome/properties/mappings", tag = "connectome")]
74pub async fn get_properties_mappings(
75    State(_state): State<ApiState>,
76) -> ApiResult<Json<HashMap<String, Vec<String>>>> {
77    // TODO: Get all cortical mappings
78    Ok(Json(HashMap::new()))
79}
80
81/// GET /v1/connectome/snapshot
82#[utoipa::path(get, path = "/v1/connectome/snapshot", tag = "connectome", responses((status = 200, body = HashMap<String, serde_json::Value>)))]
83pub async fn get_snapshot(
84    State(state): State<ApiState>,
85) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
86    let connectome_service = state.connectome_service.as_ref();
87    let areas = connectome_service
88        .list_cortical_areas()
89        .await
90        .map_err(|e| ApiError::internal(format!("{}", e)))?;
91    let regions = connectome_service
92        .list_brain_regions()
93        .await
94        .map_err(|e| ApiError::internal(format!("{}", e)))?;
95    let mut response = HashMap::new();
96    response.insert(
97        "cortical_area_count".to_string(),
98        serde_json::json!(areas.len()),
99    );
100    response.insert(
101        "brain_region_count".to_string(),
102        serde_json::json!(regions.len()),
103    );
104    Ok(Json(response))
105}
106
107/// GET /v1/connectome/stats
108#[utoipa::path(get, path = "/v1/connectome/stats", tag = "connectome", responses((status = 200, body = HashMap<String, serde_json::Value>)))]
109pub async fn get_stats(
110    State(state): State<ApiState>,
111) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
112    let analytics_service = state.analytics_service.as_ref();
113    let health = analytics_service
114        .get_system_health()
115        .await
116        .map_err(|e| ApiError::internal(format!("{}", e)))?;
117    let mut response = HashMap::new();
118    response.insert(
119        "neuron_count".to_string(),
120        serde_json::json!(health.neuron_count),
121    );
122    response.insert(
123        "cortical_area_count".to_string(),
124        serde_json::json!(health.cortical_area_count),
125    );
126    Ok(Json(response))
127}
128
129/// POST /v1/connectome/batch_neuron_operations
130#[utoipa::path(
131    post,
132    path = "/v1/connectome/batch_neuron_operations",
133    tag = "connectome"
134)]
135pub async fn post_batch_neuron_operations(
136    State(_state): State<ApiState>,
137    Json(_ops): Json<Vec<HashMap<String, serde_json::Value>>>,
138) -> ApiResult<Json<HashMap<String, i32>>> {
139    let mut response = HashMap::new();
140    response.insert("processed".to_string(), 0);
141    Ok(Json(response))
142}
143
144/// POST /v1/connectome/batch_synapse_operations
145#[utoipa::path(
146    post,
147    path = "/v1/connectome/batch_synapse_operations",
148    tag = "connectome"
149)]
150pub async fn post_batch_synapse_operations(
151    State(_state): State<ApiState>,
152    Json(_ops): Json<Vec<HashMap<String, serde_json::Value>>>,
153) -> ApiResult<Json<HashMap<String, i32>>> {
154    let mut response = HashMap::new();
155    response.insert("processed".to_string(), 0);
156    Ok(Json(response))
157}
158
159/// GET /v1/connectome/neuron_count
160#[utoipa::path(get, path = "/v1/connectome/neuron_count", tag = "connectome")]
161pub async fn get_neuron_count(State(state): State<ApiState>) -> ApiResult<Json<i64>> {
162    let analytics = state.analytics_service.as_ref();
163    let health = analytics
164        .get_system_health()
165        .await
166        .map_err(|e| ApiError::internal(format!("{}", e)))?;
167    Ok(Json(health.neuron_count as i64))
168}
169
170/// GET /v1/connectome/synapse_count
171#[utoipa::path(get, path = "/v1/connectome/synapse_count", tag = "connectome")]
172pub async fn get_synapse_count(State(_state): State<ApiState>) -> ApiResult<Json<i64>> {
173    Ok(Json(0))
174}
175
176/// GET /v1/connectome/paths
177#[utoipa::path(get, path = "/v1/connectome/paths", tag = "connectome")]
178pub async fn get_paths(
179    State(_state): State<ApiState>,
180    Query(_params): Query<HashMap<String, String>>,
181) -> ApiResult<Json<Vec<Vec<String>>>> {
182    Ok(Json(Vec::new()))
183}
184
185/// GET /v1/connectome/cumulative_stats
186#[utoipa::path(get, path = "/v1/connectome/cumulative_stats", tag = "connectome")]
187pub async fn get_cumulative_stats(
188    State(_state): State<ApiState>,
189) -> ApiResult<Json<HashMap<String, i64>>> {
190    let mut response = HashMap::new();
191    response.insert("total_bursts".to_string(), 0);
192    Ok(Json(response))
193}
194
195/// GET /v1/connectome/area_details
196#[utoipa::path(get, path = "/v1/connectome/area_details", tag = "connectome")]
197pub async fn get_area_details(
198    State(state): State<ApiState>,
199    Query(params): Query<HashMap<String, String>>,
200) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
201    let area_ids_str = params
202        .get("area_ids")
203        .ok_or_else(|| ApiError::invalid_input("area_ids required"))?;
204    let area_ids: Vec<&str> = area_ids_str.split(',').collect();
205    let connectome_service = state.connectome_service.as_ref();
206    let mut details = HashMap::new();
207    for area_id in area_ids {
208        if let Ok(area) = connectome_service.get_cortical_area(area_id).await {
209            details.insert(
210                area_id.to_string(),
211                serde_json::json!({"cortical_id": area.cortical_id}),
212            );
213        }
214    }
215    Ok(Json(details))
216}
217
218/// POST /v1/connectome/rebuild
219#[utoipa::path(post, path = "/v1/connectome/rebuild", tag = "connectome")]
220pub async fn post_rebuild(
221    State(_state): State<ApiState>,
222) -> ApiResult<Json<HashMap<String, String>>> {
223    Ok(Json(HashMap::from([(
224        "message".to_string(),
225        "Not yet implemented".to_string(),
226    )])))
227}
228
229/// GET /v1/connectome/structure
230#[utoipa::path(get, path = "/v1/connectome/structure", tag = "connectome")]
231pub async fn get_structure(
232    State(state): State<ApiState>,
233) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
234    let connectome_service = state.connectome_service.as_ref();
235    let areas = connectome_service
236        .list_cortical_areas()
237        .await
238        .map_err(|e| ApiError::internal(format!("{}", e)))?;
239    let mut response = HashMap::new();
240    response.insert("cortical_areas".to_string(), serde_json::json!(areas.len()));
241    Ok(Json(response))
242}
243
244/// POST /v1/connectome/clear
245#[utoipa::path(post, path = "/v1/connectome/clear", tag = "connectome")]
246pub async fn post_clear(
247    State(_state): State<ApiState>,
248) -> ApiResult<Json<HashMap<String, String>>> {
249    Ok(Json(HashMap::from([(
250        "message".to_string(),
251        "Not yet implemented".to_string(),
252    )])))
253}
254
255/// GET /v1/connectome/validation
256#[utoipa::path(get, path = "/v1/connectome/validation", tag = "connectome")]
257pub async fn get_validation(
258    State(_state): State<ApiState>,
259) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
260    let mut response = HashMap::new();
261    response.insert("valid".to_string(), serde_json::json!(true));
262    Ok(Json(response))
263}
264
265/// GET /v1/connectome/topology
266#[utoipa::path(get, path = "/v1/connectome/topology", tag = "connectome")]
267pub async fn get_topology(
268    State(_state): State<ApiState>,
269) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
270    let mut response = HashMap::new();
271    response.insert("layers".to_string(), serde_json::json!(0));
272    Ok(Json(response))
273}
274
275/// POST /v1/connectome/optimize
276#[utoipa::path(post, path = "/v1/connectome/optimize", tag = "connectome")]
277pub async fn post_optimize(
278    State(_state): State<ApiState>,
279) -> ApiResult<Json<HashMap<String, String>>> {
280    Ok(Json(HashMap::from([(
281        "message".to_string(),
282        "Not yet implemented".to_string(),
283    )])))
284}
285
286/// GET /v1/connectome/connectivity_matrix
287#[utoipa::path(get, path = "/v1/connectome/connectivity_matrix", tag = "connectome")]
288pub async fn get_connectivity_matrix(
289    State(_state): State<ApiState>,
290) -> ApiResult<Json<HashMap<String, Vec<Vec<i32>>>>> {
291    let mut response = HashMap::new();
292    response.insert("matrix".to_string(), Vec::new());
293    Ok(Json(response))
294}
295
296/// POST /v1/connectome/neurons/batch
297#[utoipa::path(post, path = "/v1/connectome/neurons/batch", tag = "connectome")]
298pub async fn post_neurons_batch(
299    State(_state): State<ApiState>,
300    Json(_ops): Json<Vec<HashMap<String, serde_json::Value>>>,
301) -> ApiResult<Json<HashMap<String, i32>>> {
302    let mut response = HashMap::new();
303    response.insert("processed".to_string(), 0);
304    Ok(Json(response))
305}
306
307/// POST /v1/connectome/synapses/batch
308#[utoipa::path(post, path = "/v1/connectome/synapses/batch", tag = "connectome")]
309pub async fn post_synapses_batch(
310    State(_state): State<ApiState>,
311    Json(_ops): Json<Vec<HashMap<String, serde_json::Value>>>,
312) -> ApiResult<Json<HashMap<String, i32>>> {
313    let mut response = HashMap::new();
314    response.insert("processed".to_string(), 0);
315    Ok(Json(response))
316}
317
318// EXACT Python path matches:
319/// GET /v1/connectome/cortical_areas/list/summary
320#[utoipa::path(
321    get,
322    path = "/v1/connectome/cortical_areas/list/summary",
323    tag = "connectome"
324)]
325pub async fn get_cortical_areas_list_summary(
326    State(state): State<ApiState>,
327) -> ApiResult<Json<Vec<HashMap<String, serde_json::Value>>>> {
328    let connectome_service = state.connectome_service.as_ref();
329    let areas = connectome_service
330        .list_cortical_areas()
331        .await
332        .map_err(|e| ApiError::internal(format!("{}", e)))?;
333    let summary: Vec<HashMap<String, serde_json::Value>> = areas
334        .iter()
335        .map(|a| {
336            let mut map = HashMap::new();
337            map.insert("cortical_id".to_string(), serde_json::json!(a.cortical_id));
338            map.insert("cortical_name".to_string(), serde_json::json!(a.name));
339            map
340        })
341        .collect();
342    Ok(Json(summary))
343}
344
345/// GET /v1/connectome/cortical_areas/list/transforming
346#[utoipa::path(
347    get,
348    path = "/v1/connectome/cortical_areas/list/transforming",
349    tag = "connectome"
350)]
351pub async fn get_cortical_areas_list_transforming(
352    State(_state): State<ApiState>,
353) -> ApiResult<Json<Vec<String>>> {
354    Ok(Json(Vec::new()))
355}
356
357/// GET /v1/connectome/cortical_area/{cortical_id}/neurons
358#[utoipa::path(
359    get,
360    path = "/v1/connectome/cortical_area/{cortical_id}/neurons",
361    tag = "connectome"
362)]
363pub async fn get_cortical_area_neurons(
364    State(state): State<ApiState>,
365    Path(cortical_id): Path<String>,
366) -> ApiResult<Json<Vec<u64>>> {
367    use tracing::debug;
368
369    let neuron_service = state.neuron_service.as_ref();
370
371    // CRITICAL FIX: Query actual neurons from NPU instead of returning empty stub
372    let neurons = neuron_service
373        .list_neurons_in_area(&cortical_id, None)
374        .await
375        .map_err(|e| {
376            ApiError::internal(format!(
377                "Failed to get neurons in area {}: {}",
378                cortical_id, e
379            ))
380        })?;
381
382    let neuron_ids: Vec<u64> = neurons.iter().map(|n| n.id).collect();
383
384    debug!(target: "feagi-api", "GET /connectome/cortical_area/{}/neurons - found {} neurons", cortical_id, neuron_ids.len());
385    Ok(Json(neuron_ids))
386}
387
388/// GET /v1/connectome/{cortical_area_id}/synapses
389#[utoipa::path(
390    get,
391    path = "/v1/connectome/{cortical_area_id}/synapses",
392    tag = "connectome"
393)]
394pub async fn get_area_synapses(
395    State(state): State<ApiState>,
396    Path(area_id): Path<String>,
397) -> ApiResult<Json<Vec<HashMap<String, serde_json::Value>>>> {
398    use tracing::debug;
399
400    let connectome_service = state.connectome_service.as_ref();
401    let neuron_service = state.neuron_service.as_ref();
402
403    // CRITICAL FIX: Query actual synapses from NPU instead of returning empty stub
404    // Get cortical_idx for the area
405    let area_info = connectome_service
406        .get_cortical_area(&area_id)
407        .await
408        .map_err(|_| ApiError::not_found("CorticalArea", &area_id))?;
409
410    let cortical_idx = area_info.cortical_idx;
411
412    // Get all neurons in this cortical area
413    let neurons = neuron_service
414        .list_neurons_in_area(&area_id, None)
415        .await
416        .map_err(|e| ApiError::internal(format!("Failed to get neurons: {}", e)))?;
417
418    tracing::debug!(
419        target: "feagi-api",
420        "Getting synapses for area {} (idx={}): {} neurons",
421        area_id,
422        cortical_idx,
423        neurons.len()
424    );
425
426    // Collect all outgoing synapses from neurons in this area
427    // Access NPU through ConnectomeManager singleton
428    warn!(
429        "[API] /v1/connectome/cortical_area/{}/synapses endpoint called - this acquires NPU lock!",
430        area_id
431    );
432    let manager = feagi_brain_development::ConnectomeManager::instance();
433    let manager_lock = manager.read();
434    let npu_arc = manager_lock
435        .get_npu()
436        .ok_or_else(|| ApiError::internal("NPU not initialized"))?;
437    let lock_start = std::time::Instant::now();
438    tracing::debug!("[NPU-LOCK] CONNECTOME-API: Acquiring NPU lock for synapse queries");
439    let npu_lock = npu_arc.lock().unwrap();
440    let lock_wait = lock_start.elapsed();
441    tracing::debug!(
442        "[NPU-LOCK] CONNECTOME-API: Lock acquired (waited {:.2}ms)",
443        lock_wait.as_secs_f64() * 1000.0
444    );
445
446    let mut all_synapses = Vec::new();
447    for neuron_info in &neurons {
448        let neuron_id = neuron_info.id as u32;
449        let outgoing = npu_lock.get_outgoing_synapses(neuron_id);
450
451        for (target_id, weight, psp, synapse_type) in outgoing {
452            let mut synapse_obj = HashMap::new();
453            synapse_obj.insert("source_neuron_id".to_string(), serde_json::json!(neuron_id));
454            synapse_obj.insert("target_neuron_id".to_string(), serde_json::json!(target_id));
455            synapse_obj.insert("weight".to_string(), serde_json::json!(weight));
456            synapse_obj.insert("postsynaptic_potential".to_string(), serde_json::json!(psp));
457            synapse_obj.insert("synapse_type".to_string(), serde_json::json!(synapse_type));
458            all_synapses.push(synapse_obj);
459        }
460    }
461
462    debug!(target: "feagi-api", "Found {} synapses from area {}", all_synapses.len(), area_id);
463    Ok(Json(all_synapses))
464}
465
466/// GET /v1/connectome/cortical_info/{cortical_area}
467#[utoipa::path(
468    get,
469    path = "/v1/connectome/cortical_info/{cortical_area}",
470    tag = "connectome"
471)]
472pub async fn get_cortical_info(
473    State(state): State<ApiState>,
474    Path(cortical_area): Path<String>,
475) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
476    let connectome_service = state.connectome_service.as_ref();
477    let area = connectome_service
478        .get_cortical_area(&cortical_area)
479        .await
480        .map_err(|e| ApiError::not_found("area", &format!("{}", e)))?;
481    let mut response = HashMap::new();
482    response.insert(
483        "cortical_id".to_string(),
484        serde_json::json!(area.cortical_id),
485    );
486    response.insert("cortical_name".to_string(), serde_json::json!(area.name));
487    Ok(Json(response))
488}
489
490/// GET /v1/connectome/stats/cortical/cumulative/{cortical_area}
491#[utoipa::path(
492    get,
493    path = "/v1/connectome/stats/cortical/cumulative/{cortical_area}",
494    tag = "connectome"
495)]
496pub async fn get_stats_cortical_cumulative(
497    State(_state): State<ApiState>,
498    Path(_area): Path<String>,
499) -> ApiResult<Json<HashMap<String, i64>>> {
500    let mut response = HashMap::new();
501    response.insert("total_fires".to_string(), 0);
502    Ok(Json(response))
503}
504
505/// GET /v1/connectome/neuron/{neuron_id}/properties
506#[utoipa::path(
507    get,
508    path = "/v1/connectome/neuron/{neuron_id}/properties",
509    tag = "connectome"
510)]
511pub async fn get_neuron_properties_by_id(
512    State(state): State<ApiState>,
513    Path(neuron_id): Path<u64>,
514) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
515    let connectome_service = state.connectome_service.as_ref();
516    let props = connectome_service
517        .get_neuron_properties(neuron_id)
518        .await
519        .map_err(ApiError::from)?;
520    Ok(Json(props))
521}
522
523/// GET /v1/connectome/neuron_properties
524#[utoipa::path(get, path = "/v1/connectome/neuron_properties", tag = "connectome")]
525pub async fn get_neuron_properties_query(
526    State(state): State<ApiState>,
527    Query(params): Query<HashMap<String, String>>,
528) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
529    let neuron_id: u64 = params
530        .get("neuron_id")
531        .ok_or_else(|| ApiError::invalid_input("neuron_id required"))?
532        .parse()
533        .map_err(|_| ApiError::invalid_input("neuron_id must be an integer"))?;
534
535    let connectome_service = state.connectome_service.as_ref();
536    let props = connectome_service
537        .get_neuron_properties(neuron_id)
538        .await
539        .map_err(ApiError::from)?;
540    Ok(Json(props))
541}
542
543#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
544#[into_params(parameter_in = Query)]
545pub struct NeuronPropertiesAtQuery {
546    /// Cortical area ID (base64 string)
547    pub cortical_id: String,
548    /// X coordinate within the cortical area
549    pub x: u32,
550    /// Y coordinate within the cortical area
551    pub y: u32,
552    /// Z coordinate within the cortical area
553    pub z: u32,
554}
555
556/// GET /v1/connectome/neuron_properties_at
557///
558/// Resolve a neuron by `(cortical_id, x, y, z)` and return its live properties/state.
559///
560/// This is intended for clients (e.g., Brain Visualizer) that do not have neuron IDs.
561#[utoipa::path(
562    get,
563    path = "/v1/connectome/neuron_properties_at",
564    tag = "connectome",
565    params(NeuronPropertiesAtQuery)
566)]
567pub async fn get_neuron_properties_at_query(
568    State(state): State<ApiState>,
569    Query(params): Query<NeuronPropertiesAtQuery>,
570) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
571    let cortical_id = params.cortical_id;
572    let x = params.x;
573    let y = params.y;
574    let z = params.z;
575
576    // Resolve cortical_idx via service layer.
577    let connectome_service = state.connectome_service.as_ref();
578    let area = connectome_service
579        .get_cortical_area(&cortical_id)
580        .await
581        .map_err(|_| ApiError::not_found("CorticalArea", &cortical_id))?;
582
583    // Resolve neuron_id via NPU coordinate lookup (fast path).
584    //
585    // IMPORTANT (Axum): handler futures must be `Send`.
586    // Do NOT hold non-Send locks/guards across `.await`.
587    let neuron_id_u32: u32 = {
588        // Note: this uses the global ConnectomeManager singleton, consistent with existing connectome endpoints.
589        let manager = feagi_brain_development::ConnectomeManager::instance();
590        let manager_lock = manager.read();
591        let npu_arc = manager_lock
592            .get_npu()
593            .ok_or_else(|| ApiError::internal("NPU not initialized"))?;
594        let npu_lock = npu_arc.lock().unwrap();
595
596        npu_lock
597            .get_neuron_id_at_coordinate(area.cortical_idx, x, y, z)
598            .ok_or_else(|| {
599                ApiError::not_found(
600                    "Neuron",
601                    &format!("cortical_id={} x={} y={} z={}", cortical_id, x, y, z),
602                )
603            })?
604    };
605
606    let mut props = connectome_service
607        .get_neuron_properties(neuron_id_u32 as u64)
608        .await
609        .map_err(ApiError::from)?;
610
611    // Always include resolved identity fields for clients.
612    props.insert(
613        "neuron_id".to_string(),
614        serde_json::json!(neuron_id_u32 as u64),
615    );
616    props.insert("cortical_id".to_string(), serde_json::json!(cortical_id));
617    props.insert(
618        "cortical_idx".to_string(),
619        serde_json::json!(area.cortical_idx),
620    );
621
622    Ok(Json(props))
623}
624
625/// GET /v1/connectome/area_neurons
626#[utoipa::path(get, path = "/v1/connectome/area_neurons", tag = "connectome")]
627pub async fn get_area_neurons_query(
628    State(_state): State<ApiState>,
629    Query(_params): Query<HashMap<String, String>>,
630) -> ApiResult<Json<Vec<u64>>> {
631    Ok(Json(Vec::new()))
632}
633
634/// GET /v1/connectome/fire_queue/{cortical_area}
635#[utoipa::path(
636    get,
637    path = "/v1/connectome/fire_queue/{cortical_area}",
638    tag = "connectome"
639)]
640pub async fn get_fire_queue_area(
641    State(_state): State<ApiState>,
642    Path(_area): Path<String>,
643) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
644    Ok(Json(HashMap::new()))
645}
646
647/// GET /v1/connectome/plasticity
648#[utoipa::path(get, path = "/v1/connectome/plasticity", tag = "connectome")]
649pub async fn get_plasticity_info(
650    State(_state): State<ApiState>,
651) -> ApiResult<Json<HashMap<String, bool>>> {
652    let mut response = HashMap::new();
653    response.insert("enabled".to_string(), true);
654    Ok(Json(response))
655}
656
657/// GET /v1/connectome/path
658#[utoipa::path(get, path = "/v1/connectome/path", tag = "connectome")]
659pub async fn get_path_query(
660    State(_state): State<ApiState>,
661    Query(_params): Query<HashMap<String, String>>,
662) -> ApiResult<Json<Vec<Vec<String>>>> {
663    Ok(Json(Vec::new()))
664}
665
666/// GET /v1/connectome/download
667#[utoipa::path(get, path = "/v1/connectome/download", tag = "connectome")]
668pub async fn get_download_connectome(
669    State(state): State<ApiState>,
670) -> ApiResult<Json<serde_json::Value>> {
671    // Export connectome via service layer (architecture-compliant)
672    let snapshot = state
673        .connectome_service
674        .export_connectome()
675        .await
676        .map_err(ApiError::from)?;
677
678    // Serialize snapshot to JSON
679    let json_value = serde_json::to_value(&snapshot)
680        .map_err(|e| ApiError::internal(format!("Failed to serialize connectome: {}", e)))?;
681
682    Ok(Json(json_value))
683}
684
685/// GET /v1/connectome/download-cortical-area/{cortical_area}
686#[utoipa::path(
687    get,
688    path = "/v1/connectome/download-cortical-area/{cortical_area}",
689    tag = "connectome"
690)]
691pub async fn get_download_cortical_area(
692    State(_state): State<ApiState>,
693    Path(_area): Path<String>,
694) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
695    Ok(Json(HashMap::new()))
696}
697
698/// POST /v1/connectome/upload
699#[utoipa::path(post, path = "/v1/connectome/upload", tag = "connectome")]
700pub async fn post_upload_connectome(
701    State(state): State<ApiState>,
702    Json(data): Json<serde_json::Value>,
703) -> ApiResult<Json<HashMap<String, String>>> {
704    // Deserialize snapshot from JSON
705    let snapshot: feagi_npu_neural::types::connectome::ConnectomeSnapshot =
706        serde_json::from_value(data).map_err(|e| {
707            ApiError::invalid_input(format!("Invalid connectome snapshot format: {}", e))
708        })?;
709
710    // Import connectome via service layer (architecture-compliant)
711    state
712        .connectome_service
713        .import_connectome(snapshot)
714        .await
715        .map_err(ApiError::from)?;
716
717    Ok(Json(HashMap::from([(
718        "message".to_string(),
719        "Connectome imported successfully".to_string(),
720    )])))
721}
722
723/// POST /v1/connectome/upload-cortical-area
724#[utoipa::path(post, path = "/v1/connectome/upload-cortical-area", tag = "connectome")]
725pub async fn post_upload_cortical_area(
726    State(_state): State<ApiState>,
727    Json(_data): Json<HashMap<String, serde_json::Value>>,
728) -> ApiResult<Json<HashMap<String, String>>> {
729    Ok(Json(HashMap::from([(
730        "message".to_string(),
731        "Upload not yet implemented".to_string(),
732    )])))
733}
734
735/// GET /v1/connectome/cortical_area/list/types
736#[utoipa::path(
737    get,
738    path = "/v1/connectome/cortical_area/list/types",
739    tag = "connectome",
740    responses(
741        (status = 200, description = "List of cortical types with their cortical IDs and group IDs", body = HashMap<String, serde_json::Value>),
742        (status = 500, description = "Internal server error")
743    )
744)]
745pub async fn get_cortical_area_list_types(
746    State(state): State<ApiState>,
747) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
748    // Note: decode_cortical_id removed - use CorticalID methods
749    use std::collections::{HashMap, HashSet};
750
751    let connectome_service = state.connectome_service.as_ref();
752    let areas = connectome_service
753        .list_cortical_areas()
754        .await
755        .map_err(|e| ApiError::internal(format!("Failed to list cortical areas: {}", e)))?;
756
757    // Helper function to map cortical subtype to human-readable title
758    fn get_cortical_type_title(subtype: &str) -> String {
759        match subtype {
760            "svi" => "segmented vision".to_string(),
761            "mot" => "motor".to_string(),
762            "bat" => "battery".to_string(),
763            "mis" => "miscellaneous".to_string(),
764            "gaz" => "gaze control".to_string(),
765            "pow" => "power".to_string(),
766            "dea" => "death".to_string(),
767            _ => {
768                // For unknown types, capitalize first letter and add spaces
769                if !subtype.is_empty() {
770                    let mut chars = subtype.chars();
771                    let first = chars.next().unwrap().to_uppercase().collect::<String>();
772                    let rest: String = chars.collect();
773                    format!("{}{}", first, rest)
774                } else {
775                    "unknown".to_string()
776                }
777            }
778        }
779    }
780
781    // Group areas by cortical subtype
782    let mut type_map: HashMap<String, (String, Vec<String>, HashSet<u8>)> = HashMap::new();
783
784    for area in areas {
785        // Parse cortical ID from base64
786        use feagi_structures::genomic::cortical_area::CorticalID;
787        if let Ok(cortical_id_typed) = CorticalID::try_from_base_64(&area.cortical_id) {
788            // Extract subtype and group_id using CorticalID methods
789            if let Some(subtype) = cortical_id_typed.extract_subtype() {
790                let entry = type_map.entry(subtype.clone()).or_insert_with(|| {
791                    let title = get_cortical_type_title(&subtype);
792                    (title, Vec::new(), HashSet::new())
793                });
794
795                // Add cortical ID in base64 format
796                entry.1.push(area.cortical_id.clone());
797
798                // Add group_id if available
799                if let Some(group_id) = cortical_id_typed.extract_group_id() {
800                    entry.2.insert(group_id);
801                }
802            }
803        }
804    }
805
806    // Convert to response format
807    let mut response: HashMap<String, serde_json::Value> = HashMap::new();
808    for (subtype, (title, mut cortical_ids, group_ids)) in type_map {
809        // Sort cortical_ids for consistent output
810        cortical_ids.sort();
811
812        let mut group_ids_vec: Vec<u8> = group_ids.into_iter().collect();
813        group_ids_vec.sort_unstable();
814
815        response.insert(
816            subtype,
817            serde_json::json!({
818                "title": title,
819                "cortical_ids": cortical_ids,
820                "group_ids": group_ids_vec
821            }),
822        );
823    }
824
825    Ok(Json(response))
826}