Skip to main content

oxirs_gql/
visual_schema_designer.rs

1//! Visual Schema Designer
2//!
3//! This module provides a visual schema design framework for creating,
4//! editing, and visualizing GraphQL schemas with an interactive interface.
5//!
6//! ## Features
7//!
8//! - **Schema Visualization**: Generate visual representations of schemas
9//! - **Interactive Editing**: Add, modify, and remove types and fields
10//! - **Relationship Mapping**: Visualize relationships between types
11//! - **Validation**: Real-time schema validation
12//! - **Import/Export**: Support for SDL import and export
13//! - **Layout Algorithms**: Automatic layout for clean visualization
14//!
15//! ## Usage
16//!
17//! ```rust,ignore
18//! use oxirs_gql::visual_schema_designer::{SchemaDesigner, SchemaNode, LayoutOptions};
19//!
20//! let designer = SchemaDesigner::new();
21//! designer.import_sdl(sdl_string).await?;
22//!
23//! // Get visualization data
24//! let graph = designer.get_visualization().await;
25//! ```
26
27use anyhow::{anyhow, Result};
28use serde::{Deserialize, Serialize};
29use std::collections::{HashMap, HashSet};
30use std::sync::Arc;
31use tokio::sync::RwLock;
32
33/// Node type in the schema graph
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum NodeType {
36    /// Object type
37    ObjectType,
38    /// Interface type
39    InterfaceType,
40    /// Input type
41    InputType,
42    /// Enum type
43    EnumType,
44    /// Union type
45    UnionType,
46    /// Scalar type
47    ScalarType,
48    /// Directive
49    Directive,
50}
51
52impl std::fmt::Display for NodeType {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            NodeType::ObjectType => write!(f, "type"),
56            NodeType::InterfaceType => write!(f, "interface"),
57            NodeType::InputType => write!(f, "input"),
58            NodeType::EnumType => write!(f, "enum"),
59            NodeType::UnionType => write!(f, "union"),
60            NodeType::ScalarType => write!(f, "scalar"),
61            NodeType::Directive => write!(f, "directive"),
62        }
63    }
64}
65
66/// Edge type in the schema graph
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
68pub enum EdgeType {
69    /// Field reference
70    Field,
71    /// Implements interface
72    Implements,
73    /// Union member
74    UnionMember,
75    /// Input argument
76    Argument,
77    /// Directive application
78    DirectiveUse,
79}
80
81/// Position in the visual canvas
82#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
83pub struct Position {
84    /// X coordinate
85    pub x: f64,
86    /// Y coordinate
87    pub y: f64,
88}
89
90impl Position {
91    /// Create a new position
92    pub fn new(x: f64, y: f64) -> Self {
93        Self { x, y }
94    }
95
96    /// Calculate distance to another position
97    pub fn distance_to(&self, other: &Position) -> f64 {
98        let dx = self.x - other.x;
99        let dy = self.y - other.y;
100        (dx * dx + dy * dy).sqrt()
101    }
102}
103
104/// Size dimensions
105#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
106pub struct Size {
107    /// Width
108    pub width: f64,
109    /// Height
110    pub height: f64,
111}
112
113impl Default for Size {
114    fn default() -> Self {
115        Self {
116            width: 200.0,
117            height: 100.0,
118        }
119    }
120}
121
122/// Schema node (type definition)
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct SchemaNode {
125    /// Unique identifier
126    pub id: String,
127    /// Node name
128    pub name: String,
129    /// Node type
130    pub node_type: NodeType,
131    /// Description
132    pub description: Option<String>,
133    /// Fields (for object, interface, input types)
134    pub fields: Vec<FieldDefinition>,
135    /// Enum values (for enum types)
136    pub enum_values: Vec<EnumValue>,
137    /// Union members (for union types)
138    pub union_members: Vec<String>,
139    /// Implemented interfaces
140    pub implements: Vec<String>,
141    /// Applied directives
142    pub directives: Vec<DirectiveUse>,
143    /// Visual position
144    pub position: Position,
145    /// Visual size
146    pub size: Size,
147    /// Whether node is collapsed in view
148    pub collapsed: bool,
149    /// Whether node is selected
150    pub selected: bool,
151    /// Custom metadata
152    pub metadata: HashMap<String, String>,
153}
154
155impl SchemaNode {
156    /// Create a new schema node
157    pub fn new(id: &str, name: &str, node_type: NodeType) -> Self {
158        Self {
159            id: id.to_string(),
160            name: name.to_string(),
161            node_type,
162            description: None,
163            fields: Vec::new(),
164            enum_values: Vec::new(),
165            union_members: Vec::new(),
166            implements: Vec::new(),
167            directives: Vec::new(),
168            position: Position::default(),
169            size: Size::default(),
170            collapsed: false,
171            selected: false,
172            metadata: HashMap::new(),
173        }
174    }
175
176    /// Set description
177    pub fn with_description(mut self, description: &str) -> Self {
178        self.description = Some(description.to_string());
179        self
180    }
181
182    /// Add a field
183    pub fn with_field(mut self, field: FieldDefinition) -> Self {
184        self.fields.push(field);
185        self
186    }
187
188    /// Set position
189    pub fn with_position(mut self, x: f64, y: f64) -> Self {
190        self.position = Position::new(x, y);
191        self
192    }
193
194    /// Calculate visual height based on fields
195    pub fn calculate_height(&self) -> f64 {
196        let base_height = 40.0; // Header
197        let field_height = 24.0;
198        let padding = 16.0;
199
200        if self.collapsed {
201            return base_height;
202        }
203
204        let content_height = match self.node_type {
205            NodeType::ObjectType | NodeType::InterfaceType | NodeType::InputType => {
206                self.fields.len() as f64 * field_height
207            }
208            NodeType::EnumType => self.enum_values.len() as f64 * field_height,
209            NodeType::UnionType => self.union_members.len() as f64 * field_height,
210            _ => 0.0,
211        };
212
213        base_height + content_height + padding
214    }
215}
216
217/// Field definition
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct FieldDefinition {
220    /// Field name
221    pub name: String,
222    /// Field type (as string)
223    pub field_type: String,
224    /// Whether field is nullable
225    pub nullable: bool,
226    /// Whether field is a list
227    pub is_list: bool,
228    /// Description
229    pub description: Option<String>,
230    /// Arguments (for object type fields)
231    pub arguments: Vec<ArgumentDefinition>,
232    /// Applied directives
233    pub directives: Vec<DirectiveUse>,
234    /// Default value (for input fields)
235    pub default_value: Option<String>,
236    /// Whether field is deprecated
237    pub deprecated: bool,
238    /// Deprecation reason
239    pub deprecation_reason: Option<String>,
240}
241
242impl FieldDefinition {
243    /// Create a new field definition
244    pub fn new(name: &str, field_type: &str) -> Self {
245        Self {
246            name: name.to_string(),
247            field_type: field_type.to_string(),
248            nullable: true,
249            is_list: false,
250            description: None,
251            arguments: Vec::new(),
252            directives: Vec::new(),
253            default_value: None,
254            deprecated: false,
255            deprecation_reason: None,
256        }
257    }
258
259    /// Set nullable
260    pub fn non_null(mut self) -> Self {
261        self.nullable = false;
262        self
263    }
264
265    /// Set as list
266    pub fn list(mut self) -> Self {
267        self.is_list = true;
268        self
269    }
270
271    /// Set description
272    pub fn with_description(mut self, description: &str) -> Self {
273        self.description = Some(description.to_string());
274        self
275    }
276
277    /// Get type string for display
278    pub fn type_string(&self) -> String {
279        let mut s = self.field_type.clone();
280        if self.is_list {
281            s = format!("[{}]", s);
282        }
283        if !self.nullable {
284            s = format!("{}!", s);
285        }
286        s
287    }
288}
289
290/// Argument definition
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct ArgumentDefinition {
293    /// Argument name
294    pub name: String,
295    /// Argument type
296    pub arg_type: String,
297    /// Whether nullable
298    pub nullable: bool,
299    /// Default value
300    pub default_value: Option<String>,
301    /// Description
302    pub description: Option<String>,
303}
304
305impl ArgumentDefinition {
306    /// Create a new argument
307    pub fn new(name: &str, arg_type: &str) -> Self {
308        Self {
309            name: name.to_string(),
310            arg_type: arg_type.to_string(),
311            nullable: true,
312            default_value: None,
313            description: None,
314        }
315    }
316}
317
318/// Enum value
319#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct EnumValue {
321    /// Value name
322    pub name: String,
323    /// Description
324    pub description: Option<String>,
325    /// Whether deprecated
326    pub deprecated: bool,
327    /// Deprecation reason
328    pub deprecation_reason: Option<String>,
329}
330
331impl EnumValue {
332    /// Create a new enum value
333    pub fn new(name: &str) -> Self {
334        Self {
335            name: name.to_string(),
336            description: None,
337            deprecated: false,
338            deprecation_reason: None,
339        }
340    }
341}
342
343/// Directive usage
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct DirectiveUse {
346    /// Directive name
347    pub name: String,
348    /// Arguments
349    pub arguments: HashMap<String, String>,
350}
351
352/// Schema edge (relationship)
353#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct SchemaEdge {
355    /// Unique identifier
356    pub id: String,
357    /// Source node ID
358    pub source: String,
359    /// Target node ID
360    pub target: String,
361    /// Source field name (for field edges)
362    pub source_field: Option<String>,
363    /// Edge type
364    pub edge_type: EdgeType,
365    /// Label
366    pub label: Option<String>,
367    /// Whether edge is highlighted
368    pub highlighted: bool,
369}
370
371impl SchemaEdge {
372    /// Create a new edge
373    pub fn new(id: &str, source: &str, target: &str, edge_type: EdgeType) -> Self {
374        Self {
375            id: id.to_string(),
376            source: source.to_string(),
377            target: target.to_string(),
378            source_field: None,
379            edge_type,
380            label: None,
381            highlighted: false,
382        }
383    }
384
385    /// Set source field
386    pub fn with_source_field(mut self, field: &str) -> Self {
387        self.source_field = Some(field.to_string());
388        self
389    }
390
391    /// Set label
392    pub fn with_label(mut self, label: &str) -> Self {
393        self.label = Some(label.to_string());
394        self
395    }
396}
397
398/// Schema graph (visualization data)
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct SchemaGraph {
401    /// Nodes
402    pub nodes: Vec<SchemaNode>,
403    /// Edges
404    pub edges: Vec<SchemaEdge>,
405    /// Graph width
406    pub width: f64,
407    /// Graph height
408    pub height: f64,
409    /// Zoom level
410    pub zoom: f64,
411    /// Pan offset
412    pub pan: Position,
413}
414
415impl Default for SchemaGraph {
416    fn default() -> Self {
417        Self {
418            nodes: Vec::new(),
419            edges: Vec::new(),
420            width: 1000.0,
421            height: 800.0,
422            zoom: 1.0,
423            pan: Position::default(),
424        }
425    }
426}
427
428/// Layout algorithm
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
430pub enum LayoutAlgorithm {
431    /// Force-directed layout
432    #[default]
433    ForceDirected,
434    /// Hierarchical (top-down)
435    Hierarchical,
436    /// Circular layout
437    Circular,
438    /// Grid layout
439    Grid,
440    /// Manual (no auto-layout)
441    Manual,
442}
443
444/// Layout options
445#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct LayoutOptions {
447    /// Layout algorithm
448    pub algorithm: LayoutAlgorithm,
449    /// Node spacing
450    pub node_spacing: f64,
451    /// Layer spacing (for hierarchical)
452    pub layer_spacing: f64,
453    /// Iterations (for force-directed)
454    pub iterations: u32,
455    /// Animation duration (ms)
456    pub animation_duration: u64,
457    /// Group by type
458    pub group_by_type: bool,
459}
460
461impl Default for LayoutOptions {
462    fn default() -> Self {
463        Self {
464            algorithm: LayoutAlgorithm::default(),
465            node_spacing: 50.0,
466            layer_spacing: 100.0,
467            iterations: 100,
468            animation_duration: 300,
469            group_by_type: true,
470        }
471    }
472}
473
474/// Edit operation
475#[derive(Debug, Clone, Serialize, Deserialize)]
476pub enum EditOperation {
477    /// Add a node
478    AddNode { node: SchemaNode },
479    /// Remove a node
480    RemoveNode { node_id: String },
481    /// Update a node
482    UpdateNode { node: SchemaNode },
483    /// Add a field to a node
484    AddField {
485        node_id: String,
486        field: FieldDefinition,
487    },
488    /// Remove a field from a node
489    RemoveField { node_id: String, field_name: String },
490    /// Update a field
491    UpdateField {
492        node_id: String,
493        field: FieldDefinition,
494    },
495    /// Add an edge
496    AddEdge { edge: SchemaEdge },
497    /// Remove an edge
498    RemoveEdge { edge_id: String },
499    /// Move a node
500    MoveNode { node_id: String, position: Position },
501    /// Batch operation
502    Batch { operations: Vec<EditOperation> },
503}
504
505/// Validation result
506#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct ValidationResult {
508    /// Whether schema is valid
509    pub valid: bool,
510    /// Errors
511    pub errors: Vec<ValidationError>,
512    /// Warnings
513    pub warnings: Vec<ValidationWarning>,
514}
515
516/// Validation error
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct ValidationError {
519    /// Error code
520    pub code: String,
521    /// Error message
522    pub message: String,
523    /// Node ID (if applicable)
524    pub node_id: Option<String>,
525    /// Field name (if applicable)
526    pub field_name: Option<String>,
527}
528
529/// Validation warning
530#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct ValidationWarning {
532    /// Warning code
533    pub code: String,
534    /// Warning message
535    pub message: String,
536    /// Node ID (if applicable)
537    pub node_id: Option<String>,
538}
539
540/// Designer state
541struct DesignerState {
542    /// Schema graph
543    graph: SchemaGraph,
544    /// Node index
545    node_index: HashMap<String, usize>,
546    /// Edge index
547    edge_index: HashMap<String, usize>,
548    /// Edit history (for undo)
549    history: Vec<EditOperation>,
550    /// History position
551    history_position: usize,
552    /// Layout options
553    layout_options: LayoutOptions,
554}
555
556impl DesignerState {
557    fn new() -> Self {
558        Self {
559            graph: SchemaGraph::default(),
560            node_index: HashMap::new(),
561            edge_index: HashMap::new(),
562            history: Vec::new(),
563            history_position: 0,
564            layout_options: LayoutOptions::default(),
565        }
566    }
567
568    fn rebuild_indices(&mut self) {
569        self.node_index.clear();
570        for (i, node) in self.graph.nodes.iter().enumerate() {
571            self.node_index.insert(node.id.clone(), i);
572        }
573
574        self.edge_index.clear();
575        for (i, edge) in self.graph.edges.iter().enumerate() {
576            self.edge_index.insert(edge.id.clone(), i);
577        }
578    }
579}
580
581/// Visual Schema Designer
582///
583/// Provides an interface for creating and editing GraphQL schemas
584/// with visual representation and validation.
585pub struct SchemaDesigner {
586    /// Internal state
587    state: Arc<RwLock<DesignerState>>,
588}
589
590impl SchemaDesigner {
591    /// Create a new schema designer
592    pub fn new() -> Self {
593        Self {
594            state: Arc::new(RwLock::new(DesignerState::new())),
595        }
596    }
597
598    /// Get the current schema graph
599    pub async fn get_visualization(&self) -> SchemaGraph {
600        let state = self.state.read().await;
601        state.graph.clone()
602    }
603
604    /// Add a node
605    pub async fn add_node(&self, mut node: SchemaNode) -> Result<String> {
606        let node_id = if node.id.is_empty() {
607            uuid::Uuid::new_v4().to_string()
608        } else {
609            node.id.clone()
610        };
611        node.id = node_id.clone();
612
613        let mut state = self.state.write().await;
614
615        // Check for duplicate name
616        if state.graph.nodes.iter().any(|n| n.name == node.name) {
617            return Err(anyhow!("Type '{}' already exists", node.name));
618        }
619
620        // Calculate size
621        node.size.height = node.calculate_height();
622
623        state.graph.nodes.push(node.clone());
624        let node_index_value = state.graph.nodes.len() - 1;
625        state.node_index.insert(node_id.clone(), node_index_value);
626
627        // Record in history
628        let history_pos = state.history_position;
629        state.history.truncate(history_pos);
630        state.history.push(EditOperation::AddNode { node });
631        state.history_position = state.history.len();
632
633        Ok(node_id)
634    }
635
636    /// Remove a node
637    pub async fn remove_node(&self, node_id: &str) -> Result<()> {
638        let mut state = self.state.write().await;
639
640        let Some(&index) = state.node_index.get(node_id) else {
641            return Err(anyhow!("Node '{}' not found", node_id));
642        };
643
644        let _node = state.graph.nodes.remove(index);
645
646        // Remove related edges
647        state
648            .graph
649            .edges
650            .retain(|e| e.source != node_id && e.target != node_id);
651
652        // Rebuild indices
653        state.rebuild_indices();
654
655        // Record in history
656        let history_pos = state.history_position;
657        state.history.truncate(history_pos);
658        state.history.push(EditOperation::RemoveNode {
659            node_id: node_id.to_string(),
660        });
661        state.history_position = state.history.len();
662
663        Ok(())
664    }
665
666    /// Update a node
667    pub async fn update_node(&self, node: SchemaNode) -> Result<()> {
668        let mut state = self.state.write().await;
669
670        let Some(&index) = state.node_index.get(&node.id) else {
671            return Err(anyhow!("Node '{}' not found", node.id));
672        };
673
674        let mut updated_node = node.clone();
675        updated_node.size.height = updated_node.calculate_height();
676
677        state.graph.nodes[index] = updated_node;
678
679        // Record in history
680        let history_pos = state.history_position;
681        state.history.truncate(history_pos);
682        state.history.push(EditOperation::UpdateNode { node });
683        state.history_position = state.history.len();
684
685        Ok(())
686    }
687
688    /// Add a field to a node
689    pub async fn add_field(&self, node_id: &str, field: FieldDefinition) -> Result<()> {
690        let mut state = self.state.write().await;
691
692        let Some(&index) = state.node_index.get(node_id) else {
693            return Err(anyhow!("Node '{}' not found", node_id));
694        };
695
696        // Check for duplicate field name
697        if state.graph.nodes[index]
698            .fields
699            .iter()
700            .any(|f| f.name == field.name)
701        {
702            return Err(anyhow!(
703                "Field '{}' already exists in '{}'",
704                field.name,
705                node_id
706            ));
707        }
708
709        state.graph.nodes[index].fields.push(field.clone());
710        state.graph.nodes[index].size.height = state.graph.nodes[index].calculate_height();
711
712        // Create edge if field references another type
713        let target_type = field
714            .field_type
715            .trim_matches(|c| c == '[' || c == ']' || c == '!');
716        if let Some(target_node) = state.graph.nodes.iter().find(|n| n.name == target_type) {
717            let edge = SchemaEdge::new(
718                &uuid::Uuid::new_v4().to_string(),
719                node_id,
720                &target_node.id,
721                EdgeType::Field,
722            )
723            .with_source_field(&field.name);
724            state.graph.edges.push(edge);
725            state.rebuild_indices();
726        }
727
728        // Record in history
729        let history_pos = state.history_position;
730        state.history.truncate(history_pos);
731        state.history.push(EditOperation::AddField {
732            node_id: node_id.to_string(),
733            field,
734        });
735        state.history_position = state.history.len();
736
737        Ok(())
738    }
739
740    /// Remove a field from a node
741    pub async fn remove_field(&self, node_id: &str, field_name: &str) -> Result<()> {
742        let mut state = self.state.write().await;
743
744        let Some(&index) = state.node_index.get(node_id) else {
745            return Err(anyhow!("Node '{}' not found", node_id));
746        };
747
748        state.graph.nodes[index]
749            .fields
750            .retain(|f| f.name != field_name);
751        state.graph.nodes[index].size.height = state.graph.nodes[index].calculate_height();
752
753        // Remove related edges
754        state
755            .graph
756            .edges
757            .retain(|e| e.source != node_id || e.source_field.as_deref() != Some(field_name));
758        state.rebuild_indices();
759
760        // Record in history
761        let history_pos = state.history_position;
762        state.history.truncate(history_pos);
763        state.history.push(EditOperation::RemoveField {
764            node_id: node_id.to_string(),
765            field_name: field_name.to_string(),
766        });
767        state.history_position = state.history.len();
768
769        Ok(())
770    }
771
772    /// Move a node
773    pub async fn move_node(&self, node_id: &str, position: Position) -> Result<()> {
774        let mut state = self.state.write().await;
775
776        let Some(&index) = state.node_index.get(node_id) else {
777            return Err(anyhow!("Node '{}' not found", node_id));
778        };
779
780        state.graph.nodes[index].position = position;
781
782        Ok(())
783    }
784
785    /// Apply layout algorithm
786    pub async fn apply_layout(&self) -> Result<()> {
787        let mut state = self.state.write().await;
788
789        match state.layout_options.algorithm {
790            LayoutAlgorithm::ForceDirected => {
791                self.apply_force_directed_layout(&mut state)?;
792            }
793            LayoutAlgorithm::Hierarchical => {
794                self.apply_hierarchical_layout(&mut state)?;
795            }
796            LayoutAlgorithm::Circular => {
797                self.apply_circular_layout(&mut state)?;
798            }
799            LayoutAlgorithm::Grid => {
800                self.apply_grid_layout(&mut state)?;
801            }
802            LayoutAlgorithm::Manual => {
803                // No layout changes
804            }
805        }
806
807        Ok(())
808    }
809
810    fn apply_force_directed_layout(&self, state: &mut DesignerState) -> Result<()> {
811        let node_count = state.graph.nodes.len();
812        if node_count == 0 {
813            return Ok(());
814        }
815
816        // Initialize positions randomly if not set
817        for node in &mut state.graph.nodes {
818            if node.position.x == 0.0 && node.position.y == 0.0 {
819                node.position.x = fastrand::f64() * 800.0;
820                node.position.y = fastrand::f64() * 600.0;
821            }
822        }
823
824        let spacing = state.layout_options.node_spacing;
825        let iterations = state.layout_options.iterations;
826
827        // Simple force-directed simulation
828        for _ in 0..iterations {
829            let mut forces: Vec<Position> = vec![Position::default(); node_count];
830
831            // Repulsion between all nodes
832            for i in 0..node_count {
833                for j in (i + 1)..node_count {
834                    let dx = state.graph.nodes[j].position.x - state.graph.nodes[i].position.x;
835                    let dy = state.graph.nodes[j].position.y - state.graph.nodes[i].position.y;
836                    let dist = (dx * dx + dy * dy).sqrt().max(1.0);
837                    let repulsion = spacing * spacing / dist;
838
839                    let fx = repulsion * dx / dist;
840                    let fy = repulsion * dy / dist;
841
842                    forces[i].x -= fx;
843                    forces[i].y -= fy;
844                    forces[j].x += fx;
845                    forces[j].y += fy;
846                }
847            }
848
849            // Attraction along edges
850            for edge in &state.graph.edges {
851                let Some(&source_idx) = state.node_index.get(&edge.source) else {
852                    continue;
853                };
854                let Some(&target_idx) = state.node_index.get(&edge.target) else {
855                    continue;
856                };
857
858                let dx = state.graph.nodes[target_idx].position.x
859                    - state.graph.nodes[source_idx].position.x;
860                let dy = state.graph.nodes[target_idx].position.y
861                    - state.graph.nodes[source_idx].position.y;
862                let dist = (dx * dx + dy * dy).sqrt();
863
864                let attraction = dist / spacing;
865                let fx = attraction * dx / dist.max(1.0);
866                let fy = attraction * dy / dist.max(1.0);
867
868                forces[source_idx].x += fx * 0.1;
869                forces[source_idx].y += fy * 0.1;
870                forces[target_idx].x -= fx * 0.1;
871                forces[target_idx].y -= fy * 0.1;
872            }
873
874            // Apply forces with damping
875            let damping = 0.85;
876            for (i, node) in state.graph.nodes.iter_mut().enumerate() {
877                node.position.x += forces[i].x * damping;
878                node.position.y += forces[i].y * damping;
879
880                // Keep in bounds
881                node.position.x = node.position.x.max(50.0).min(state.graph.width - 50.0);
882                node.position.y = node.position.y.max(50.0).min(state.graph.height - 50.0);
883            }
884        }
885
886        Ok(())
887    }
888
889    fn apply_hierarchical_layout(&self, state: &mut DesignerState) -> Result<()> {
890        if state.graph.nodes.is_empty() {
891            return Ok(());
892        }
893
894        let spacing = state.layout_options.node_spacing;
895        let layer_spacing = state.layout_options.layer_spacing;
896
897        // Group by type if enabled
898        let groups: Vec<Vec<usize>> = if state.layout_options.group_by_type {
899            let mut type_groups: HashMap<NodeType, Vec<usize>> = HashMap::new();
900            for (i, node) in state.graph.nodes.iter().enumerate() {
901                type_groups.entry(node.node_type).or_default().push(i);
902            }
903            type_groups.into_values().collect()
904        } else {
905            vec![(0..state.graph.nodes.len()).collect()]
906        };
907
908        let mut y = 50.0;
909        for group in groups {
910            let mut x = 50.0;
911            for idx in group {
912                state.graph.nodes[idx].position.x = x;
913                state.graph.nodes[idx].position.y = y;
914                x += state.graph.nodes[idx].size.width + spacing;
915            }
916            y += layer_spacing;
917        }
918
919        Ok(())
920    }
921
922    fn apply_circular_layout(&self, state: &mut DesignerState) -> Result<()> {
923        let node_count = state.graph.nodes.len();
924        if node_count == 0 {
925            return Ok(());
926        }
927
928        let center_x = state.graph.width / 2.0;
929        let center_y = state.graph.height / 2.0;
930        let radius = (state.graph.width.min(state.graph.height) / 2.0) - 150.0;
931
932        for (i, node) in state.graph.nodes.iter_mut().enumerate() {
933            let angle = 2.0 * std::f64::consts::PI * (i as f64) / (node_count as f64);
934            node.position.x = center_x + radius * angle.cos();
935            node.position.y = center_y + radius * angle.sin();
936        }
937
938        Ok(())
939    }
940
941    fn apply_grid_layout(&self, state: &mut DesignerState) -> Result<()> {
942        let node_count = state.graph.nodes.len();
943        if node_count == 0 {
944            return Ok(());
945        }
946
947        let cols = (node_count as f64).sqrt().ceil() as usize;
948        let spacing = state.layout_options.node_spacing;
949
950        for (i, node) in state.graph.nodes.iter_mut().enumerate() {
951            let row = i / cols;
952            let col = i % cols;
953            node.position.x = 50.0 + (col as f64) * (node.size.width + spacing);
954            node.position.y = 50.0 + (row as f64) * (node.size.height + spacing);
955        }
956
957        Ok(())
958    }
959
960    /// Set layout options
961    pub async fn set_layout_options(&self, options: LayoutOptions) {
962        let mut state = self.state.write().await;
963        state.layout_options = options;
964    }
965
966    /// Validate the schema
967    pub async fn validate(&self) -> ValidationResult {
968        let state = self.state.read().await;
969
970        let mut errors = Vec::new();
971        let mut warnings = Vec::new();
972
973        // Check for Query type
974        if !state.graph.nodes.iter().any(|n| n.name == "Query") {
975            errors.push(ValidationError {
976                code: "MISSING_QUERY".to_string(),
977                message: "Schema must have a Query type".to_string(),
978                node_id: None,
979                field_name: None,
980            });
981        }
982
983        // Check for unresolved type references
984        let type_names: HashSet<_> = state.graph.nodes.iter().map(|n| n.name.as_str()).collect();
985        let builtin_types: HashSet<&str> = ["String", "Int", "Float", "Boolean", "ID"]
986            .iter()
987            .copied()
988            .collect();
989
990        for node in &state.graph.nodes {
991            for field in &node.fields {
992                let field_type = field
993                    .field_type
994                    .trim_matches(|c| c == '[' || c == ']' || c == '!');
995                if !type_names.contains(field_type) && !builtin_types.contains(field_type) {
996                    errors.push(ValidationError {
997                        code: "UNRESOLVED_TYPE".to_string(),
998                        message: format!("Unknown type '{}' in field '{}'", field_type, field.name),
999                        node_id: Some(node.id.clone()),
1000                        field_name: Some(field.name.clone()),
1001                    });
1002                }
1003            }
1004
1005            // Check for empty types
1006            if matches!(
1007                node.node_type,
1008                NodeType::ObjectType | NodeType::InterfaceType | NodeType::InputType
1009            ) && node.fields.is_empty()
1010            {
1011                warnings.push(ValidationWarning {
1012                    code: "EMPTY_TYPE".to_string(),
1013                    message: format!("Type '{}' has no fields", node.name),
1014                    node_id: Some(node.id.clone()),
1015                });
1016            }
1017
1018            // Check for empty enum
1019            if node.node_type == NodeType::EnumType && node.enum_values.is_empty() {
1020                errors.push(ValidationError {
1021                    code: "EMPTY_ENUM".to_string(),
1022                    message: format!("Enum '{}' has no values", node.name),
1023                    node_id: Some(node.id.clone()),
1024                    field_name: None,
1025                });
1026            }
1027        }
1028
1029        ValidationResult {
1030            valid: errors.is_empty(),
1031            errors,
1032            warnings,
1033        }
1034    }
1035
1036    /// Export to SDL (Schema Definition Language)
1037    pub async fn export_sdl(&self) -> String {
1038        let state = self.state.read().await;
1039        let mut sdl = String::new();
1040
1041        for node in &state.graph.nodes {
1042            // Add description
1043            if let Some(desc) = &node.description {
1044                sdl.push_str(&format!("\"\"\"{}\"\"\"\n", desc));
1045            }
1046
1047            match node.node_type {
1048                NodeType::ObjectType => {
1049                    let implements = if !node.implements.is_empty() {
1050                        format!(" implements {}", node.implements.join(" & "))
1051                    } else {
1052                        String::new()
1053                    };
1054                    sdl.push_str(&format!("type {}{} {{\n", node.name, implements));
1055                    for field in &node.fields {
1056                        if let Some(desc) = &field.description {
1057                            sdl.push_str(&format!("  \"\"\"{}\"\"\"\n", desc));
1058                        }
1059                        sdl.push_str(&format!("  {}: {}\n", field.name, field.type_string()));
1060                    }
1061                    sdl.push_str("}\n\n");
1062                }
1063                NodeType::InterfaceType => {
1064                    sdl.push_str(&format!("interface {} {{\n", node.name));
1065                    for field in &node.fields {
1066                        sdl.push_str(&format!("  {}: {}\n", field.name, field.type_string()));
1067                    }
1068                    sdl.push_str("}\n\n");
1069                }
1070                NodeType::InputType => {
1071                    sdl.push_str(&format!("input {} {{\n", node.name));
1072                    for field in &node.fields {
1073                        sdl.push_str(&format!("  {}: {}\n", field.name, field.type_string()));
1074                    }
1075                    sdl.push_str("}\n\n");
1076                }
1077                NodeType::EnumType => {
1078                    sdl.push_str(&format!("enum {} {{\n", node.name));
1079                    for value in &node.enum_values {
1080                        sdl.push_str(&format!("  {}\n", value.name));
1081                    }
1082                    sdl.push_str("}\n\n");
1083                }
1084                NodeType::UnionType => {
1085                    sdl.push_str(&format!(
1086                        "union {} = {}\n\n",
1087                        node.name,
1088                        node.union_members.join(" | ")
1089                    ));
1090                }
1091                NodeType::ScalarType => {
1092                    sdl.push_str(&format!("scalar {}\n\n", node.name));
1093                }
1094                NodeType::Directive => {
1095                    // Skip directive definitions in basic export
1096                }
1097            }
1098        }
1099
1100        sdl
1101    }
1102
1103    /// Import from SDL
1104    pub async fn import_sdl(&self, sdl: &str) -> Result<()> {
1105        // Basic SDL parser - in production, use a proper GraphQL parser
1106        let mut state = self.state.write().await;
1107        state.graph.nodes.clear();
1108        state.graph.edges.clear();
1109
1110        let lines: Vec<&str> = sdl.lines().collect();
1111        let mut current_node: Option<SchemaNode> = None;
1112        let mut in_type = false;
1113
1114        for line in lines {
1115            let line = line.trim();
1116
1117            if line.starts_with("type ")
1118                || line.starts_with("interface ")
1119                || line.starts_with("input ")
1120                || line.starts_with("enum ")
1121            {
1122                // Save previous node
1123                if let Some(node) = current_node.take() {
1124                    state.graph.nodes.push(node);
1125                }
1126
1127                let (kind, rest) = if let Some(rest) = line.strip_prefix("type ") {
1128                    (NodeType::ObjectType, rest)
1129                } else if let Some(rest) = line.strip_prefix("interface ") {
1130                    (NodeType::InterfaceType, rest)
1131                } else if let Some(rest) = line.strip_prefix("input ") {
1132                    (NodeType::InputType, rest)
1133                } else if let Some(rest) = line.strip_prefix("enum ") {
1134                    (NodeType::EnumType, rest)
1135                } else {
1136                    continue;
1137                };
1138
1139                let name = rest
1140                    .split(|c: char| c.is_whitespace() || c == '{')
1141                    .next()
1142                    .unwrap_or("");
1143                current_node = Some(SchemaNode::new(
1144                    &uuid::Uuid::new_v4().to_string(),
1145                    name,
1146                    kind,
1147                ));
1148                in_type = true;
1149            } else if line == "}" {
1150                if let Some(node) = current_node.take() {
1151                    state.graph.nodes.push(node);
1152                }
1153                in_type = false;
1154            } else if in_type && !line.is_empty() && !line.starts_with("\"\"\"") {
1155                if let Some(ref mut node) = current_node {
1156                    // Parse field
1157                    if let Some((name, type_part)) = line.split_once(':') {
1158                        let name = name.trim();
1159                        let type_str = type_part.trim().trim_end_matches(',');
1160                        let field = FieldDefinition::new(name, type_str);
1161                        node.fields.push(field);
1162                    } else if node.node_type == NodeType::EnumType {
1163                        // Enum value
1164                        node.enum_values.push(EnumValue::new(line));
1165                    }
1166                }
1167            } else if let Some(rest) = line.strip_prefix("scalar ") {
1168                let name = rest.trim();
1169                let node = SchemaNode::new(
1170                    &uuid::Uuid::new_v4().to_string(),
1171                    name,
1172                    NodeType::ScalarType,
1173                );
1174                state.graph.nodes.push(node);
1175            } else if let Some(rest) = line.strip_prefix("union ") {
1176                if let Some((name, members)) = rest.split_once('=') {
1177                    let name = name.trim();
1178                    let members: Vec<String> =
1179                        members.split('|').map(|s| s.trim().to_string()).collect();
1180                    let mut node = SchemaNode::new(
1181                        &uuid::Uuid::new_v4().to_string(),
1182                        name,
1183                        NodeType::UnionType,
1184                    );
1185                    node.union_members = members;
1186                    state.graph.nodes.push(node);
1187                }
1188            }
1189        }
1190
1191        // Save last node
1192        if let Some(node) = current_node {
1193            state.graph.nodes.push(node);
1194        }
1195
1196        // Rebuild indices
1197        state.rebuild_indices();
1198
1199        // Create edges for field references
1200        self.create_edges_from_fields(&mut state);
1201
1202        // Apply layout
1203        drop(state);
1204        self.apply_layout().await?;
1205
1206        Ok(())
1207    }
1208
1209    fn create_edges_from_fields(&self, state: &mut DesignerState) {
1210        let type_map: HashMap<String, String> = state
1211            .graph
1212            .nodes
1213            .iter()
1214            .map(|n| (n.name.clone(), n.id.clone()))
1215            .collect();
1216
1217        let builtin_types: HashSet<&str> = ["String", "Int", "Float", "Boolean", "ID"]
1218            .iter()
1219            .copied()
1220            .collect();
1221
1222        let mut edges = Vec::new();
1223
1224        for node in &state.graph.nodes {
1225            for field in &node.fields {
1226                let field_type = field
1227                    .field_type
1228                    .trim_matches(|c| c == '[' || c == ']' || c == '!');
1229
1230                if builtin_types.contains(field_type) {
1231                    continue;
1232                }
1233
1234                if let Some(target_id) = type_map.get(field_type) {
1235                    let edge = SchemaEdge::new(
1236                        &uuid::Uuid::new_v4().to_string(),
1237                        &node.id,
1238                        target_id,
1239                        EdgeType::Field,
1240                    )
1241                    .with_source_field(&field.name);
1242                    edges.push(edge);
1243                }
1244            }
1245
1246            // Implements edges
1247            for interface in &node.implements {
1248                if let Some(target_id) = type_map.get(interface) {
1249                    let edge = SchemaEdge::new(
1250                        &uuid::Uuid::new_v4().to_string(),
1251                        &node.id,
1252                        target_id,
1253                        EdgeType::Implements,
1254                    );
1255                    edges.push(edge);
1256                }
1257            }
1258
1259            // Union member edges
1260            for member in &node.union_members {
1261                if let Some(target_id) = type_map.get(member) {
1262                    let edge = SchemaEdge::new(
1263                        &uuid::Uuid::new_v4().to_string(),
1264                        &node.id,
1265                        target_id,
1266                        EdgeType::UnionMember,
1267                    );
1268                    edges.push(edge);
1269                }
1270            }
1271        }
1272
1273        state.graph.edges = edges;
1274        state.rebuild_indices();
1275    }
1276
1277    /// Can undo
1278    pub async fn can_undo(&self) -> bool {
1279        let state = self.state.read().await;
1280        state.history_position > 0
1281    }
1282
1283    /// Undo last operation
1284    pub async fn undo(&self) -> Result<()> {
1285        let mut state = self.state.write().await;
1286        if state.history_position == 0 {
1287            return Err(anyhow!("Nothing to undo"));
1288        }
1289        state.history_position -= 1;
1290        // In a full implementation, would reverse the operation
1291        Ok(())
1292    }
1293
1294    /// Can redo
1295    pub async fn can_redo(&self) -> bool {
1296        let state = self.state.read().await;
1297        state.history_position < state.history.len()
1298    }
1299
1300    /// Redo operation
1301    pub async fn redo(&self) -> Result<()> {
1302        let mut state = self.state.write().await;
1303        if state.history_position >= state.history.len() {
1304            return Err(anyhow!("Nothing to redo"));
1305        }
1306        state.history_position += 1;
1307        // In a full implementation, would reapply the operation
1308        Ok(())
1309    }
1310}
1311
1312impl Default for SchemaDesigner {
1313    fn default() -> Self {
1314        Self::new()
1315    }
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320    use super::*;
1321
1322    #[tokio::test]
1323    async fn test_designer_creation() {
1324        let designer = SchemaDesigner::new();
1325        let graph = designer.get_visualization().await;
1326        assert!(graph.nodes.is_empty());
1327    }
1328
1329    #[tokio::test]
1330    async fn test_add_node() {
1331        let designer = SchemaDesigner::new();
1332        let node = SchemaNode::new("", "User", NodeType::ObjectType);
1333        let node_id = designer.add_node(node).await.expect("should succeed");
1334
1335        let graph = designer.get_visualization().await;
1336        assert_eq!(graph.nodes.len(), 1);
1337        assert_eq!(graph.nodes[0].id, node_id);
1338    }
1339
1340    #[tokio::test]
1341    async fn test_add_field() {
1342        let designer = SchemaDesigner::new();
1343        let node = SchemaNode::new("", "User", NodeType::ObjectType);
1344        let node_id = designer.add_node(node).await.expect("should succeed");
1345
1346        let field = FieldDefinition::new("name", "String").non_null();
1347        designer
1348            .add_field(&node_id, field)
1349            .await
1350            .expect("should succeed");
1351
1352        let graph = designer.get_visualization().await;
1353        assert_eq!(graph.nodes[0].fields.len(), 1);
1354        assert_eq!(graph.nodes[0].fields[0].name, "name");
1355    }
1356
1357    #[tokio::test]
1358    async fn test_validation() {
1359        let designer = SchemaDesigner::new();
1360
1361        // Empty schema should fail validation
1362        let result = designer.validate().await;
1363        assert!(!result.valid);
1364        assert!(result.errors.iter().any(|e| e.code == "MISSING_QUERY"));
1365
1366        // Add Query type
1367        let query = SchemaNode::new("", "Query", NodeType::ObjectType)
1368            .with_field(FieldDefinition::new("hello", "String"));
1369        designer.add_node(query).await.expect("should succeed");
1370
1371        let result = designer.validate().await;
1372        assert!(result.valid);
1373    }
1374
1375    #[tokio::test]
1376    async fn test_export_sdl() {
1377        let designer = SchemaDesigner::new();
1378
1379        let user = SchemaNode::new("", "User", NodeType::ObjectType)
1380            .with_field(FieldDefinition::new("id", "ID").non_null())
1381            .with_field(FieldDefinition::new("name", "String"));
1382        designer.add_node(user).await.expect("should succeed");
1383
1384        let sdl = designer.export_sdl().await;
1385        assert!(sdl.contains("type User"));
1386        assert!(sdl.contains("id: ID!"));
1387        assert!(sdl.contains("name: String"));
1388    }
1389
1390    #[tokio::test]
1391    async fn test_import_sdl() {
1392        let designer = SchemaDesigner::new();
1393
1394        let sdl = r#"
1395            type Query {
1396                users: [User]
1397            }
1398
1399            type User {
1400                id: ID!
1401                name: String
1402            }
1403        "#;
1404
1405        designer.import_sdl(sdl).await.expect("should succeed");
1406
1407        let graph = designer.get_visualization().await;
1408        assert_eq!(graph.nodes.len(), 2);
1409    }
1410
1411    #[tokio::test]
1412    async fn test_layout_algorithms() {
1413        let designer = SchemaDesigner::new();
1414
1415        // Add some nodes
1416        for i in 0..5 {
1417            let node = SchemaNode::new("", &format!("Type{}", i), NodeType::ObjectType);
1418            designer.add_node(node).await.expect("should succeed");
1419        }
1420
1421        // Test force-directed
1422        designer
1423            .set_layout_options(LayoutOptions {
1424                algorithm: LayoutAlgorithm::ForceDirected,
1425                ..Default::default()
1426            })
1427            .await;
1428        designer.apply_layout().await.expect("should succeed");
1429
1430        // Test circular
1431        designer
1432            .set_layout_options(LayoutOptions {
1433                algorithm: LayoutAlgorithm::Circular,
1434                ..Default::default()
1435            })
1436            .await;
1437        designer.apply_layout().await.expect("should succeed");
1438
1439        // Test grid
1440        designer
1441            .set_layout_options(LayoutOptions {
1442                algorithm: LayoutAlgorithm::Grid,
1443                ..Default::default()
1444            })
1445            .await;
1446        designer.apply_layout().await.expect("should succeed");
1447
1448        let graph = designer.get_visualization().await;
1449        // All nodes should have non-zero positions after layout
1450        for node in &graph.nodes {
1451            assert!(node.position.x > 0.0 || node.position.y > 0.0);
1452        }
1453    }
1454
1455    #[tokio::test]
1456    async fn test_remove_node() {
1457        let designer = SchemaDesigner::new();
1458
1459        let node = SchemaNode::new("", "User", NodeType::ObjectType);
1460        let node_id = designer.add_node(node).await.expect("should succeed");
1461
1462        designer
1463            .remove_node(&node_id)
1464            .await
1465            .expect("should succeed");
1466
1467        let graph = designer.get_visualization().await;
1468        assert!(graph.nodes.is_empty());
1469    }
1470
1471    #[tokio::test]
1472    async fn test_field_type_string() {
1473        let field = FieldDefinition::new("items", "Item").list().non_null();
1474        assert_eq!(field.type_string(), "[Item]!");
1475    }
1476
1477    #[tokio::test]
1478    async fn test_position_distance() {
1479        let p1 = Position::new(0.0, 0.0);
1480        let p2 = Position::new(3.0, 4.0);
1481        assert!((p1.distance_to(&p2) - 5.0).abs() < 0.0001);
1482    }
1483}