use crate::panels::update_components::UpdateComponents;
use brep_render::assembly_status;
use brep_render::engine_state::EngineState;
use eframe::egui;
use serde_json::Value;
use std::collections::HashMap;
pub(crate) const FIXED_GLYPH: &str = "\u{23DA}";
pub(crate) const OUTDATED_GLYPH: &str = "\u{21BB}";
pub(crate) const OUTDATED_AMBER: egui::Color32 = egui::Color32::from_rgb(0xff, 0x9f, 0x0a);
pub(crate) struct ComponentRow {
pub id: String,
pub label: String,
pub part_name: String,
pub fixed: bool,
pub outdated: bool,
pub rollup_status: Option<String>,
pub visible: bool,
pub selected: bool,
pub solids: Vec<String>,
pub children: Vec<ChainNode>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ChainNode {
pub label: String,
pub children: Vec<ChainNode>,
}
pub(crate) fn is_acomp_segment(segment: &str) -> bool {
segment
.strip_prefix("ACOMP")
.map(|digits| !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()))
.unwrap_or(false)
}
fn chain_key(parent_key: &str, label: &str) -> String {
format!("{parent_key}:{label}")
}
pub(crate) fn chain_groups(member_locals: &[&str]) -> Vec<ChainNode> {
let mut order: Vec<String> = Vec::new();
let mut grouped: HashMap<String, Vec<&str>> = HashMap::new();
let mut leaves: Vec<ChainNode> = Vec::new();
for local in member_locals {
match local.split_once(':') {
Some((head, rest)) if is_acomp_segment(head) => {
if !grouped.contains_key(head) {
order.push(head.to_string());
}
grouped.entry(head.to_string()).or_default().push(rest);
}
_ => leaves.push(ChainNode {
label: (*local).to_string(),
children: Vec::new(),
}),
}
}
let mut out: Vec<ChainNode> = order
.into_iter()
.map(|head| {
let members = grouped.remove(&head).unwrap_or_default();
ChainNode {
children: chain_groups(&members),
label: head,
}
})
.collect();
out.append(&mut leaves);
out
}
fn owning_component_of_ref(element: &str) -> Option<&str> {
let name = element.split('@').next().unwrap_or(element);
let head = name.split(':').next().unwrap_or(name);
is_acomp_segment(head).then_some(head)
}
pub(crate) fn snapshot(state: &mut EngineState, updates: &UpdateComponents) -> Vec<ComponentRow> {
let mut rollup: HashMap<String, String> = HashMap::new();
let constraint_state = state.assembly_state_value();
if let Some(constraints) = constraint_state
.get("constraints")
.and_then(Value::as_array)
{
for entry in constraints {
let status = entry
.get("persistentData")
.and_then(|data| data.get("status"))
.and_then(Value::as_str)
.unwrap_or("")
.to_string();
let elements = entry
.get("inputParams")
.and_then(|params| params.get("elements"))
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
for element in elements.iter().filter_map(Value::as_str) {
let Some(component) = owning_component_of_ref(element) else {
continue;
};
let worse = rollup
.get(component)
.map(|current| {
assembly_status::status_severity(&status)
> assembly_status::status_severity(current)
})
.unwrap_or(true);
if worse {
rollup.insert(component.to_string(), status.clone());
}
}
}
}
let selected_solids = state.emphasis.selected_solids.clone();
let solid_visible: HashMap<String, bool> = state
.scene
.solids()
.iter()
.map(|solid| (solid.name.clone(), solid.visible))
.collect();
state
.assembly_components()
.iter()
.map(|record| {
let prefix = format!("{}:", record.id);
let locals: Vec<&str> = record
.solids
.iter()
.map(|name| name.strip_prefix(&prefix).unwrap_or(name))
.collect();
ComponentRow {
label: format!("{} ({})", record.part_name, record.id),
part_name: record.part_name.clone(),
fixed: record.fixed,
outdated: updates.is_outdated(&record.part_name),
rollup_status: rollup.get(&record.id).cloned(),
visible: record
.solids
.iter()
.all(|name| solid_visible.get(name).copied().unwrap_or(true)),
selected: record
.solids
.iter()
.any(|name| selected_solids.contains(name)),
solids: record.solids.clone(),
children: chain_groups(&locals),
id: record.id.clone(),
}
})
.collect()
}
#[allow(unused_variables)]
pub(crate) fn publish_tree(rows: &[ComponentRow]) {
if crate::automation::registry::enabled() {
let listing: Vec<Value> = rows
.iter()
.map(|row| {
serde_json::json!({
"id": row.id,
"label": row.label,
"fixed": row.fixed,
"outdated": row.outdated,
"visible": row.visible,
"selected": row.selected,
"status": row.rollup_status,
"solids": row.solids,
})
})
.collect();
crate::automation::registry::publish("__brepAssemblyTree", "assembly structure tree rows", &Value::Array(listing).to_string());
}
}