rustdoc-markdown 0.91.0

A tool to convert Rust documentation to Markdown, for use with LLMs
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
use anyhow::{Result, anyhow};
use rustdoc_types::{
    Crate, GenericArg, GenericArgs, GenericBound, GenericParamDef, Generics, Id, ItemEnum, Path,
    Term, Type, WherePredicate,
};
use std::collections::{HashMap, HashSet, VecDeque}; // Use HashMap instead of BTreeMap where needed
use std::fmt::{Display, Formatter}; // Use FmtWrite alias
use std::hash::Hash;
use std::io::Write as IoWrite; // Use IoWrite alias and IMPORT Cursor
use tracing::{debug, info, warn};

use crate::get_type_id;

// --- ID Graph Structures ---

#[doc(hidden)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum EdgeLabel {
    Contains,             // Module contains Item (original structure)
    ReferencesType,       // Item references Type ID (e.g., field type, return type)
    GenericArgument,      // Path uses Type ID as generic arg
    AssociatedType,       // Item references Associated Type ID
    AssociatedConstant,   // Item references Associated Constant ID
    TraitBound,           // Generic Param/Where Clause has Trait Bound ID
    Implements,           // Impl block implements Trait ID
    ImplFor,              // Impl block is for Type ID
    ImplItem,             // Impl block contains Item ID
    TraitItem,            // Trait contains Item ID
    EnumVariant,          // Enum contains Variant ID
    VariantField,         // Variant contains Field ID
    StructField,          // Struct contains Field ID
    UnionField,           // Union contains Field ID
    FieldType,            // Field ID has Type ID
    AliasTo,              // TypeAlias/TraitAlias points to Type/Trait ID
    SignatureInput,       // Function signature references input Type ID
    SignatureOutput,      // Function signature references output Type ID
    SuperTrait,           // Trait has supertrait Trait ID
    Dependency,           // Generic catch-all for less specific type dependencies
    IntraDocLink,         // Doc comment links to Item ID
    AssociatedConstraint, // Generic Arg Constraint references Item ID
    ParamType,            // Generic Param Def references Type ID
    ParamBound,           // Generic Param Def references Bound/Trait ID
    PredicateType,        // Where Predicate references Type ID
    PredicateBound,       // Where Predicate references Bound/Trait ID
    PredicateEqLhs,       // Where Predicate Eq references LHS Type ID
    PredicateEqRhs,       // Where Predicate Eq references RHS Term ID
    DynTraitBound,        // DynTrait references Trait ID
    ImplTraitBound,       // ImplTrait references Bound/Trait ID
    UseTarget,            // Use item references target item/module ID
}

impl Display for EdgeLabel {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

#[doc(hidden)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Edge {
    pub source: Id,
    pub target: Id,
    pub label: EdgeLabel,
}

#[doc(hidden)]
#[derive(Debug, Default, Clone)] // Add Clone
pub struct IdGraph {
    pub edges: HashSet<Edge>, // Use HashSet to avoid duplicate edges
    // Add an adjacency list representation for easier traversal (target -> Vec<(source, label)>)
    // Note: We build the forward graph (source -> targets) for dependency finding.
    // For finding roots (no incoming edges), we analyze the final edge set.
    // For tree printing, we need source -> Vec<(target, label)>
    pub adjacency: HashMap<Id, Vec<(Id, EdgeLabel)>>,
    // Reverse adjacency list for filtering (target -> Vec<(source, label)>)
    pub reverse_adjacency: HashMap<Id, Vec<(Id, EdgeLabel)>>,
}

impl IdGraph {
    fn new() -> Self {
        Self::default()
    }

    /// Adds an edge, ensuring both source and target are in the crate index.
    pub(crate) fn add_edge(&mut self, source: Id, target: Id, label: EdgeLabel, krate: &Crate) {
        // Only add edges where both nodes are part of the local crate
        if krate.index.contains_key(&source) && krate.index.contains_key(&target) {
            let edge = Edge {
                source,
                target,
                label: label.clone(),
            };
            // Clone edge before inserting into the HashSet to avoid move error
            if self.edges.insert(edge.clone()) {
                // Also update the adjacency list for forward traversal (needed for dump)
                self.adjacency
                    .entry(source)
                    .or_default()
                    .push((target, label.clone()));
                // Update reverse adjacency list
                self.reverse_adjacency
                    .entry(target)
                    .or_default()
                    .push((edge.source, label)); // Correct tuple syntax
            }
        }
    }

    /// Finds all direct children of a node (source -> targets).
    fn get_children(&self, source_id: &Id) -> Option<&Vec<(Id, EdgeLabel)>> {
        self.adjacency.get(source_id)
    }

    /// Finds all nodes that have no incoming edges *from within the graph*.
    #[doc(hidden)]
    pub fn find_roots(&self) -> HashSet<Id> {
        let mut all_nodes: HashSet<Id> = HashSet::new();
        let mut targets: HashSet<Id> = HashSet::new();

        for edge in &self.edges {
            all_nodes.insert(edge.source);
            all_nodes.insert(edge.target);
            targets.insert(edge.target);
        }

        all_nodes.difference(&targets).cloned().collect()
    }

    #[allow(dead_code)] // Keep for future debugging use
    pub(crate) fn find_incoming_edges(&self, target_id: &Id) -> Vec<&Edge> {
        self.edges
            .iter()
            .filter(|edge| edge.target == *target_id)
            .collect()
    }

    /// Filters the graph to keep only edges that are part of a path leading to the target_leaf_id.
    /// Returns a new `IdGraph` containing only the filtered edges.
    #[doc(hidden)]
    pub fn filter_to_leaf(&self, target_leaf_id: Id) -> IdGraph {
        let mut filtered_graph = IdGraph::new();
        let mut reachable_nodes = HashSet::new(); // Nodes that can reach the target
        let mut queue = VecDeque::new();

        // Start BFS from the target node using the reverse adjacency list
        // Check existence in reverse_adjacency OR adjacency (node might exist but have no incoming edges)
        if self.reverse_adjacency.contains_key(&target_leaf_id)
            || self.adjacency.contains_key(&target_leaf_id)
        {
            reachable_nodes.insert(target_leaf_id);
            queue.push_back(target_leaf_id);
        } else {
            // Target ID doesn't exist in the original graph's node set
            return filtered_graph; // Return empty graph
        }

        while let Some(current_id) = queue.pop_front() {
            if let Some(parents) = self.reverse_adjacency.get(&current_id) {
                for (parent_id, _) in parents {
                    if reachable_nodes.insert(*parent_id) {
                        queue.push_back(*parent_id);
                    }
                }
            }
        }

        // Now, add edges from the original graph *only if both* source and target are in reachable_nodes
        for edge in &self.edges {
            if reachable_nodes.contains(&edge.source) && reachable_nodes.contains(&edge.target) {
                // Manually add to filtered graph components (avoiding add_edge's krate check)
                if filtered_graph.edges.insert(edge.clone()) {
                    filtered_graph
                        .adjacency
                        .entry(edge.source)
                        .or_default()
                        .push((edge.target, edge.label.clone()));
                    filtered_graph
                        .reverse_adjacency
                        .entry(edge.target)
                        .or_default()
                        .push((edge.source, edge.label.clone())); // Correct tuple syntax
                }
            }
        }

        filtered_graph // Return the newly constructed filtered graph
    }
}

// --- End ID Graph Structures ---

// --- Module Resolution Structures ---

#[allow(unused)]
#[derive(Debug, Clone)]
enum ResolutionState {
    Unresolved,
    Resolving,
    Resolved(HashSet<Id>),
}

type ResolutionCache = HashMap<Id, ResolutionState>;

/// Represents a module with its fully resolved items after handling 'use' statements.
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct ResolvedModule {
    pub id: Id,
    /// The fully resolved set of item IDs directly accessible within this module.
    pub items: HashSet<Id>,
}

/// Recursively resolves items for a module, handling `use` statements and cycles.
fn resolve_module_items(module_id: Id, krate: &Crate, cache: &mut ResolutionCache) -> HashSet<Id> {
    // Check cache for cycle or previous result
    match cache.get(&module_id) {
        Some(ResolutionState::Resolving) => {
            debug!("Cycle detected resolving module ID: {:?}", module_id);
            return HashSet::new(); // Break cycle
        }
        Some(ResolutionState::Resolved(items)) => {
            return items.clone();
        }
        Some(ResolutionState::Unresolved) | None => {
            // Continue resolution
        }
    }

    // Mark as resolving
    cache.insert(module_id, ResolutionState::Resolving);
    debug!("Resolving module ID: {:?}", module_id);

    let mut resolved_items = HashSet::new();

    // Get the original module definition
    if let Some(module_item) = krate.index.get(&module_id) {
        if let ItemEnum::Module(module_data) = &module_item.inner {
            for item_id in &module_data.items {
                if let Some(item) = krate.index.get(item_id) {
                    match &item.inner {
                        ItemEnum::Use(use_item) => {
                            if let Some(target_id) = use_item.id {
                                if use_item.is_glob {
                                    // Glob import: Recursively resolve the target module/enum/etc.
                                    // Check if target_id points to a Module. Glob imports
                                    // can also be used on Enums, but we primarily care
                                    // about module imports here for bringing items into scope.
                                    if let Some(target_item) = krate.index.get(&target_id) {
                                        if matches!(target_item.inner, ItemEnum::Module(_)) {
                                            debug!(
                                                "Glob import from {:?} to {:?} in module {:?}",
                                                target_id, item_id, module_id
                                            );
                                            let imported_items =
                                                resolve_module_items(target_id, krate, cache);
                                            resolved_items.extend(imported_items);
                                        } else {
                                            // Glob import from something not a module (e.g., Enum)
                                            // Just add the enum ID itself for now.
                                            resolved_items.insert(target_id);
                                        }
                                    }
                                } else {
                                    // Single item import: Add the target ID directly
                                    resolved_items.insert(target_id);
                                }
                            }
                            // Ignore use items with id: None (primitive re-exports) for resolution
                        }
                        _ => {
                            // Not a use statement, add the item ID directly
                            resolved_items.insert(*item_id);
                        }
                    }
                }
            }
        } // Module item might not have Module inner if it's a re-export target itself? No, index should contain the real item.
    // Let's warn if the found item isn't a module.
    // else {
    //     warn!("Item with module ID {:?} is not actually a Module kind: {:?}", module_id, module_item.inner);
    // }
    } else {
        warn!("Module ID {:?} not found in crate index.", module_id);
    }

    // Mark as resolved and cache the result
    debug!(
        "Resolved module ID {:?} with {} items.",
        module_id,
        resolved_items.len()
    );
    cache.insert(module_id, ResolutionState::Resolved(resolved_items.clone()));
    resolved_items
}

/// Builds an index of all modules with their items resolved after handling 'use' statements.
#[doc(hidden)]
pub fn build_resolved_module_index(krate: &Crate) -> HashMap<Id, ResolvedModule> {
    info!("Building resolved module index...");
    let mut resolved_index = HashMap::new();
    let mut cache: ResolutionCache = HashMap::new();

    for (id, item) in &krate.index {
        if let ItemEnum::Module(_) = &item.inner
            && !resolved_index.contains_key(id)
        {
            let resolved_items = resolve_module_items(*id, krate, &mut cache);
            resolved_index.insert(
                *id,
                ResolvedModule {
                    id: *id,
                    items: resolved_items,
                },
            );
        }
    }
    info!(
        "Built resolved module index for {} modules.",
        resolved_index.len()
    );
    resolved_index
}

// --- End Module Resolution Structures ---

/// Finds all reachable `Id`s referenced within a `Type`.
fn find_type_dependencies(
    ty: &Type,
    source_id: Id, // The ID of the item *containing* this type reference
    krate: &Crate,
    dependencies: &mut HashSet<Id>,
    graph: &mut IdGraph,
    edge_label: EdgeLabel, // How the source_id relates to this type
) {
    // Add the direct ID if the type itself resolves to one
    if let Some(id) = get_type_id(ty)
        && krate.index.contains_key(&id)
        && dependencies.insert(id)
    {
        graph.add_edge(source_id, id, edge_label.clone(), krate);
    }

    // Recursively check inner types and generic arguments
    match ty {
        Type::ResolvedPath(Path { args, id, .. }) => {
            // Add the path's own ID
            if krate.index.contains_key(id) && dependencies.insert(*id) {
                graph.add_edge(source_id, *id, edge_label.clone(), krate);
            }
            // Check generic arguments
            if let Some(args_box) = args.as_ref() {
                // args is &Box<GenericArgs>, need to get &GenericArgs
                find_generic_args_dependencies(
                    args_box,
                    source_id, // The source item uses these generic args
                    krate,
                    dependencies,
                    graph,
                );
            }
        }
        Type::Tuple(inner_types) => {
            for inner_ty in inner_types {
                find_type_dependencies(
                    inner_ty,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::Dependency, // Generic dependency for tuple contents
                );
            }
        }
        Type::Slice(inner_ty) => {
            find_type_dependencies(
                inner_ty,
                source_id,
                krate,
                dependencies,
                graph,
                EdgeLabel::Dependency, // Type contained in slice
            );
        }
        Type::Array { type_, .. } => {
            find_type_dependencies(
                type_,
                source_id,
                krate,
                dependencies,
                graph,
                EdgeLabel::Dependency, // Type contained in array
            );
        }
        Type::Pat { type_, .. } => {
            find_type_dependencies(
                type_,
                source_id,
                krate,
                dependencies,
                graph,
                EdgeLabel::Dependency, // Type in pattern
            );
        }
        Type::RawPointer { type_, .. } => {
            find_type_dependencies(
                type_,
                source_id,
                krate,
                dependencies,
                graph,
                EdgeLabel::Dependency, // Pointee type
            );
        }
        Type::BorrowedRef { type_, .. } => {
            find_type_dependencies(
                type_,
                source_id,
                krate,
                dependencies,
                graph,
                EdgeLabel::Dependency, // Referenced type
            );
        }
        Type::QualifiedPath {
            args,
            self_type,
            trait_,
            ..
        } => {
            find_type_dependencies(
                self_type,
                source_id,
                krate,
                dependencies,
                graph,
                EdgeLabel::Dependency, // Self type in qualified path
            );
            if let Some(trait_path) = trait_
                && krate.index.contains_key(&trait_path.id)
                && dependencies.insert(trait_path.id)
            {
                // This source_id uses an associated type from trait_path.id
                graph.add_edge(
                    source_id,
                    trait_path.id,
                    EdgeLabel::AssociatedType, // Or AssociatedConstant? Ambiguous here. Use AssociatedType as default.
                    krate,
                );
            }
            if let Some(args_box) = args {
                find_generic_args_dependencies(args_box, source_id, krate, dependencies, graph);
            }
        }
        Type::DynTrait(dyn_trait) => {
            for poly_trait in &dyn_trait.traits {
                if krate.index.contains_key(&poly_trait.trait_.id)
                    && dependencies.insert(poly_trait.trait_.id)
                {
                    graph.add_edge(
                        source_id,
                        poly_trait.trait_.id,
                        EdgeLabel::DynTraitBound,
                        krate,
                    );
                }
                // Check generic param defs within the poly trait
                for param_def in &poly_trait.generic_params {
                    find_generic_param_def_dependencies(
                        param_def,
                        source_id,
                        krate,
                        dependencies,
                        graph,
                    );
                }
            }
        }
        Type::ImplTrait(bounds) => {
            for bound in bounds {
                find_generic_bound_dependencies(
                    bound,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::ImplTraitBound,
                );
            }
        }
        Type::FunctionPointer(fp) => {
            // generic_params are HRTBs for the pointer itself
            for param_def in &fp.generic_params {
                find_generic_param_def_dependencies(
                    param_def,
                    source_id, // The source item uses this function pointer type
                    krate,
                    dependencies,
                    graph,
                );
            }
            // sig contains input/output types
            for (_name, input_type) in &fp.sig.inputs {
                find_type_dependencies(
                    input_type,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::SignatureInput,
                );
            }
            if let Some(output) = &fp.sig.output {
                find_type_dependencies(
                    output,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::SignatureOutput,
                );
            }
        }
        // Types without complex inner structures or IDs
        Type::Generic(_) | Type::Primitive(_) | Type::Infer => {}
    }
}

fn find_generic_args_dependencies(
    args: &GenericArgs,
    source_id: Id, // The ID of the item whose path includes these args
    krate: &Crate,
    dependencies: &mut HashSet<Id>,
    graph: &mut IdGraph,
) {
    match args {
        GenericArgs::AngleBracketed {
            args, constraints, ..
        } => {
            for arg in args {
                match arg {
                    GenericArg::Type(t) => find_type_dependencies(
                        t,
                        source_id,
                        krate,
                        dependencies,
                        graph,
                        EdgeLabel::GenericArgument,
                    ),
                    GenericArg::Const(_) => {}
                    GenericArg::Lifetime(_) | GenericArg::Infer => {}
                }
            }
            for constraint in constraints {
                // AssocItemConstraint { name: String, kind: AssocItemConstraintKind }
                match constraint {
                    // Use tuple variant matching
                    rustdoc_types::AssocItemConstraint {
                        name: _,          // TODO: Could the name be an ID sometimes? Unlikely.
                        args: assoc_args, // args for the associated type constraint itself
                        binding: rustdoc_types::AssocItemConstraintKind::Equality(term),
                    } => {
                        // The source_id uses this associated type constraint.
                        // Find dependencies within the term (RHS of equality).
                        match term {
                            Term::Type(t) => find_type_dependencies(
                                t,
                                source_id,
                                krate,
                                dependencies,
                                graph,
                                EdgeLabel::AssociatedConstraint, // Term type referenced in constraint
                            ),
                            Term::Constant(_) => {} // Constant expr/value are stringly typed
                        }
                        // Also find dependencies in the arguments *to* the associated type
                        if let Some(assoc_args_box) = assoc_args {
                            find_generic_args_dependencies(
                                assoc_args_box,
                                source_id,
                                krate,
                                dependencies,
                                graph,
                            );
                        }
                    }
                    rustdoc_types::AssocItemConstraint {
                        name: _,
                        args: assoc_args,
                        binding: rustdoc_types::AssocItemConstraintKind::Constraint(bounds),
                    } => {
                        // The source_id uses this associated type constraint.
                        for bound in bounds {
                            find_generic_bound_dependencies(
                                bound,
                                source_id,
                                krate,
                                dependencies,
                                graph,
                                EdgeLabel::AssociatedConstraint, // Bound referenced in constraint
                            );
                        }
                        // Also find dependencies in the arguments *to* the associated type
                        if let Some(assoc_args_box) = assoc_args {
                            find_generic_args_dependencies(
                                assoc_args_box,
                                source_id,
                                krate,
                                dependencies,
                                graph,
                            );
                        }
                    }
                }
            }
        }
        GenericArgs::Parenthesized { inputs, output, .. } => {
            // Process inputs
            for input_type in inputs {
                find_type_dependencies(
                    input_type,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::GenericArgument, // Or a more specific label if context implies Fn traits
                );
            }
            // Process output
            if let Some(output_type) = output {
                find_type_dependencies(
                    output_type,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::GenericArgument, // Or a more specific label
                );
            }
        }
        GenericArgs::ReturnTypeNotation => {} // TODO: Handle this? T::method(..) - maybe the T part?
    }
}

fn find_generic_bound_dependencies(
    bound: &GenericBound,
    source_id: Id, // The ID of the item imposing this bound (e.g., in where clause, or on param)
    krate: &Crate,
    dependencies: &mut HashSet<Id>,
    graph: &mut IdGraph,
    edge_label: EdgeLabel, // e.g., ParamBound, PredicateBound
) {
    match bound {
        GenericBound::TraitBound {
            trait_, // This is a Path struct
            generic_params,
            ..
        } => {
            if krate.index.contains_key(&trait_.id) && dependencies.insert(trait_.id) {
                graph.add_edge(source_id, trait_.id, edge_label.clone(), krate);
            }
            // Trait path itself might have generic args
            if let Some(args) = trait_.args.as_ref() {
                find_generic_args_dependencies(args, source_id, krate, dependencies, graph);
            }
            // Check HRTBs (generic_params)
            for param_def in generic_params {
                find_generic_param_def_dependencies(
                    param_def,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                );
            }
        }
        GenericBound::Outlives(_) | GenericBound::Use(_) => {}
    }
}

fn find_generics_dependencies(
    generics: &Generics,
    source_id: Id, // ID of the item defining these generics
    krate: &Crate,
    dependencies: &mut HashSet<Id>,
    graph: &mut IdGraph,
) {
    for param in &generics.params {
        find_generic_param_def_dependencies(param, source_id, krate, dependencies, graph);
    }
    for predicate in &generics.where_predicates {
        match predicate {
            WherePredicate::BoundPredicate {
                type_,
                bounds,
                generic_params, // HRTBs for the predicate
                ..
            } => {
                // source_id imposes a bound on type_
                find_type_dependencies(
                    type_,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::PredicateType,
                );
                for bound in bounds {
                    // source_id uses 'bound' in a where predicate
                    find_generic_bound_dependencies(
                        bound,
                        source_id,
                        krate,
                        dependencies,
                        graph,
                        EdgeLabel::PredicateBound,
                    );
                }
                // Check HRTBs (generic_params)
                for param_def in generic_params {
                    find_generic_param_def_dependencies(
                        param_def,
                        source_id, // HRTB defined on item source_id
                        krate,
                        dependencies,
                        graph,
                    );
                }
            }
            WherePredicate::LifetimePredicate { .. } => {} // Lifetimes don't have IDs
            WherePredicate::EqPredicate { lhs, rhs, .. } => {
                // source_id requires lhs == rhs
                find_type_dependencies(
                    lhs,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::PredicateEqLhs,
                );
                match rhs {
                    Term::Type(t) => find_type_dependencies(
                        t,
                        source_id,
                        krate,
                        dependencies,
                        graph,
                        EdgeLabel::PredicateEqRhs,
                    ),
                    Term::Constant(_) => {} // Constant expr/value are stringly typed
                }
            }
        }
    }
}

fn find_generic_param_def_dependencies(
    param_def: &GenericParamDef,
    source_id: Id, // ID of the item defining this parameter
    krate: &Crate,
    dependencies: &mut HashSet<Id>,
    graph: &mut IdGraph,
) {
    match &param_def.kind {
        rustdoc_types::GenericParamDefKind::Lifetime { .. } => {}
        rustdoc_types::GenericParamDefKind::Type {
            bounds, default, ..
        } => {
            for bound in bounds {
                // source_id adds 'bound' to its generic param 'param_def.name'
                find_generic_bound_dependencies(
                    bound,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::ParamBound,
                );
            }
            if let Some(ty) = default {
                // source_id provides default type 'ty' for its generic param 'param_def.name'
                find_type_dependencies(
                    ty,
                    source_id,
                    krate,
                    dependencies,
                    graph,
                    EdgeLabel::ParamType, // Label indicating it's a default type for a param
                );
            }
        }
        rustdoc_types::GenericParamDefKind::Const { type_, .. } => {
            // source_id uses 'type_' for its const generic param 'param_def.name'
            // Ignore default string
            find_type_dependencies(
                type_,
                source_id,
                krate,
                dependencies,
                graph,
                EdgeLabel::ParamType, // Label indicating it's the type of a const param
            );
        }
    }
}

fn normalize_path(user_path: &str, _crate_name: &str, normalized_crate_name: &str) -> Vec<String> {
    let path = if user_path.starts_with("::") {
        format!("{}{}", normalized_crate_name, user_path)
    } else if !user_path.contains("::") && !user_path.is_empty() {
        // Assume single segment refers to top-level item in the crate
        format!("{}::{}", normalized_crate_name, user_path)
    } else {
        user_path.to_string() // Use as is if it contains '::' but doesn't start with it (e.g., external crate path)
    };
    path.split("::").map(|s| s.to_string()).collect()
}

fn path_matches(item_path: &[String], filter_path: &[String]) -> bool {
    if filter_path.len() > item_path.len() {
        return false; // Filter path is more specific than item path
    }
    item_path.starts_with(filter_path)
}

/// Selects items based on path filters and recursively includes their dependencies.
/// Builds the graph for *all* items in the crate, regardless of filtering.
#[doc(hidden)]
pub fn select_items(
    krate: &Crate,
    user_paths: &[String],
    resolved_modules: &HashMap<Id, ResolvedModule>,
) -> Result<(HashSet<Id>, IdGraph)> {
    let mut selected_ids: HashSet<Id> = HashSet::new();
    let mut graph = IdGraph::new(); // Instantiate the graph

    // --- Build the full graph first ---
    info!("Building full dependency graph...");
    for id in krate.index.keys() {
        build_graph_for_item(*id, krate, &mut graph);
    }
    info!("Built full graph with {} edges.", graph.edges.len());

    // --- Now select items based on filters ---
    if user_paths.is_empty() {
        info!("No path filters specified, selecting all items.");
        selected_ids.extend(krate.index.keys().cloned());
        return Ok((selected_ids, graph));
    }

    let root_item = krate
        .index
        .get(&krate.root)
        .ok_or_else(|| anyhow!("Crate root item not found in index"))?;
    let crate_name = root_item
        .name
        .as_ref()
        .ok_or_else(|| anyhow!("Crate root item has no name"))?;
    let normalized_crate_name = crate_name.replace('-', "_");

    let normalized_filters: Vec<Vec<String>> = user_paths
        .iter()
        .map(|p| normalize_path(p, crate_name, &normalized_crate_name))
        .collect();

    info!("Normalized path filters: {:?}", normalized_filters);

    // Initial selection based on paths matching items in resolved modules
    // Iterate through resolved modules instead of krate.paths directly
    for resolved_mod in resolved_modules.values() {
        for item_id in &resolved_mod.items {
            // Get the summary for the item (if it exists) to check its path
            if let Some(item_summary) = krate.paths.get(item_id) {
                // We only care about items from the local crate for initial selection (crate_id 0)
                if item_summary.crate_id == 0 {
                    let mut qualified_item_path = item_summary.path.clone();
                    // Ensure the path starts with the crate name if it doesn't already
                    if !qualified_item_path.is_empty()
                        && qualified_item_path[0] != normalized_crate_name
                    {
                        qualified_item_path.insert(0, normalized_crate_name.clone());
                    }

                    for filter in &normalized_filters {
                        if path_matches(&qualified_item_path, filter) {
                            debug!(
                                "Path filter {:?} matched item {:?} ({:?}) via module {:?}",
                                filter, qualified_item_path, item_id, resolved_mod.id
                            );
                            selected_ids.insert(*item_id);
                            // No break here, an item might be reachable via multiple modules/paths
                        }
                    }
                }
            }
        }
    }

    if selected_ids.is_empty() {
        warn!(
            "No items matched the provided path filters: {:?}",
            user_paths
        );
        // Still return the full graph even if selection is empty
        return Ok((selected_ids, graph));
    }

    info!(
        "Initially selected {} items based on path filters and resolved modules.",
        selected_ids.len()
    );

    // --- Iterative dependency selection (using the pre-built graph) ---
    let mut queue: VecDeque<Id> = selected_ids.iter().cloned().collect();
    let mut visited_for_selection = HashSet::new(); // Keep track of visited nodes during selection traversal

    while let Some(id) = queue.pop_front() {
        if !visited_for_selection.insert(id) {
            continue; // Already processed this item for dependency selection
        }

        // Find dependencies using the graph's adjacency list
        if let Some(children) = graph.get_children(&id) {
            for (dep_id, _label) in children {
                // Check if dep_id exists in krate.index before adding
                if krate.index.contains_key(dep_id) && selected_ids.insert(*dep_id) {
                    debug!("Including dependency {:?} from item {:?}", dep_id, id);
                    queue.push_back(*dep_id);
                }
            }
        }
    }

    info!(
        "Selected {} items after including dependencies.",
        selected_ids.len()
    );

    Ok((selected_ids, graph))
}

/// Finds dependencies for a single item AND adds corresponding edges to the graph.
/// Returns a HashSet of dependent IDs found for this item.
fn build_graph_for_item(source_id: Id, krate: &Crate, graph: &mut IdGraph) -> HashSet<Id> {
    let mut item_deps: HashSet<Id> = HashSet::new();

    if let Some(item) = krate.index.get(&source_id) {
        // 1. Direct Links (value is Id)
        for link_id_val in item.links.values() {
            // Check if link_id_val exists in krate.index before adding
            if krate.index.contains_key(link_id_val) && item_deps.insert(*link_id_val) {
                graph.add_edge(source_id, *link_id_val, EdgeLabel::IntraDocLink, krate);
            }
        }

        // 2. Item Kind Specific Dependencies
        match &item.inner {
            ItemEnum::Module(m) => {
                for item_id in &m.items {
                    if krate.index.contains_key(item_id) {
                        // Note: This edge represents the *original* module structure
                        // Resolution of 'use' happens separately for documentation generation.
                        graph.add_edge(source_id, *item_id, EdgeLabel::Contains, krate);
                        // Do NOT add to item_deps here, Contains edge handles it.
                        // Dependency resolution follows the graph edges later.
                    }
                }
            }
            ItemEnum::Use(use_item) => {
                // Add edge from Use item to its target ID (if it exists)
                if let Some(target_id) = use_item.id
                    && krate.index.contains_key(&target_id)
                    && item_deps.insert(target_id)
                {
                    graph.add_edge(source_id, target_id, EdgeLabel::UseTarget, krate);
                }
            }
            ItemEnum::Struct(s) => {
                for impl_id in &s.impls {
                    if krate.index.contains_key(impl_id) && item_deps.insert(*impl_id) {
                        graph.add_edge(source_id, *impl_id, EdgeLabel::ImplFor, krate);
                        // Struct -> Impl relation
                    }
                }
                find_generics_dependencies(&s.generics, source_id, krate, &mut item_deps, graph);
                match &s.kind {
                    rustdoc_types::StructKind::Plain { fields, .. } => {
                        for field_id in fields {
                            if krate.index.contains_key(field_id) {
                                if item_deps.insert(*field_id) {
                                    graph.add_edge(
                                        source_id,
                                        *field_id,
                                        EdgeLabel::StructField,
                                        krate,
                                    );
                                }
                                // Also get dependencies of the field's type
                                if let Some(field_item) = krate.index.get(field_id)
                                    && let ItemEnum::StructField(field_type) = &field_item.inner
                                {
                                    find_type_dependencies(
                                        field_type,
                                        *field_id, // Source is the field ID
                                        krate,
                                        &mut item_deps,
                                        graph,
                                        EdgeLabel::FieldType,
                                    );
                                }
                            }
                        }
                    }
                    rustdoc_types::StructKind::Tuple(fields) => {
                        for field_id in fields.iter().flatten() {
                            if krate.index.contains_key(field_id) {
                                if item_deps.insert(*field_id) {
                                    graph.add_edge(
                                        source_id,
                                        *field_id,
                                        EdgeLabel::StructField,
                                        krate,
                                    );
                                }
                                if let Some(field_item) = krate.index.get(field_id)
                                    && let ItemEnum::StructField(field_type) = &field_item.inner
                                {
                                    find_type_dependencies(
                                        field_type,
                                        *field_id, // Source is the field ID
                                        krate,
                                        &mut item_deps,
                                        graph,
                                        EdgeLabel::FieldType,
                                    );
                                }
                            }
                        }
                    }
                    rustdoc_types::StructKind::Unit => {}
                }
            }
            ItemEnum::Enum(e) => {
                for variant_id in &e.variants {
                    if krate.index.contains_key(variant_id) && item_deps.insert(*variant_id) {
                        graph.add_edge(source_id, *variant_id, EdgeLabel::EnumVariant, krate);
                    }
                }
                for impl_id in &e.impls {
                    if krate.index.contains_key(impl_id) && item_deps.insert(*impl_id) {
                        graph.add_edge(source_id, *impl_id, EdgeLabel::ImplFor, krate);
                    }
                }
                find_generics_dependencies(&e.generics, source_id, krate, &mut item_deps, graph);
            }
            ItemEnum::Variant(v) => {
                // Source is the enum containing this variant
                match &v.kind {
                    rustdoc_types::VariantKind::Plain => {}
                    rustdoc_types::VariantKind::Tuple(fields) => {
                        for field_id in fields.iter().flatten() {
                            if krate.index.contains_key(field_id) {
                                if item_deps.insert(*field_id) {
                                    graph.add_edge(
                                        source_id,
                                        *field_id,
                                        EdgeLabel::VariantField,
                                        krate,
                                    );
                                }
                                if let Some(field_item) = krate.index.get(field_id)
                                    && let ItemEnum::StructField(field_type) = &field_item.inner
                                {
                                    find_type_dependencies(
                                        field_type,
                                        *field_id,
                                        krate,
                                        &mut item_deps,
                                        graph,
                                        EdgeLabel::FieldType,
                                    );
                                }
                            }
                        }
                    }
                    rustdoc_types::VariantKind::Struct { fields, .. } => {
                        for field_id in fields {
                            if krate.index.contains_key(field_id) {
                                if item_deps.insert(*field_id) {
                                    graph.add_edge(
                                        source_id,
                                        *field_id,
                                        EdgeLabel::VariantField,
                                        krate,
                                    );
                                }
                                if let Some(field_item) = krate.index.get(field_id)
                                    && let ItemEnum::StructField(field_type) = &field_item.inner
                                {
                                    find_type_dependencies(
                                        field_type,
                                        *field_id,
                                        krate,
                                        &mut item_deps,
                                        graph,
                                        EdgeLabel::FieldType,
                                    );
                                }
                            }
                        }
                    }
                }
            }
            ItemEnum::Function(f) => {
                find_generics_dependencies(&f.generics, source_id, krate, &mut item_deps, graph);
                for (_name, param_type) in &f.sig.inputs {
                    find_type_dependencies(
                        param_type,
                        source_id,
                        krate,
                        &mut item_deps,
                        graph,
                        EdgeLabel::SignatureInput,
                    );
                }
                if let Some(output) = &f.sig.output {
                    find_type_dependencies(
                        output,
                        source_id,
                        krate,
                        &mut item_deps,
                        graph,
                        EdgeLabel::SignatureOutput,
                    );
                }
            }
            ItemEnum::Trait(t) => {
                for item_id in &t.items {
                    if krate.index.contains_key(item_id) && item_deps.insert(*item_id) {
                        graph.add_edge(source_id, *item_id, EdgeLabel::TraitItem, krate);
                    }
                }
                find_generics_dependencies(&t.generics, source_id, krate, &mut item_deps, graph);
                for bound in &t.bounds {
                    find_generic_bound_dependencies(
                        bound,
                        source_id,
                        krate,
                        &mut item_deps,
                        graph,
                        EdgeLabel::SuperTrait,
                    );
                }
                for impl_id in &t.implementations {
                    if krate.index.contains_key(impl_id) && item_deps.insert(*impl_id) {
                        // Relation Trait -> Impl Block (Implementor)
                        graph.add_edge(source_id, *impl_id, EdgeLabel::Implements, krate);
                    }
                }
            }
            ItemEnum::Impl(imp) => {
                for item_id in &imp.items {
                    if krate.index.contains_key(item_id) && item_deps.insert(*item_id) {
                        graph.add_edge(source_id, *item_id, EdgeLabel::ImplItem, krate);
                    }
                }
                if let Some(trait_path) = &imp.trait_ {
                    if krate.index.contains_key(&trait_path.id) && item_deps.insert(trait_path.id) {
                        graph.add_edge(source_id, trait_path.id, EdgeLabel::Implements, krate);
                    }
                    if let Some(args) = trait_path.args.as_ref() {
                        find_generic_args_dependencies(
                            args,
                            source_id,
                            krate,
                            &mut item_deps,
                            graph,
                        );
                    }
                }
                find_type_dependencies(
                    &imp.for_,
                    source_id,
                    krate,
                    &mut item_deps,
                    graph,
                    EdgeLabel::ImplFor,
                );
                find_generics_dependencies(&imp.generics, source_id, krate, &mut item_deps, graph);
            }
            ItemEnum::TypeAlias(ta) => {
                find_type_dependencies(
                    &ta.type_,
                    source_id,
                    krate,
                    &mut item_deps,
                    graph,
                    EdgeLabel::AliasTo,
                );
                find_generics_dependencies(&ta.generics, source_id, krate, &mut item_deps, graph);
            }
            ItemEnum::Constant { type_, .. } => {
                find_type_dependencies(
                    type_,
                    source_id,
                    krate,
                    &mut item_deps,
                    graph,
                    EdgeLabel::ReferencesType,
                );
            }
            ItemEnum::Static(s) => {
                find_type_dependencies(
                    &s.type_,
                    source_id,
                    krate,
                    &mut item_deps,
                    graph,
                    EdgeLabel::ReferencesType,
                );
            }
            ItemEnum::AssocConst { type_, .. } => {
                find_type_dependencies(
                    type_,
                    source_id,
                    krate,
                    &mut item_deps,
                    graph,
                    EdgeLabel::ReferencesType,
                );
            }
            ItemEnum::AssocType {
                generics,
                bounds,
                type_,
                ..
            } => {
                find_generics_dependencies(generics, source_id, krate, &mut item_deps, graph);
                for bound in bounds {
                    find_generic_bound_dependencies(
                        bound,
                        source_id,
                        krate,
                        &mut item_deps,
                        graph,
                        EdgeLabel::TraitBound, // Bound on associated type
                    );
                }
                if let Some(def_type) = type_ {
                    find_type_dependencies(
                        def_type,
                        source_id,
                        krate,
                        &mut item_deps,
                        graph,
                        EdgeLabel::ReferencesType, // Default type for assoc type
                    );
                }
            }
            ItemEnum::Union(u) => {
                find_generics_dependencies(&u.generics, source_id, krate, &mut item_deps, graph);
                for field_id in &u.fields {
                    if krate.index.contains_key(field_id) {
                        if item_deps.insert(*field_id) {
                            graph.add_edge(source_id, *field_id, EdgeLabel::UnionField, krate);
                        }
                        if let Some(field_item) = krate.index.get(field_id)
                            && let ItemEnum::StructField(field_type) = &field_item.inner
                        {
                            find_type_dependencies(
                                field_type,
                                *field_id,
                                krate,
                                &mut item_deps,
                                graph,
                                EdgeLabel::FieldType,
                            );
                        }
                    }
                }
                for impl_id in &u.impls {
                    if krate.index.contains_key(impl_id) && item_deps.insert(*impl_id) {
                        graph.add_edge(source_id, *impl_id, EdgeLabel::ImplFor, krate);
                    }
                }
            }
            ItemEnum::TraitAlias(ta) => {
                find_generics_dependencies(&ta.generics, source_id, krate, &mut item_deps, graph);
                for bound in &ta.params {
                    find_generic_bound_dependencies(
                        bound,
                        source_id,
                        krate,
                        &mut item_deps,
                        graph,
                        EdgeLabel::AliasTo, // Bounds defining the alias
                    );
                }
            }
            ItemEnum::StructField(ty) => {
                // source_id is the StructField item itself
                find_type_dependencies(
                    ty,
                    source_id,
                    krate,
                    &mut item_deps,
                    graph,
                    EdgeLabel::FieldType,
                );
            }
            // Items with no obvious ID dependencies representable in the graph
            ItemEnum::ExternType
            | ItemEnum::Macro(_)
            | ItemEnum::ProcMacro(_)
            | ItemEnum::Primitive(_)
            | ItemEnum::ExternCrate { .. } => {}
        }
    }
    item_deps
}

// --- Graph Dumping Logic ---

/// Helper to get item info string (name, path, kind)
fn get_item_info_string(id: &Id, krate: &Crate) -> String {
    let name_str = krate
        .index
        .get(id)
        .and_then(|item| item.name.as_deref())
        .unwrap_or("{unnamed}");
    let path_str = krate
        .paths
        .get(id)
        .map(|p| p.path.join("::"))
        .unwrap_or_else(|| "{no_path}".to_string());
    let kind_str = krate
        .index
        .get(id)
        .map(|item| format!("{:?}", crate::Printer::infer_item_kind(item))) // Reuse infer_item_kind
        .or_else(|| {
            krate
                .paths
                .get(id)
                .map(|summary| format!("{:?}", summary.kind))
        })
        .unwrap_or_else(|| "{UnknownKind}".to_string());

    format!(
        "Id({}): {} (Path: {}, Kind: {})",
        id.0, name_str, path_str, kind_str
    )
}

/// Recursive function to dump the graph structure.
#[allow(clippy::too_many_arguments)]
fn dump_node(
    node_id: Id,
    graph: &IdGraph, // Use the potentially filtered graph
    krate: &Crate,
    writer: &mut dyn IoWrite,         // Changed to dyn Write for flexibility
    visited: &mut HashSet<Id>,        // Use mutable reference to shared visited set
    path_to_target: &mut HashSet<Id>, // Tracks current path to target leaf
    indent: usize,
    depth: usize,                     // Current recursion depth
    max_depth: Option<usize>,         // Maximum allowed depth
    prefix: &str,                     // Prefix like "├── " or "└── "
    parent_label: Option<&EdgeLabel>, // Label connecting this node to its parent
    is_root_call: bool,               // Flag to know if this is the initial call for a root
) -> Result<()> {
    // Track current node in the path being explored towards the target
    let inserted_in_path = path_to_target.insert(node_id);

    // Determine if this node has already been visited *globally*
    let is_newly_visited = visited.insert(node_id);

    // Determine if we should print this node
    // Print if:
    // 1. It's the root of the current dump traversal (is_root_call is true)
    // 2. OR it's newly visited globally
    // 3. OR it's already visited globally BUT it's part of the current path to the target
    let should_print = is_root_call || is_newly_visited || path_to_target.contains(&node_id);

    if should_print {
        // Format the current node information
        let node_info = get_item_info_string(&node_id, krate);
        let label_info = parent_label
            .map(|l| format!(" [{}]", l))
            .unwrap_or_default();
        // Add cycle marker only if globally visited before AND relevant to current path
        let cycle_marker =
            if !is_newly_visited && path_to_target.contains(&node_id) && !is_root_call {
                " [... cycle or previously visited on current path ...]"
            } else if !is_newly_visited && !is_root_call {
                // This case should ideally not be reached often if filtering works, but indicates a visited node NOT on the current path
                " [... previously visited (not on current path) ...]" // This might still be printed if filter is off
            } else {
                ""
            };

        writeln!(
            writer,
            "{}{}{}{}{}",
            " ".repeat(indent),
            prefix,
            node_info,
            label_info,
            cycle_marker
        )?;
    }

    // Check depth limit *before* recursing
    if let Some(max) = max_depth
        && depth >= max
    {
        // If we've reached max depth and there are children, indicate truncation
        if is_newly_visited && graph.get_children(&node_id).is_some_and(|c| !c.is_empty()) {
            writeln!(
                writer,
                "{}{} [... children truncated due to max depth ...]",
                " ".repeat(indent + 4), // Indent the truncation message
                if graph.get_children(&node_id).unwrap().len() == 1 {
                    "└──"
                } else {
                    "├──"
                }  // Use appropriate prefix for one or more truncated children
            )?;
        }
        // Backtrack and return early if max depth is reached
        if inserted_in_path {
            path_to_target.remove(&node_id);
        }
        return Ok(());
    }

    // Recurse only if newly visited globally
    // (If !is_newly_visited, we've already explored its children from a previous encounter)
    if is_newly_visited {
        // Get children from the potentially filtered graph and sort them
        if let Some(children) = graph.get_children(&node_id) {
            let mut sorted_children = children.to_vec(); // Clone to sort

            // Sort by target Id primarily, then label for stability
            sorted_children.sort_by_key(|(target_id, label)| (target_id.0, format!("{}", label)));

            let num_children = sorted_children.len();
            for (i, (child_id, child_label)) in sorted_children.iter().enumerate() {
                let new_prefix = if i == num_children - 1 {
                    "└── "
                } else {
                    "├── "
                };
                let child_indent = indent + 4; // Indent children further

                // Recurse with the same mutable visited set and path_to_target set
                dump_node(
                    *child_id,
                    graph, // Pass the same graph down
                    krate,
                    writer,
                    visited,        // Pass mutable reference down
                    path_to_target, // Pass mutable reference down
                    child_indent,
                    depth + 1, // Increment depth for child
                    max_depth, // Pass max_depth down
                    new_prefix,
                    Some(child_label),
                    false, // Not a root call anymore
                )?;
            }
        }
    }

    // Backtrack: Remove current node from the path_to_target set *if* it was added by this call
    if inserted_in_path {
        path_to_target.remove(&node_id);
    }

    Ok(())
}

/// Dumps a subset of the dependency graph to a writer.
#[doc(hidden)]
pub fn dump_graph_subset(
    graph: &IdGraph, // Use the potentially filtered graph
    krate: &Crate,
    root_ids: &HashSet<Id>,
    writer: &mut dyn IoWrite, // Changed to dyn Write
    dump_description: &str,
    max_depth: Option<usize>, // Add max_depth parameter
) -> Result<()> {
    // Use a single visited set for the entire dump process across all roots
    let mut visited = HashSet::new();

    let mut sorted_roots: Vec<_> = root_ids.iter().cloned().collect();
    // Sort roots by Id for consistent output
    sorted_roots.sort_by_key(|id| id.0);

    if sorted_roots.is_empty() && !graph.edges.is_empty() {
        writeln!(
            writer,
            "Warning: Graph has edges but no {} roots found (potentially due to filtering or cycles). Dumping all nodes alphabetically:",
            dump_description
        )?;
        // Fallback: dump all nodes if no roots found
        let mut all_nodes: Vec<_> = graph.adjacency.keys().cloned().collect();
        all_nodes.sort_by_key(|id| id.0);
        for node_id in all_nodes {
            // Check if already visited globally
            if !visited.contains(&node_id) {
                // Initialize an empty path_to_target for this arbitrary root start
                let mut path_to_target = HashSet::new();
                dump_node(
                    node_id,
                    graph,
                    krate,
                    writer,
                    &mut visited,        // Pass shared mutable visited set
                    &mut path_to_target, // Pass new mutable path set
                    0,
                    0,         // Initial depth is 0
                    max_depth, // Pass max_depth
                    "",        // No prefix for top-level nodes in fallback
                    None,
                    true, // It's a root call in this fallback context
                )?;
            }
        }
    } else {
        writeln!(writer, "Graph Roots ({}):", dump_description)?;
        for root_id in sorted_roots {
            // Check if already visited globally before starting a new root traversal
            if !visited.contains(&root_id) {
                // Initialize a path_to_target set for *each* root dump traversal
                let mut path_to_target = HashSet::new();
                dump_node(
                    root_id,
                    graph,
                    krate,
                    writer,
                    &mut visited,        // Pass shared mutable visited set
                    &mut path_to_target, // Pass new mutable path set for this root
                    0,
                    0,         // Initial depth is 0
                    max_depth, // Pass max_depth
                    "",        // No prefix for root nodes
                    None,
                    true, // It's a root call
                )?;
            }
        }
    }
    Ok(())
}

// --- End Graph Dumping Logic ---