use crate::graph::VisualGraph;
use crate::intent::{RenderIntentGraph, RenderIntentNode, RenderIntentEdge, CurveType};
use crate::layout::strategies::compute_layout;
use crate::layout::profile::{RenderProfile, FanoutMode};
use rand::Rng;
use std::collections::HashMap;
pub struct IntentBuilder;
impl IntentBuilder {
pub fn from_graph(graph: &VisualGraph, profile: &RenderProfile) -> RenderIntentGraph {
let layout = compute_layout(graph, &profile.layout, profile.spacing);
let mut nodes = Vec::new();
let mut node_index = HashMap::new();
for node in &graph.nodes {
let (x, y) = layout.get(&node.id).copied().unwrap_or((0.0, 0.0));
let intent_node = RenderIntentNode {
id: node.id,
label: node.label.clone(),
center: (x, y),
radius: 18.0,
shape: node.shape,
fill_color: "white".into(),
stroke_color: "black".into(),
stroke_width: 2.0,
font_size: profile.font_size as f64,
font_family: "sans-serif".into(),
font_color: "black".into(),
label_offset: (0.0, 5.0),
is_accept: node.is_accept,
is_start: node.id == 0,
node_type: node.node_type,
};
println!("Node {} :: start: {}; end: {}", intent_node.id, intent_node.is_start, intent_node.is_accept);
node_index.insert(node.id, intent_node.clone());
nodes.push(intent_node);
}
let mut edge_groups: HashMap<(usize, usize), Vec<&_>> = HashMap::new();
for edge in &graph.edges {
let key = if edge.from < edge.to {
(edge.from, edge.to)
} else {
(edge.to, edge.from)
};
edge_groups.entry(key).or_default().push(edge);
}
let mut edges = Vec::new();
for group in edge_groups.values() {
let n = group.len();
let mid = (n - 1) as f64 / 2.0;
for (i, edge) in group.iter().enumerate() {
let from_node = &node_index[&edge.from];
let to_node = &node_index[&edge.to];
let (x1, y1) = from_node.center;
let (x2, y2) = to_node.center;
let curve = if edge.from == edge.to {
CurveType::Loop {
angle: profile.loop_angle as f64,
offset: 1.0,
}
} else if profile.no_fanout {
CurveType::Straight
} else if n > 1 {
let idx = i as f64 - mid;
let factor = match profile.fanout_mode {
FanoutMode::Symmetric => idx.signum(),
FanoutMode::Offset => i as f64,
FanoutMode::Random => {
let mut rng = rand::thread_rng();
rng.gen_range(-1.0..1.0) * idx.abs()
}
};
let bend = profile.fanout_max_bend as f64 * factor;
let dx = x2 - x1;
let dy = y2 - y1;
let len = (dx * dx + dy * dy).sqrt().max(1.0);
let (nx, ny) = (dx / len, dy / len);
let (px, py) = (-ny, nx);
let base = ((x1 + x2) / 2.0, (y1 + y2) / 2.0);
let control1 = (base.0 + px * bend * 0.6, base.1 + py * bend * 0.6);
let control2 = (base.0 + px * bend * 1.2, base.1 + py * bend * 1.2);
CurveType::Cubic { control1, control2 }
} else {
CurveType::Straight
};
edges.push(RenderIntentEdge {
from: edge.from,
to: edge.to,
from_point: (x1, y1),
to_point: (x2, y2),
curve,
bend_offset: 0.0,
stroke_color: "black".into(),
stroke_width: 2.0,
arrow_size: 8.0,
arrow_refx: 6.0,
arrow_fill: "black".into(),
label: edge.label.clone(),
label_pos: ((x1 + x2) / 2.0, (y1 + y2) / 2.0 - 5.0),
label_font_size: profile.font_size as f64,
label_font_color: "black".into(),
});
}
}
RenderIntentGraph {
nodes,
edges,
width: 600.0,
height: 400.0,
}
}
}