codeprism_core/
graph.rs

1//! Graph storage and query engine for code intelligence
2//!
3//! This module provides in-memory graph storage with efficient querying capabilities
4//! for supporting advanced MCP tools like trace_path, find_references, etc.
5
6use crate::ast::{Edge, EdgeKind, Node, NodeId, NodeKind};
7use crate::error::Result;
8use dashmap::DashMap;
9use regex;
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, HashSet, VecDeque};
12use std::path::PathBuf;
13use std::sync::Arc;
14
15/// In-memory graph store for code intelligence
16#[derive(Debug)]
17pub struct GraphStore {
18    /// All nodes indexed by their ID
19    nodes: Arc<DashMap<NodeId, Node>>,
20    /// Outgoing edges from each node
21    outgoing_edges: Arc<DashMap<NodeId, Vec<Edge>>>,
22    /// Incoming edges to each node
23    incoming_edges: Arc<DashMap<NodeId, Vec<Edge>>>,
24    /// Index of nodes by file path
25    file_index: Arc<DashMap<PathBuf, Vec<NodeId>>>,
26    /// Index of nodes by symbol name
27    symbol_index: Arc<DashMap<String, Vec<NodeId>>>,
28    /// Index of nodes by kind
29    kind_index: Arc<DashMap<NodeKind, Vec<NodeId>>>,
30}
31
32impl GraphStore {
33    /// Create a new empty graph store
34    pub fn new() -> Self {
35        Self {
36            nodes: Arc::new(DashMap::new()),
37            outgoing_edges: Arc::new(DashMap::new()),
38            incoming_edges: Arc::new(DashMap::new()),
39            file_index: Arc::new(DashMap::new()),
40            symbol_index: Arc::new(DashMap::new()),
41            kind_index: Arc::new(DashMap::new()),
42        }
43    }
44
45    /// Add a node to the graph
46    pub fn add_node(&self, node: Node) {
47        let node_id = node.id;
48
49        // Add to file index
50        self.file_index
51            .entry(node.file.clone())
52            .or_default()
53            .push(node_id);
54
55        // Add to symbol index
56        self.symbol_index
57            .entry(node.name.clone())
58            .or_default()
59            .push(node_id);
60
61        // Add to kind index
62        self.kind_index.entry(node.kind).or_default().push(node_id);
63
64        // Add the node
65        self.nodes.insert(node_id, node);
66    }
67
68    /// Add an edge to the graph
69    pub fn add_edge(&self, edge: Edge) {
70        // Add to outgoing edges
71        self.outgoing_edges
72            .entry(edge.source)
73            .or_default()
74            .push(edge.clone());
75
76        // Add to incoming edges
77        self.incoming_edges
78            .entry(edge.target)
79            .or_default()
80            .push(edge);
81    }
82
83    /// Get a node by ID
84    pub fn get_node(&self, node_id: &NodeId) -> Option<Node> {
85        self.nodes.get(node_id).map(|entry| entry.clone())
86    }
87
88    /// Get all nodes in a file
89    pub fn get_nodes_in_file(&self, file_path: &PathBuf) -> Vec<Node> {
90        if let Some(node_ids) = self.file_index.get(file_path) {
91            node_ids.iter().filter_map(|id| self.get_node(id)).collect()
92        } else {
93            Vec::new()
94        }
95    }
96
97    /// Get nodes by symbol name
98    pub fn get_nodes_by_name(&self, name: &str) -> Vec<Node> {
99        if let Some(node_ids) = self.symbol_index.get(name) {
100            node_ids.iter().filter_map(|id| self.get_node(id)).collect()
101        } else {
102            Vec::new()
103        }
104    }
105
106    /// Get nodes by kind
107    pub fn get_nodes_by_kind(&self, kind: NodeKind) -> Vec<Node> {
108        if let Some(node_ids) = self.kind_index.get(&kind) {
109            node_ids.iter().filter_map(|id| self.get_node(id)).collect()
110        } else {
111            Vec::new()
112        }
113    }
114
115    /// Get outgoing edges from a node
116    pub fn get_outgoing_edges(&self, node_id: &NodeId) -> Vec<Edge> {
117        self.outgoing_edges
118            .get(node_id)
119            .map(|edges| edges.clone())
120            .unwrap_or_default()
121    }
122
123    /// Get incoming edges to a node
124    pub fn get_incoming_edges(&self, node_id: &NodeId) -> Vec<Edge> {
125        self.incoming_edges
126            .get(node_id)
127            .map(|edges| edges.clone())
128            .unwrap_or_default()
129    }
130
131    /// Get graph statistics
132    pub fn get_stats(&self) -> GraphStats {
133        GraphStats {
134            total_nodes: self.nodes.len(),
135            total_edges: self.outgoing_edges.iter().map(|entry| entry.len()).sum(),
136            total_files: self.file_index.len(),
137            nodes_by_kind: self
138                .kind_index
139                .iter()
140                .map(|entry| (*entry.key(), entry.len()))
141                .collect(),
142        }
143    }
144
145    /// Clear all data from the graph
146    pub fn clear(&self) {
147        self.nodes.clear();
148        self.outgoing_edges.clear();
149        self.incoming_edges.clear();
150        self.file_index.clear();
151        self.symbol_index.clear();
152        self.kind_index.clear();
153    }
154
155    /// Remove a node and all its edges
156    pub fn remove_node(&self, node_id: &NodeId) -> Option<Node> {
157        if let Some((_, node)) = self.nodes.remove(node_id) {
158            // Remove from indices
159            if let Some(mut file_nodes) = self.file_index.get_mut(&node.file) {
160                file_nodes.retain(|id| id != node_id);
161            }
162
163            if let Some(mut symbol_nodes) = self.symbol_index.get_mut(&node.name) {
164                symbol_nodes.retain(|id| id != node_id);
165            }
166
167            if let Some(mut kind_nodes) = self.kind_index.get_mut(&node.kind) {
168                kind_nodes.retain(|id| id != node_id);
169            }
170
171            // Remove edges
172            self.outgoing_edges.remove(node_id);
173            self.incoming_edges.remove(node_id);
174
175            // Remove edges that reference this node
176            for mut edges in self.outgoing_edges.iter_mut() {
177                edges.retain(|edge| edge.target != *node_id);
178            }
179
180            for mut edges in self.incoming_edges.iter_mut() {
181                edges.retain(|edge| edge.source != *node_id);
182            }
183
184            Some(node)
185        } else {
186            None
187        }
188    }
189
190    /// Get all file paths in the index
191    pub fn get_all_files(&self) -> Vec<PathBuf> {
192        self.file_index
193            .iter()
194            .map(|entry| entry.key().clone())
195            .collect()
196    }
197
198    /// Iterate over file index entries (file path -> node IDs)
199    pub fn iter_file_index(&self) -> impl Iterator<Item = (PathBuf, Vec<NodeId>)> + '_ {
200        self.file_index
201            .iter()
202            .map(|entry| (entry.key().clone(), entry.value().clone()))
203    }
204
205    /// Iterate over symbol index entries (symbol name -> node IDs)
206    pub fn iter_symbol_index(&self) -> impl Iterator<Item = (String, Vec<NodeId>)> + '_ {
207        self.symbol_index
208            .iter()
209            .map(|entry| (entry.key().clone(), entry.value().clone()))
210    }
211
212    /// Get nodes by file path
213    pub fn get_nodes_by_file(&self, file_path: &PathBuf) -> Vec<NodeId> {
214        self.file_index
215            .get(file_path)
216            .map(|ids| ids.clone())
217            .unwrap_or_default()
218    }
219
220    /// Get nodes by symbol name
221    pub fn get_node_ids_by_name(&self, name: &str) -> Vec<NodeId> {
222        self.symbol_index
223            .get(name)
224            .map(|ids| ids.clone())
225            .unwrap_or_default()
226    }
227}
228
229impl Default for GraphStore {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235/// Graph statistics
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct GraphStats {
238    /// Total number of nodes
239    pub total_nodes: usize,
240    /// Total number of edges
241    pub total_edges: usize,
242    /// Total number of files
243    pub total_files: usize,
244    /// Nodes grouped by kind
245    pub nodes_by_kind: HashMap<NodeKind, usize>,
246}
247
248/// Graph query engine for advanced operations
249pub struct GraphQuery {
250    graph: Arc<GraphStore>,
251}
252
253impl GraphQuery {
254    /// Create a new graph query engine
255    pub fn new(graph: Arc<GraphStore>) -> Self {
256        Self { graph }
257    }
258
259    /// Find the shortest path between two nodes
260    pub fn find_path(
261        &self,
262        source: &NodeId,
263        target: &NodeId,
264        max_depth: Option<usize>,
265    ) -> Result<Option<PathResult>> {
266        let max_depth = max_depth.unwrap_or(10);
267
268        if source == target {
269            return Ok(Some(PathResult {
270                source: *source,
271                target: *target,
272                path: vec![*source],
273                distance: 0,
274                edges: Vec::new(),
275            }));
276        }
277
278        let mut queue = VecDeque::new();
279        let mut visited = HashSet::new();
280        let mut parent = HashMap::new();
281        let mut edge_map = HashMap::new();
282
283        queue.push_back((*source, 0));
284        visited.insert(*source);
285
286        while let Some((current, depth)) = queue.pop_front() {
287            if depth >= max_depth {
288                continue;
289            }
290
291            for edge in self.graph.get_outgoing_edges(&current) {
292                if !visited.contains(&edge.target) {
293                    visited.insert(edge.target);
294                    parent.insert(edge.target, current);
295                    edge_map.insert(edge.target, edge.clone());
296                    queue.push_back((edge.target, depth + 1));
297
298                    if edge.target == *target {
299                        // Reconstruct path
300                        let mut path = Vec::new();
301                        let mut edges = Vec::new();
302                        let mut current_node = *target;
303
304                        // Build path from target back to source
305                        path.push(current_node);
306                        while let Some(&prev) = parent.get(&current_node) {
307                            if let Some(edge) = edge_map.get(&current_node) {
308                                edges.push(edge.clone());
309                            }
310                            current_node = prev;
311                            path.push(current_node);
312                        }
313
314                        // Reverse to get path from source to target
315                        path.reverse();
316                        edges.reverse();
317
318                        let distance = path.len() - 1; // Number of edges, not nodes
319
320                        return Ok(Some(PathResult {
321                            source: *source,
322                            target: *target,
323                            path,
324                            distance,
325                            edges,
326                        }));
327                    }
328                }
329            }
330        }
331
332        Ok(None)
333    }
334
335    /// Find all references to a symbol (incoming edges)
336    pub fn find_references(&self, node_id: &NodeId) -> Result<Vec<SymbolReference>> {
337        let mut references = Vec::new();
338
339        for edge in self.graph.get_incoming_edges(node_id) {
340            if let Some(source_node) = self.graph.get_node(&edge.source) {
341                references.push(SymbolReference {
342                    location: ReferenceLocation {
343                        file: source_node.file.clone(),
344                        span: source_node.span.clone(),
345                    },
346                    source_node,
347                    edge_kind: edge.kind,
348                });
349            }
350        }
351
352        Ok(references)
353    }
354
355    /// Find all dependencies of a node (outgoing edges)
356    pub fn find_dependencies(
357        &self,
358        node_id: &NodeId,
359        dependency_type: DependencyType,
360    ) -> Result<Vec<SymbolDependency>> {
361        let mut dependencies = Vec::new();
362
363        for edge in self.graph.get_outgoing_edges(node_id) {
364            let include_edge = match dependency_type {
365                DependencyType::Direct => true,
366                DependencyType::Calls => matches!(edge.kind, EdgeKind::Calls),
367                DependencyType::Imports => matches!(edge.kind, EdgeKind::Imports),
368                DependencyType::Reads => matches!(edge.kind, EdgeKind::Reads),
369                DependencyType::Writes => matches!(edge.kind, EdgeKind::Writes),
370            };
371
372            if include_edge {
373                if let Some(target_node) = self.graph.get_node(&edge.target) {
374                    dependencies.push(SymbolDependency {
375                        target_node,
376                        edge_kind: edge.kind,
377                        dependency_type: dependency_type.clone(),
378                    });
379                }
380            }
381        }
382
383        Ok(dependencies)
384    }
385
386    /// Search symbols by name pattern (regex or fuzzy)
387    pub fn search_symbols(
388        &self,
389        pattern: &str,
390        symbol_types: Option<Vec<NodeKind>>,
391        limit: Option<usize>,
392    ) -> Result<Vec<SymbolInfo>> {
393        let limit = limit.unwrap_or(50);
394        let mut results = Vec::new();
395
396        // Try to compile as regex first, fall back to substring search if invalid
397        let regex_result = regex::Regex::new(pattern);
398        let use_regex = regex_result.is_ok();
399        let regex = regex_result.ok();
400
401        for entry in self.graph.symbol_index.iter() {
402            let symbol_name = entry.key();
403
404            let matches = if use_regex {
405                // Use regex matching
406                regex.as_ref().unwrap().is_match(symbol_name)
407            } else {
408                // Fall back to case-insensitive substring search
409                symbol_name.to_lowercase().contains(&pattern.to_lowercase())
410            };
411
412            if matches {
413                for node_id in entry.value() {
414                    if let Some(node) = self.graph.get_node(node_id) {
415                        // Filter by symbol types if specified
416                        if let Some(ref types) = symbol_types {
417                            if !types.contains(&node.kind) {
418                                continue;
419                            }
420                        }
421
422                        results.push(SymbolInfo {
423                            node,
424                            references_count: self.graph.get_incoming_edges(node_id).len(),
425                            dependencies_count: self.graph.get_outgoing_edges(node_id).len(),
426                        });
427
428                        if results.len() >= limit {
429                            break;
430                        }
431                    }
432                }
433
434                if results.len() >= limit {
435                    break;
436                }
437            }
438        }
439
440        Ok(results)
441    }
442
443    /// Search symbols by name pattern with inheritance filters
444    pub fn search_symbols_with_inheritance(
445        &self,
446        pattern: &str,
447        symbol_types: Option<Vec<NodeKind>>,
448        inheritance_filters: Option<Vec<InheritanceFilter>>,
449        limit: Option<usize>,
450    ) -> Result<Vec<SymbolInfo>> {
451        let mut results = self.search_symbols(pattern, symbol_types, limit)?;
452
453        if let Some(filters) = inheritance_filters {
454            results.retain(|symbol_info| {
455                filters
456                    .iter()
457                    .any(|filter| self.matches_inheritance_filter(&symbol_info.node, filter))
458            });
459        }
460
461        Ok(results)
462    }
463
464    /// Check if a node matches an inheritance filter
465    fn matches_inheritance_filter(&self, node: &Node, filter: &InheritanceFilter) -> bool {
466        match filter {
467            InheritanceFilter::InheritsFrom(base_class) => {
468                self.inherits_from(&node.id, base_class).unwrap_or(false)
469            }
470            InheritanceFilter::HasMetaclass(metaclass) => {
471                self.has_metaclass(&node.id, metaclass).unwrap_or(false)
472            }
473            InheritanceFilter::UsesMixin(mixin) => {
474                self.uses_mixin(&node.id, mixin).unwrap_or(false)
475            }
476        }
477    }
478
479    /// Get comprehensive inheritance information for a class
480    pub fn get_inheritance_info(&self, node_id: &NodeId) -> Result<InheritanceInfo> {
481        let node = self
482            .graph
483            .get_node(node_id)
484            .ok_or_else(|| crate::error::Error::node_not_found(node_id.to_hex()))?;
485
486        if !matches!(node.kind, NodeKind::Class) {
487            return Ok(InheritanceInfo::default());
488        }
489
490        let base_classes = self.get_base_classes(node_id)?;
491        let subclasses = self.get_subclasses(node_id)?;
492        let metaclass = self.get_metaclass(node_id)?;
493        let mixins = self.get_mixins(node_id)?;
494        let mro = self.calculate_method_resolution_order(node_id)?;
495        let dynamic_attributes = self.get_dynamic_attributes(node_id)?;
496
497        Ok(InheritanceInfo {
498            class_name: node.name.clone(),
499            base_classes,
500            subclasses,
501            metaclass,
502            mixins,
503            method_resolution_order: mro,
504            dynamic_attributes,
505            is_metaclass: self.is_metaclass(node_id)?,
506            inheritance_chain: self.get_full_inheritance_chain(node_id)?,
507        })
508    }
509
510    /// Get direct base classes of a class
511    pub fn get_base_classes(&self, node_id: &NodeId) -> Result<Vec<InheritanceRelation>> {
512        let mut base_classes = Vec::new();
513
514        for edge in self.graph.get_outgoing_edges(node_id) {
515            if matches!(edge.kind, EdgeKind::Extends) {
516                if let Some(parent_node) = self.graph.get_node(&edge.target) {
517                    let is_metaclass = parent_node
518                        .metadata
519                        .get("is_metaclass")
520                        .and_then(|v| v.as_bool())
521                        .unwrap_or(false);
522
523                    base_classes.push(InheritanceRelation {
524                        class_name: parent_node.name.clone(),
525                        node_id: parent_node.id,
526                        relationship_type: if is_metaclass {
527                            "metaclass".to_string()
528                        } else {
529                            "extends".to_string()
530                        },
531                        file: parent_node.file.clone(),
532                        span: parent_node.span.clone(),
533                    });
534                }
535            }
536        }
537
538        Ok(base_classes)
539    }
540
541    /// Get direct subclasses of a class
542    pub fn get_subclasses(&self, node_id: &NodeId) -> Result<Vec<InheritanceRelation>> {
543        let mut subclasses = Vec::new();
544
545        for edge in self.graph.get_incoming_edges(node_id) {
546            if matches!(edge.kind, EdgeKind::Extends) {
547                if let Some(child_node) = self.graph.get_node(&edge.source) {
548                    subclasses.push(InheritanceRelation {
549                        class_name: child_node.name.clone(),
550                        node_id: child_node.id,
551                        relationship_type: "extends".to_string(),
552                        file: child_node.file.clone(),
553                        span: child_node.span.clone(),
554                    });
555                }
556            }
557        }
558
559        Ok(subclasses)
560    }
561
562    /// Get the metaclass of a class (if any)
563    pub fn get_metaclass(&self, node_id: &NodeId) -> Result<Option<InheritanceRelation>> {
564        for edge in self.graph.get_outgoing_edges(node_id) {
565            if matches!(edge.kind, EdgeKind::Extends) {
566                if let Some(parent_node) = self.graph.get_node(&edge.target) {
567                    let is_metaclass = parent_node
568                        .metadata
569                        .get("is_metaclass")
570                        .and_then(|v| v.as_bool())
571                        .unwrap_or(false);
572
573                    if is_metaclass {
574                        return Ok(Some(InheritanceRelation {
575                            class_name: parent_node.name.clone(),
576                            node_id: parent_node.id,
577                            relationship_type: "metaclass".to_string(),
578                            file: parent_node.file.clone(),
579                            span: parent_node.span.clone(),
580                        }));
581                    }
582                }
583            }
584        }
585
586        Ok(None)
587    }
588
589    /// Get mixins used by a class
590    pub fn get_mixins(&self, node_id: &NodeId) -> Result<Vec<InheritanceRelation>> {
591        let mut mixins = Vec::new();
592
593        for edge in self.graph.get_outgoing_edges(node_id) {
594            if matches!(edge.kind, EdgeKind::Extends) {
595                if let Some(parent_node) = self.graph.get_node(&edge.target) {
596                    // Heuristic: classes ending with "Mixin" or containing "mixin" are considered mixins
597                    if parent_node.name.ends_with("Mixin")
598                        || parent_node.name.to_lowercase().contains("mixin")
599                    {
600                        mixins.push(InheritanceRelation {
601                            class_name: parent_node.name.clone(),
602                            node_id: parent_node.id,
603                            relationship_type: "mixin".to_string(),
604                            file: parent_node.file.clone(),
605                            span: parent_node.span.clone(),
606                        });
607                    }
608                }
609            }
610        }
611
612        Ok(mixins)
613    }
614
615    /// Calculate method resolution order (simplified)
616    pub fn calculate_method_resolution_order(&self, node_id: &NodeId) -> Result<Vec<String>> {
617        let mut mro = Vec::new();
618        let mut visited = HashSet::new();
619
620        self.collect_mro_recursive(node_id, &mut mro, &mut visited)?;
621
622        Ok(mro)
623    }
624
625    /// Recursively collect method resolution order
626    fn collect_mro_recursive(
627        &self,
628        node_id: &NodeId,
629        mro: &mut Vec<String>,
630        visited: &mut HashSet<NodeId>,
631    ) -> Result<()> {
632        if visited.contains(node_id) {
633            return Ok(());
634        }
635
636        visited.insert(*node_id);
637
638        if let Some(node) = self.graph.get_node(node_id) {
639            mro.push(node.name.clone());
640
641            // Add parent classes to MRO
642            for edge in self.graph.get_outgoing_edges(node_id) {
643                if matches!(edge.kind, EdgeKind::Extends) {
644                    self.collect_mro_recursive(&edge.target, mro, visited)?;
645                }
646            }
647        }
648
649        Ok(())
650    }
651
652    /// Get dynamic attributes potentially created by metaclasses or decorators
653    pub fn get_dynamic_attributes(&self, node_id: &NodeId) -> Result<Vec<DynamicAttribute>> {
654        let mut attributes = Vec::new();
655
656        // Look for common patterns that create dynamic attributes
657        if let Some(metaclass) = self.get_metaclass(node_id)? {
658            // Common metaclass-created attributes
659            let common_metaclass_attrs = vec![
660                "_registry",
661                "_instances",
662                "_subclasses",
663                "_handlers",
664                "_processors",
665                "_mixins",
666                "_plugins",
667                "_decorators",
668                "_metadata",
669            ];
670
671            for attr_name in common_metaclass_attrs {
672                attributes.push(DynamicAttribute {
673                    name: attr_name.to_string(),
674                    created_by: format!("metaclass:{}", metaclass.class_name),
675                    attribute_type: "dynamic".to_string(),
676                });
677            }
678        }
679
680        Ok(attributes)
681    }
682
683    /// Check if a class is a metaclass
684    pub fn is_metaclass(&self, node_id: &NodeId) -> Result<bool> {
685        if let Some(node) = self.graph.get_node(node_id) {
686            // Check metadata first
687            if let Some(is_meta) = node.metadata.get("is_metaclass").and_then(|v| v.as_bool()) {
688                return Ok(is_meta);
689            }
690
691            // Heuristic: inherits from 'type' or name contains 'Meta'
692            let inherits_from_type = self.inherits_from(node_id, "type")?;
693            let name_suggests_metaclass =
694                node.name.contains("Meta") || node.name.ends_with("Metaclass");
695
696            Ok(inherits_from_type || name_suggests_metaclass)
697        } else {
698            Ok(false)
699        }
700    }
701
702    /// Get the full inheritance chain up to the root
703    pub fn get_full_inheritance_chain(&self, node_id: &NodeId) -> Result<Vec<String>> {
704        let mut chain = Vec::new();
705        let mut visited = HashSet::new();
706
707        self.collect_inheritance_chain_recursive(node_id, &mut chain, &mut visited)?;
708
709        Ok(chain)
710    }
711
712    /// Recursively collect inheritance chain
713    fn collect_inheritance_chain_recursive(
714        &self,
715        node_id: &NodeId,
716        chain: &mut Vec<String>,
717        visited: &mut HashSet<NodeId>,
718    ) -> Result<()> {
719        if visited.contains(node_id) {
720            return Ok(());
721        }
722
723        visited.insert(*node_id);
724
725        if let Some(node) = self.graph.get_node(node_id) {
726            chain.push(node.name.clone());
727
728            // Follow parent classes
729            for edge in self.graph.get_outgoing_edges(node_id) {
730                if matches!(edge.kind, EdgeKind::Extends) {
731                    self.collect_inheritance_chain_recursive(&edge.target, chain, visited)?;
732                }
733            }
734        }
735
736        Ok(())
737    }
738
739    /// Check if a class inherits from a specific base class
740    pub fn inherits_from(&self, node_id: &NodeId, base_class_name: &str) -> Result<bool> {
741        let mut visited = HashSet::new();
742        self.inherits_from_recursive(node_id, base_class_name, &mut visited)
743    }
744
745    /// Recursively check inheritance
746    fn inherits_from_recursive(
747        &self,
748        node_id: &NodeId,
749        base_class_name: &str,
750        visited: &mut HashSet<NodeId>,
751    ) -> Result<bool> {
752        if visited.contains(node_id) {
753            return Ok(false);
754        }
755
756        visited.insert(*node_id);
757
758        for edge in self.graph.get_outgoing_edges(node_id) {
759            if matches!(edge.kind, EdgeKind::Extends) {
760                if let Some(parent_node) = self.graph.get_node(&edge.target) {
761                    if parent_node.name == base_class_name {
762                        return Ok(true);
763                    }
764
765                    if self.inherits_from_recursive(&edge.target, base_class_name, visited)? {
766                        return Ok(true);
767                    }
768                }
769            }
770        }
771
772        Ok(false)
773    }
774
775    /// Check if a class has a specific metaclass
776    pub fn has_metaclass(&self, node_id: &NodeId, metaclass_name: &str) -> Result<bool> {
777        if let Some(metaclass) = self.get_metaclass(node_id)? {
778            Ok(metaclass.class_name == metaclass_name)
779        } else {
780            Ok(false)
781        }
782    }
783
784    /// Check if a class uses a specific mixin
785    pub fn uses_mixin(&self, node_id: &NodeId, mixin_name: &str) -> Result<bool> {
786        let mixins = self.get_mixins(node_id)?;
787        Ok(mixins.iter().any(|m| m.class_name == mixin_name))
788    }
789}
790
791/// Result of a path finding operation
792#[derive(Debug, Clone, Serialize, Deserialize)]
793pub struct PathResult {
794    /// Source node ID
795    pub source: NodeId,
796    /// Target node ID
797    pub target: NodeId,
798    /// Path of node IDs from source to target
799    pub path: Vec<NodeId>,
800    /// Distance (number of hops)
801    pub distance: usize,
802    /// Edges in the path
803    pub edges: Vec<Edge>,
804}
805
806/// Information about a symbol
807#[derive(Debug, Clone, Serialize, Deserialize)]
808pub struct SymbolInfo {
809    /// The node representing the symbol
810    pub node: Node,
811    /// Number of references to this symbol
812    pub references_count: usize,
813    /// Number of dependencies from this symbol
814    pub dependencies_count: usize,
815}
816
817/// A reference to a symbol
818#[derive(Debug, Clone, Serialize, Deserialize)]
819pub struct SymbolReference {
820    /// The node that references the symbol
821    pub source_node: Node,
822    /// Type of reference (edge kind)
823    pub edge_kind: EdgeKind,
824    /// Location of the reference
825    pub location: ReferenceLocation,
826}
827
828/// Location of a reference
829#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct ReferenceLocation {
831    /// File containing the reference
832    pub file: PathBuf,
833    /// Span of the reference
834    pub span: crate::ast::Span,
835}
836
837/// A dependency of a symbol
838#[derive(Debug, Clone, Serialize, Deserialize)]
839pub struct SymbolDependency {
840    /// The target node that is depended upon
841    pub target_node: Node,
842    /// Type of dependency (edge kind)
843    pub edge_kind: EdgeKind,
844    /// Dependency classification
845    pub dependency_type: DependencyType,
846}
847
848/// Type of dependency analysis
849#[derive(Debug, Clone, Serialize, Deserialize)]
850pub enum DependencyType {
851    /// All direct dependencies
852    Direct,
853    /// Only function calls
854    Calls,
855    /// Only imports
856    Imports,
857    /// Only reads
858    Reads,
859    /// Only writes
860    Writes,
861}
862
863/// Inheritance filter types for advanced symbol search
864#[derive(Debug, Clone, Serialize, Deserialize)]
865pub enum InheritanceFilter {
866    /// Filter by classes that inherit from a specific base class
867    InheritsFrom(String),
868    /// Filter by classes that have a specific metaclass
869    HasMetaclass(String),
870    /// Filter by classes that use a specific mixin
871    UsesMixin(String),
872}
873
874/// Comprehensive inheritance information for a class
875#[derive(Debug, Clone, Serialize, Deserialize, Default)]
876pub struct InheritanceInfo {
877    /// The class name
878    pub class_name: String,
879    /// Direct base classes
880    pub base_classes: Vec<InheritanceRelation>,
881    /// Direct subclasses
882    pub subclasses: Vec<InheritanceRelation>,
883    /// Metaclass (if any)
884    pub metaclass: Option<InheritanceRelation>,
885    /// Mixins used by this class
886    pub mixins: Vec<InheritanceRelation>,
887    /// Method resolution order
888    pub method_resolution_order: Vec<String>,
889    /// Dynamic attributes created by metaclasses/decorators
890    pub dynamic_attributes: Vec<DynamicAttribute>,
891    /// Whether this class is a metaclass
892    pub is_metaclass: bool,
893    /// Full inheritance chain
894    pub inheritance_chain: Vec<String>,
895}
896
897/// Represents an inheritance relationship
898#[derive(Debug, Clone, Serialize, Deserialize)]
899pub struct InheritanceRelation {
900    /// Name of the related class
901    pub class_name: String,
902    /// Node ID of the related class
903    pub node_id: NodeId,
904    /// Type of relationship (extends, metaclass, mixin)
905    pub relationship_type: String,
906    /// File where the class is defined
907    pub file: PathBuf,
908    /// Location in the file
909    pub span: crate::ast::Span,
910}
911
912/// Represents a dynamic attribute created by metaclasses or decorators
913#[derive(Debug, Clone, Serialize, Deserialize)]
914pub struct DynamicAttribute {
915    /// Name of the attribute
916    pub name: String,
917    /// What created this attribute (metaclass, decorator, etc.)
918    pub created_by: String,
919    /// Type of attribute (dynamic, static, etc.)
920    pub attribute_type: String,
921}
922
923#[cfg(test)]
924mod tests {
925    use super::*;
926    use crate::ast::{Language, Span};
927
928    fn create_test_node(name: &str, kind: NodeKind, file: &str) -> Node {
929        Node::new(
930            "test_repo",
931            kind,
932            name.to_string(),
933            Language::Python,
934            PathBuf::from(file),
935            Span::new(0, 10, 1, 1, 1, 11),
936        )
937    }
938
939    fn create_test_node_with_span(
940        name: &str,
941        kind: NodeKind,
942        file: &str,
943        start_byte: usize,
944        end_byte: usize,
945    ) -> Node {
946        Node::new(
947            "test_repo",
948            kind,
949            name.to_string(),
950            Language::Python,
951            PathBuf::from(file),
952            Span::new(start_byte, end_byte, 1, 1, 1, 11),
953        )
954    }
955
956    #[test]
957    fn test_graph_store_basic_operations() {
958        let graph = GraphStore::new();
959
960        let node1 = create_test_node("function1", NodeKind::Function, "test.py");
961        let node2 = create_test_node("function2", NodeKind::Function, "test.py");
962
963        graph.add_node(node1.clone());
964        graph.add_node(node2.clone());
965
966        assert_eq!(graph.get_node(&node1.id).unwrap().id, node1.id);
967        assert_eq!(graph.get_node(&node2.id).unwrap().id, node2.id);
968
969        let edge = Edge::new(node1.id, node2.id, EdgeKind::Calls);
970        graph.add_edge(edge.clone());
971
972        let outgoing = graph.get_outgoing_edges(&node1.id);
973        assert_eq!(outgoing.len(), 1);
974        assert_eq!(outgoing[0], edge);
975
976        let incoming = graph.get_incoming_edges(&node2.id);
977        assert_eq!(incoming.len(), 1);
978        assert_eq!(incoming[0], edge);
979    }
980
981    #[test]
982    fn test_graph_query_path_finding() {
983        let graph = Arc::new(GraphStore::new());
984        let query = GraphQuery::new(graph.clone());
985
986        let node1 = create_test_node_with_span("function1", NodeKind::Function, "test.py", 0, 10);
987        let node2 = create_test_node_with_span("function2", NodeKind::Function, "test.py", 20, 30);
988        let node3 = create_test_node_with_span("function3", NodeKind::Function, "test.py", 40, 50);
989
990        graph.add_node(node1.clone());
991        graph.add_node(node2.clone());
992        graph.add_node(node3.clone());
993
994        graph.add_edge(Edge::new(node1.id, node2.id, EdgeKind::Calls));
995        graph.add_edge(Edge::new(node2.id, node3.id, EdgeKind::Calls));
996
997        let path = query.find_path(&node1.id, &node3.id, None).unwrap();
998        assert!(path.is_some());
999
1000        let path = path.unwrap();
1001        assert_eq!(path.distance, 2);
1002        assert_eq!(path.path, vec![node1.id, node2.id, node3.id]);
1003    }
1004
1005    #[test]
1006    fn test_symbol_search() {
1007        let graph = Arc::new(GraphStore::new());
1008        let query = GraphQuery::new(graph.clone());
1009
1010        let node1 = create_test_node("test_function", NodeKind::Function, "test.py");
1011        let node2 = create_test_node("another_function", NodeKind::Function, "test.py");
1012        let node3 = create_test_node("test_class", NodeKind::Class, "test.py");
1013
1014        graph.add_node(node1.clone());
1015        graph.add_node(node2.clone());
1016        graph.add_node(node3.clone());
1017
1018        let results = query.search_symbols("test", None, None).unwrap();
1019        assert_eq!(results.len(), 2); // test_function and test_class
1020
1021        let results = query
1022            .search_symbols("test", Some(vec![NodeKind::Function]), None)
1023            .unwrap();
1024        assert_eq!(results.len(), 1); // only test_function
1025    }
1026
1027    #[test]
1028    fn test_symbol_search_regex() {
1029        let graph = Arc::new(GraphStore::new());
1030        let query = GraphQuery::new(graph.clone());
1031
1032        let agent_node = create_test_node("Agent", NodeKind::Class, "agent.py");
1033        let user_agent_node = create_test_node("UserAgent", NodeKind::Class, "user_agent.py");
1034        let guild_manager_agent_node = create_test_node(
1035            "GuildManagerAgent",
1036            NodeKind::Class,
1037            "guild_manager_agent.py",
1038        );
1039        let other_node = create_test_node("ProcessAgent", NodeKind::Function, "process.py");
1040
1041        graph.add_node(agent_node.clone());
1042        graph.add_node(user_agent_node.clone());
1043        graph.add_node(guild_manager_agent_node.clone());
1044        graph.add_node(other_node.clone());
1045
1046        // Test exact match using regex
1047        let results = query.search_symbols("^Agent$", None, None).unwrap();
1048        assert_eq!(results.len(), 1); // only exact "Agent"
1049        assert_eq!(results[0].node.name, "Agent");
1050
1051        // Test suffix match
1052        let results = query.search_symbols("Agent$", None, None).unwrap();
1053        assert_eq!(results.len(), 4); // Agent, UserAgent, GuildManagerAgent, ProcessAgent
1054
1055        // Test case-sensitive prefix match
1056        let results = query.search_symbols("^Guild", None, None).unwrap();
1057        assert_eq!(results.len(), 1); // only GuildManagerAgent
1058        assert_eq!(results[0].node.name, "GuildManagerAgent");
1059
1060        // Test fallback to substring search with invalid regex
1061        let results = query.search_symbols("[invalid", None, None).unwrap();
1062        assert_eq!(results.len(), 0); // no matches for invalid pattern (falls back to substring)
1063
1064        // Test normal substring search still works
1065        let results = query.search_symbols("Agent", None, None).unwrap();
1066        assert_eq!(results.len(), 4); // All nodes containing "Agent"
1067    }
1068}