Skip to main content

lex_core/lex/ast/elements/
container.rs

1//! Rich, type-safe containers for AST tree traversal and querying
2//!
3//! # Design Philosophy
4//!
5//! This module implements **rich containers** - child-bearing nodes that are self-sufficient
6//! for all tree traversal and querying operations. This design makes container capabilities
7//! orthogonal to element type: any element using a Container<P> automatically gets the full
8//! query API without code duplication.
9//!
10//! ## Core Abstractions
11//!
12//! **Container<P>**: Generic container parameterized by a policy type P
13//! - Stores children as Vec<ContentItem>
14//! - Policy P determines nesting rules at compile time
15//! - Provides rich traversal, querying, and search APIs
16//! - Self-sufficient: all generic operations live here, not in element types
17//!
18//! **ContainerPolicy**: Trait defining what content is allowed
19//! - SessionPolicy: allows Sessions (unlimited nesting)
20//! - GeneralPolicy: no Sessions (prevents infinite nesting)
21//! - ListPolicy: only ListItem variants
22//! - VerbatimPolicy: only VerbatimLine nodes
23//!
24//! ## Container Types
25//!
26//! - **SessionContainer** = Container<SessionPolicy>
27//!   - Used by: Document.root, Session.children
28//!   - Allows: All ContentItem types including nested Sessions
29//!
30//! - **GeneralContainer** = Container<GeneralPolicy>
31//!   - Used by: Definition.children, Annotation.children, ListItem.children
32//!   - Allows: All ContentItem EXCEPT Sessions
33//!
34//! - **ListContainer** = Container<ListPolicy>
35//!   - Used by: List.items
36//!   - Allows: Only ListItem variants (homogeneous)
37//!
38//! - **VerbatimContainer** = Container<VerbatimPolicy>
39//!   - Used by: VerbatimBlock.children
40//!   - Allows: Only VerbatimLine nodes (homogeneous)
41//!
42//! ## Rich Query API
43//!
44//! All Container<P> types provide:
45//!
46//! **Direct child iteration:**
47//! ```ignore
48//! container.iter()                    // All immediate children
49//! container.iter_paragraphs()         // Only paragraph children
50//! container.iter_sessions()           // Only session children (if allowed by policy)
51//! ```
52//!
53//! **Recursive traversal:**
54//! ```ignore
55//! container.iter_all_nodes()                  // Depth-first pre-order
56//! container.iter_all_nodes_with_depth()       // With depth tracking
57//! container.iter_paragraphs_recursive()       // All paragraphs at any depth
58//! container.iter_sessions_recursive()         // All sessions at any depth
59//! ```
60//!
61//! **Searching and filtering:**
62//! ```ignore
63//! container.find_nodes(|n| n.is_paragraph())              // Generic predicate
64//! container.find_paragraphs(|p| p.text().contains("foo")) // Type-specific
65//! container.find_nodes_at_depth(2)                        // By depth
66//! container.find_nodes_in_depth_range(1, 3)               // Depth range
67//! ```
68//!
69//! **Convenience methods:**
70//! ```ignore
71//! container.first_paragraph()     // Option<&Paragraph>
72//! container.expect_paragraph()    // &Paragraph (panics if not found)
73//! container.count_by_type()       // (paragraphs, sessions, lists, verbatim)
74//! ```
75//!
76//! **Position-based queries:**
77//! ```ignore
78//! container.element_at(pos)               // Deepest element at position
79//! container.find_nodes_at_position(pos)   // All nodes containing position
80//! container.format_at_position(pos)       // Human-readable description
81//! ```
82//!
83//! ## Benefits of This Design
84//!
85//! 1. **No code duplication**: Session, Definition, Annotation, ListItem all get the same
86//!    rich API through their container field - no need to implement 400+ LOC in each.
87//!
88//! 2. **Type safety**: Policy system enforces nesting rules at compile time.
89//!    Cannot accidentally put a Session in a Definition.
90//!
91//! 3. **Clear structure**: Container's job is child management + querying.
92//!    Element's job is domain-specific behavior (title, subject, annotations, etc.).
93//!
94//! 4. **Uniform access**: Any code working with containers can use the same API
95//!    regardless of whether it's a Session, Definition, or Annotation.
96//!
97//! ## Accessing Container Children
98//!
99//! The `.children` field is private. Use one of these access patterns:
100//!
101//! **Deref coercion (preferred for Vec operations):**
102//! ```ignore
103//! let session = Session::new(...);
104//! for child in &session.children {  // Deref to &Vec<ContentItem>
105//!     // process child
106//! }
107//! let count = session.children.len();  // Works via Deref
108//! ```
109//!
110//! **ContentItem polymorphic access:**
111//! ```ignore
112//! fn process(item: &ContentItem) {
113//!     if let Some(children) = item.children() {
114//!         // Access children polymorphically
115//!     }
116//! }
117//! ```
118//!
119//! **Container trait:**
120//! ```ignore
121//! fn process<T: Container>(container: &T) {
122//!     let children = container.children();  // Returns &[ContentItem]
123//! }
124//! ```
125//!
126//! ## Implementation Notes
127//!
128//! - Macros generate repetitive iterator/finder methods (see bottom of file)
129//! - All traversal builds on ContentItem::descendants() primitive
130//! - Depth is 0-indexed from immediate children
131//! - Position queries return deepest (most nested) matching element
132//!
133//! See `docs/architecture/type-safe-containers.md` for compile-time safety guarantees.
134
135use super::super::range::{Position, Range};
136use super::super::traits::{AstNode, Visitor};
137use super::annotation::Annotation;
138use super::content_item::ContentItem;
139use super::definition::Definition;
140use super::list::{List, ListItem};
141use super::paragraph::Paragraph;
142use super::session::Session;
143use super::typed_content::{ContentElement, ListContent, SessionContent, VerbatimContent};
144use super::verbatim::Verbatim;
145use std::fmt;
146use std::marker::PhantomData;
147
148// ============================================================================
149// MACROS FOR GENERATING REPETITIVE ITERATOR/FINDER METHODS
150// ============================================================================
151
152/// Macro to generate recursive iterator methods for different AST node types
153macro_rules! impl_recursive_iterator {
154    ($method_name:ident, $type:ty, $as_method:ident, $doc:expr) => {
155        #[doc = $doc]
156        pub fn $method_name(&self) -> Box<dyn Iterator<Item = &$type> + '_> {
157            Box::new(self.iter_all_nodes().filter_map(|item| item.$as_method()))
158        }
159    };
160}
161
162/// Macro to generate "first" convenience methods
163macro_rules! impl_first_method {
164    ($method_name:ident, $type:ty, $iter_method:ident, $doc:expr) => {
165        #[doc = $doc]
166        pub fn $method_name(&self) -> Option<&$type> {
167            self.$iter_method().next()
168        }
169    };
170}
171
172/// Macro to generate predicate-based finder methods
173macro_rules! impl_find_method {
174    ($method_name:ident, $type:ty, $iter_method:ident, $doc:expr) => {
175        #[doc = $doc]
176        pub fn $method_name<F>(&self, predicate: F) -> Vec<&$type>
177        where
178            F: Fn(&$type) -> bool,
179        {
180            self.$iter_method().filter(|x| predicate(x)).collect()
181        }
182    };
183}
184
185// ============================================================================
186// CONTAINER POLICY TRAITS
187// ============================================================================
188
189/// Policy trait defining what content is allowed in a container.
190///
191/// This trait provides compile-time information about nesting rules.
192/// Each policy type defines which element types can be contained.
193pub trait ContainerPolicy: 'static {
194    /// The typed content variant this policy accepts
195    type ContentType: Into<ContentItem> + Clone;
196
197    /// Whether this container allows Session elements
198    const ALLOWS_SESSIONS: bool;
199
200    /// Whether this container allows Annotation elements
201    const ALLOWS_ANNOTATIONS: bool;
202
203    /// Human-readable name for error messages
204    const POLICY_NAME: &'static str;
205
206    /// Validate that an item is allowed in this container
207    ///
208    /// Returns Ok(()) if the item is valid, or an error message if not.
209    fn validate(item: &ContentItem) -> Result<(), String>;
210}
211
212/// Policy for Session containers - allows all elements including Sessions
213///
214/// Used by: Document.root, Session.children
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub struct SessionPolicy;
217
218impl ContainerPolicy for SessionPolicy {
219    type ContentType = SessionContent;
220
221    const ALLOWS_SESSIONS: bool = true;
222    const ALLOWS_ANNOTATIONS: bool = true;
223    const POLICY_NAME: &'static str = "SessionPolicy";
224
225    fn validate(_item: &ContentItem) -> Result<(), String> {
226        // SessionPolicy allows all content types
227        Ok(())
228    }
229}
230
231/// Policy for general containers - allows all elements EXCEPT Sessions
232///
233/// Used by: Definition.children, Annotation.children, ListItem.children
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub struct GeneralPolicy;
236
237impl ContainerPolicy for GeneralPolicy {
238    type ContentType = ContentElement;
239
240    const ALLOWS_SESSIONS: bool = false;
241    const ALLOWS_ANNOTATIONS: bool = true;
242    const POLICY_NAME: &'static str = "GeneralPolicy";
243
244    fn validate(item: &ContentItem) -> Result<(), String> {
245        match item {
246            ContentItem::Session(_) => Err("GeneralPolicy does not allow Sessions".to_string()),
247            ContentItem::ListItem(_) => {
248                Err("GeneralPolicy does not allow ListItems (use List instead)".to_string())
249            }
250            _ => Ok(()),
251        }
252    }
253}
254
255/// Policy for list containers - only allows ListItem elements
256///
257/// Used by: List.items
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub struct ListPolicy;
260
261impl ContainerPolicy for ListPolicy {
262    type ContentType = ListContent;
263
264    const ALLOWS_SESSIONS: bool = false;
265    const ALLOWS_ANNOTATIONS: bool = false;
266    const POLICY_NAME: &'static str = "ListPolicy";
267
268    fn validate(item: &ContentItem) -> Result<(), String> {
269        match item {
270            ContentItem::ListItem(_) => Ok(()),
271            _ => Err(format!(
272                "ListPolicy only allows ListItems, found {}",
273                item.node_type()
274            )),
275        }
276    }
277}
278
279/// Policy for verbatim containers - only allows VerbatimLine elements
280///
281/// Used by: VerbatimBlock.children
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub struct VerbatimPolicy;
284
285impl ContainerPolicy for VerbatimPolicy {
286    type ContentType = VerbatimContent;
287
288    const ALLOWS_SESSIONS: bool = false;
289    const ALLOWS_ANNOTATIONS: bool = false;
290    const POLICY_NAME: &'static str = "VerbatimPolicy";
291
292    fn validate(item: &ContentItem) -> Result<(), String> {
293        match item {
294            ContentItem::VerbatimLine(_) => Ok(()),
295            _ => Err(format!(
296                "VerbatimPolicy only allows VerbatimLines, found {}",
297                item.node_type()
298            )),
299        }
300    }
301}
302
303// ============================================================================
304// CONTAINER TYPES
305// ============================================================================
306
307/// Generic container with compile-time policy enforcement
308///
309/// The policy type parameter P determines what content is allowed in this container.
310/// See the ContainerPolicy trait for available policies.
311#[derive(Debug, Clone, PartialEq)]
312pub struct Container<P: ContainerPolicy> {
313    children: Vec<ContentItem>,
314    pub location: Range,
315    _policy: PhantomData<P>,
316}
317
318// ============================================================================
319// TYPE ALIASES
320// ============================================================================
321
322/// SessionContainer allows any ContentItem including nested Sessions
323///
324/// Used for document-level containers where unlimited Session nesting is allowed.
325pub type SessionContainer = Container<SessionPolicy>;
326
327/// GeneralContainer allows any ContentItem EXCEPT Sessions
328///
329/// Used for Definition, Annotation, and ListItem children where Session nesting
330/// is prohibited.
331pub type GeneralContainer = Container<GeneralPolicy>;
332
333/// ListContainer is a homogeneous container for ListItem variants only
334///
335/// Used by List.items to enforce that lists only contain list items.
336pub type ListContainer = Container<ListPolicy>;
337
338/// VerbatimContainer is a homogeneous container for VerbatimLine nodes only
339///
340/// Used by VerbatimBlock.children to enforce that verbatim blocks only contain
341/// verbatim lines (content from other formats).
342pub type VerbatimContainer = Container<VerbatimPolicy>;
343
344// ============================================================================
345// GENERIC CONTAINER IMPLEMENTATION
346// ============================================================================
347
348impl<P: ContainerPolicy> Container<P> {
349    /// Create a type-safe container from typed content
350    ///
351    /// This is the preferred way to create containers as it enforces nesting rules
352    /// at compile time via the policy's ContentType.
353    ///
354    /// # Status
355    ///
356    /// Element constructors (Session::new, Definition::new, Annotation::new) now accept
357    /// typed content directly. This helper remains useful for tests or manual AST
358    /// construction where callers want explicit control over container policies.
359    pub fn from_typed(children: Vec<P::ContentType>) -> Self {
360        Self {
361            children: children.into_iter().map(|c| c.into()).collect(),
362            location: Range::default(),
363            _policy: PhantomData,
364        }
365    }
366
367    /// Create an empty container
368    pub fn empty() -> Self {
369        Self {
370            children: Vec::new(),
371            location: Range::default(),
372            _policy: PhantomData,
373        }
374    }
375
376    /// Set the location for this container (builder pattern)
377    pub fn at(mut self, location: Range) -> Self {
378        self.location = location;
379        self
380    }
381
382    /// Get the number of children
383    pub fn len(&self) -> usize {
384        self.children.len()
385    }
386
387    /// Check if the container is empty
388    pub fn is_empty(&self) -> bool {
389        self.children.is_empty()
390    }
391
392    /// Add a child to the container (type-safe, preferred method)
393    ///
394    /// This method accepts the policy's typed content, ensuring compile-time safety.
395    ///
396    /// # Example
397    /// ```ignore
398    /// let mut container = GeneralContainer::empty();
399    /// let para = Paragraph::from_line("text".to_string());
400    /// container.push_typed(ContentElement::Paragraph(para));
401    /// ```
402    pub fn push_typed(&mut self, item: P::ContentType) {
403        self.children.push(item.into());
404    }
405
406    /// Add a child to the container with runtime validation
407    ///
408    /// This method accepts any ContentItem and validates it against the policy at runtime.
409    /// Use this when you need polymorphic access to containers.
410    ///
411    /// # Panics
412    /// Panics if the item violates the container's policy (e.g., adding a Session to a GeneralContainer).
413    ///
414    /// # Example
415    /// ```ignore
416    /// let mut container = GeneralContainer::empty();
417    /// let para = Paragraph::from_line("text".to_string());
418    /// container.push(ContentItem::Paragraph(para));  // OK
419    ///
420    /// let session = Session::with_title("Title".to_string());
421    /// container.push(ContentItem::Session(session));  // Panics!
422    /// ```
423    pub fn push(&mut self, item: ContentItem) {
424        P::validate(&item)
425            .unwrap_or_else(|err| panic!("Invalid item for {}: {}", P::POLICY_NAME, err));
426        self.children.push(item);
427    }
428
429    /// Extend the container with multiple items (with validation)
430    ///
431    /// # Panics
432    /// Panics if any item violates the container's policy.
433    pub fn extend<I>(&mut self, items: I)
434    where
435        I: IntoIterator<Item = ContentItem>,
436    {
437        for item in items {
438            self.push(item);
439        }
440    }
441
442    /// Get a specific child by index
443    pub fn get(&self, index: usize) -> Option<&ContentItem> {
444        self.children.get(index)
445    }
446
447    /// Get a mutable reference to a specific child
448    ///
449    /// Note: This allows mutation of the child itself, but not replacement with a different type.
450    pub fn get_mut(&mut self, index: usize) -> Option<&mut ContentItem> {
451        self.children.get_mut(index)
452    }
453
454    /// Remove all children from the container
455    pub fn clear(&mut self) {
456        self.children.clear();
457    }
458
459    /// Remove and return the child at the specified index
460    ///
461    /// # Panics
462    /// Panics if index is out of bounds.
463    pub fn remove(&mut self, index: usize) -> ContentItem {
464        self.children.remove(index)
465    }
466
467    /// Get an iterator over the children
468    pub fn iter(&self) -> std::slice::Iter<'_, ContentItem> {
469        self.children.iter()
470    }
471
472    /// Get a mutable iterator over the children
473    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, ContentItem> {
474        self.children.iter_mut()
475    }
476
477    /// Get mutable access to the underlying Vec for advanced operations
478    ///
479    /// # Safety
480    /// This method is intended for internal use by assembler stages that need
481    /// Vec-specific operations like `retain()`. Direct manipulation bypasses
482    /// policy validation - use with caution.
483    ///
484    /// Prefer `push()`, `push_typed()`, or other validated methods when possible.
485    pub fn as_mut_vec(&mut self) -> &mut Vec<ContentItem> {
486        &mut self.children
487    }
488
489    // ========================================================================
490    // DIRECT CHILD ITERATION (non-recursive, immediate children only)
491    // ========================================================================
492
493    /// Iterate over immediate paragraph children
494    pub fn iter_paragraphs(&self) -> impl Iterator<Item = &Paragraph> {
495        self.children.iter().filter_map(|item| item.as_paragraph())
496    }
497
498    /// Iterate over immediate session children
499    pub fn iter_sessions(&self) -> impl Iterator<Item = &Session> {
500        self.children.iter().filter_map(|item| item.as_session())
501    }
502
503    /// Iterate over immediate list children
504    pub fn iter_lists(&self) -> impl Iterator<Item = &List> {
505        self.children.iter().filter_map(|item| item.as_list())
506    }
507
508    /// Iterate over immediate verbatim block children
509    pub fn iter_verbatim_blocks(&self) -> impl Iterator<Item = &Verbatim> {
510        self.children
511            .iter()
512            .filter_map(|item| item.as_verbatim_block())
513    }
514
515    /// Iterate over immediate definition children
516    pub fn iter_definitions(&self) -> impl Iterator<Item = &Definition> {
517        self.children.iter().filter_map(|item| item.as_definition())
518    }
519
520    /// Iterate over immediate annotation children
521    pub fn iter_annotations(&self) -> impl Iterator<Item = &Annotation> {
522        self.children.iter().filter_map(|item| item.as_annotation())
523    }
524
525    // ========================================================================
526    // RECURSIVE TRAVERSAL
527    // ========================================================================
528
529    /// Iterate all nodes in the container tree (depth-first pre-order traversal)
530    pub fn iter_all_nodes(&self) -> Box<dyn Iterator<Item = &ContentItem> + '_> {
531        Box::new(
532            self.children
533                .iter()
534                .flat_map(|item| std::iter::once(item).chain(item.descendants())),
535        )
536    }
537
538    /// Iterate all nodes with their depth (0 = immediate children)
539    pub fn iter_all_nodes_with_depth(
540        &self,
541    ) -> Box<dyn Iterator<Item = (&ContentItem, usize)> + '_> {
542        Box::new(
543            self.children
544                .iter()
545                .flat_map(|item| std::iter::once((item, 0)).chain(item.descendants_with_depth(1))),
546        )
547    }
548
549    impl_recursive_iterator!(
550        iter_paragraphs_recursive,
551        Paragraph,
552        as_paragraph,
553        "Recursively iterate all paragraphs at any depth in the container"
554    );
555    impl_recursive_iterator!(
556        iter_sessions_recursive,
557        Session,
558        as_session,
559        "Recursively iterate all sessions at any depth in the container"
560    );
561    impl_recursive_iterator!(
562        iter_lists_recursive,
563        List,
564        as_list,
565        "Recursively iterate all lists at any depth in the container"
566    );
567    impl_recursive_iterator!(
568        iter_verbatim_blocks_recursive,
569        Verbatim,
570        as_verbatim_block,
571        "Recursively iterate all verbatim blocks at any depth in the container"
572    );
573    impl_recursive_iterator!(
574        iter_list_items_recursive,
575        ListItem,
576        as_list_item,
577        "Recursively iterate all list items at any depth in the container"
578    );
579    impl_recursive_iterator!(
580        iter_definitions_recursive,
581        Definition,
582        as_definition,
583        "Recursively iterate all definitions at any depth in the container"
584    );
585    impl_recursive_iterator!(
586        iter_annotations_recursive,
587        Annotation,
588        as_annotation,
589        "Recursively iterate all annotations at any depth in the container"
590    );
591
592    // ========================================================================
593    // CONVENIENCE "FIRST" METHODS
594    // ========================================================================
595
596    impl_first_method!(
597        first_paragraph,
598        Paragraph,
599        iter_paragraphs_recursive,
600        "Get the first paragraph in the container (returns None if not found)"
601    );
602    impl_first_method!(
603        first_session,
604        Session,
605        iter_sessions_recursive,
606        "Get the first session in the container tree (returns None if not found)"
607    );
608    impl_first_method!(
609        first_list,
610        List,
611        iter_lists_recursive,
612        "Get the first list in the container (returns None if not found)"
613    );
614    impl_first_method!(
615        first_definition,
616        Definition,
617        iter_definitions_recursive,
618        "Get the first definition in the container (returns None if not found)"
619    );
620    impl_first_method!(
621        first_annotation,
622        Annotation,
623        iter_annotations_recursive,
624        "Get the first annotation in the container (returns None if not found)"
625    );
626    impl_first_method!(
627        first_verbatim,
628        Verbatim,
629        iter_verbatim_blocks_recursive,
630        "Get the first verbatim block in the container (returns None if not found)"
631    );
632
633    // ========================================================================
634    // "EXPECT" METHODS (panic if not found)
635    // ========================================================================
636
637    /// Get the first paragraph, panicking if none found
638    pub fn expect_paragraph(&self) -> &Paragraph {
639        self.first_paragraph()
640            .expect("No paragraph found in container")
641    }
642
643    /// Get the first session, panicking if none found
644    pub fn expect_session(&self) -> &Session {
645        self.first_session()
646            .expect("No session found in container tree")
647    }
648
649    /// Get the first list, panicking if none found
650    pub fn expect_list(&self) -> &List {
651        self.first_list().expect("No list found in container")
652    }
653
654    /// Get the first definition, panicking if none found
655    pub fn expect_definition(&self) -> &Definition {
656        self.first_definition()
657            .expect("No definition found in container")
658    }
659
660    /// Get the first annotation, panicking if none found
661    pub fn expect_annotation(&self) -> &Annotation {
662        self.first_annotation()
663            .expect("No annotation found in container")
664    }
665
666    /// Get the first verbatim block, panicking if none found
667    pub fn expect_verbatim(&self) -> &Verbatim {
668        self.first_verbatim()
669            .expect("No verbatim block found in container")
670    }
671
672    // ========================================================================
673    // PREDICATE-BASED SEARCH
674    // ========================================================================
675
676    impl_find_method!(
677        find_paragraphs,
678        Paragraph,
679        iter_paragraphs_recursive,
680        "Find all paragraphs matching a predicate"
681    );
682    impl_find_method!(
683        find_sessions,
684        Session,
685        iter_sessions_recursive,
686        "Find all sessions matching a predicate"
687    );
688    impl_find_method!(
689        find_lists,
690        List,
691        iter_lists_recursive,
692        "Find all lists matching a predicate"
693    );
694    impl_find_method!(
695        find_definitions,
696        Definition,
697        iter_definitions_recursive,
698        "Find all definitions matching a predicate"
699    );
700    impl_find_method!(
701        find_annotations,
702        Annotation,
703        iter_annotations_recursive,
704        "Find all annotations matching a predicate"
705    );
706
707    /// Find all nodes matching a generic predicate
708    pub fn find_nodes<F>(&self, predicate: F) -> Vec<&ContentItem>
709    where
710        F: Fn(&ContentItem) -> bool,
711    {
712        self.iter_all_nodes().filter(|n| predicate(n)).collect()
713    }
714
715    // ========================================================================
716    // DEPTH-BASED QUERIES
717    // ========================================================================
718
719    /// Find all nodes at a specific depth (0 = immediate children)
720    pub fn find_nodes_at_depth(&self, target_depth: usize) -> Vec<&ContentItem> {
721        self.iter_all_nodes_with_depth()
722            .filter(|(_, depth)| *depth == target_depth)
723            .map(|(node, _)| node)
724            .collect()
725    }
726
727    /// Find all nodes within a depth range (inclusive)
728    pub fn find_nodes_in_depth_range(
729        &self,
730        min_depth: usize,
731        max_depth: usize,
732    ) -> Vec<&ContentItem> {
733        self.iter_all_nodes_with_depth()
734            .filter(|(_, depth)| *depth >= min_depth && *depth <= max_depth)
735            .map(|(node, _)| node)
736            .collect()
737    }
738
739    /// Find nodes at a specific depth matching a predicate
740    pub fn find_nodes_with_depth<F>(&self, target_depth: usize, predicate: F) -> Vec<&ContentItem>
741    where
742        F: Fn(&ContentItem) -> bool,
743    {
744        self.iter_all_nodes_with_depth()
745            .filter(|(node, depth)| *depth == target_depth && predicate(node))
746            .map(|(node, _)| node)
747            .collect()
748    }
749
750    // ========================================================================
751    // COUNTING AND STATISTICS
752    // ========================================================================
753
754    /// Count immediate children by type (paragraphs, sessions, lists, verbatim)
755    pub fn count_by_type(&self) -> (usize, usize, usize, usize) {
756        let paragraphs = self.iter_paragraphs().count();
757        let sessions = self.iter_sessions().count();
758        let lists = self.iter_lists().count();
759        let verbatim_blocks = self.iter_verbatim_blocks().count();
760        (paragraphs, sessions, lists, verbatim_blocks)
761    }
762
763    // ========================================================================
764    // POSITION-BASED QUERIES
765    // ========================================================================
766
767    /// Returns the deepest (most nested) element that contains the position
768    pub fn element_at(&self, pos: Position) -> Option<&ContentItem> {
769        for item in &self.children {
770            if let Some(result) = item.element_at(pos) {
771                return Some(result);
772            }
773        }
774        None
775    }
776
777    /// Returns the visual line element at the given position
778    ///
779    /// Returns the element representing a source line (TextLine, ListItem, VerbatimLine,
780    /// BlankLineGroup, or header nodes like Session/Definition).
781    pub fn visual_line_at(&self, pos: Position) -> Option<&ContentItem> {
782        for item in &self.children {
783            if let Some(result) = item.visual_line_at(pos) {
784                return Some(result);
785            }
786        }
787        None
788    }
789
790    /// Returns the block element at the given position
791    ///
792    /// Returns the shallowest block-level container element (Session, Definition, List,
793    /// Paragraph, Annotation, VerbatimBlock) that contains the position.
794    pub fn block_element_at(&self, pos: Position) -> Option<&ContentItem> {
795        for item in &self.children {
796            if let Some(result) = item.block_element_at(pos) {
797                return Some(result);
798            }
799        }
800        None
801    }
802
803    /// Returns the path of nodes at the given position
804    pub fn node_path_at_position(&self, pos: Position) -> Vec<&ContentItem> {
805        for item in &self.children {
806            let path = item.node_path_at_position(pos);
807            if !path.is_empty() {
808                return path;
809            }
810        }
811        Vec::new()
812    }
813
814    /// Returns the deepest AST node at the given position, if any
815    pub fn find_nodes_at_position(&self, position: Position) -> Vec<&dyn AstNode> {
816        if let Some(item) = self.element_at(position) {
817            vec![item as &dyn AstNode]
818        } else {
819            Vec::new()
820        }
821    }
822
823    /// Formats information about nodes located at a given position
824    pub fn format_at_position(&self, position: Position) -> String {
825        let nodes = self.find_nodes_at_position(position);
826        if nodes.is_empty() {
827            "No AST nodes at this position".to_string()
828        } else {
829            nodes
830                .iter()
831                .map(|node| format!("- {}: {}", node.node_type(), node.display_label()))
832                .collect::<Vec<_>>()
833                .join("\n")
834        }
835    }
836}
837
838impl<P: ContainerPolicy> AstNode for Container<P> {
839    fn node_type(&self) -> &'static str {
840        P::POLICY_NAME
841    }
842
843    fn display_label(&self) -> String {
844        format!("{} items", self.children.len())
845    }
846
847    fn range(&self) -> &Range {
848        &self.location
849    }
850
851    fn accept(&self, visitor: &mut dyn Visitor) {
852        // Container itself doesn't have a visit method
853        // It delegates to its children
854        super::super::traits::visit_children(visitor, &self.children);
855    }
856}
857
858// Implement Deref for ergonomic read-only access to the children
859impl<P: ContainerPolicy> std::ops::Deref for Container<P> {
860    type Target = [ContentItem];
861
862    fn deref(&self) -> &Self::Target {
863        &self.children
864    }
865}
866
867// DerefMut is intentionally NOT implemented to prevent bypassing type safety.
868// Use explicit methods like push(), push_typed(), get_mut(), etc. instead.
869
870impl<P: ContainerPolicy> fmt::Display for Container<P> {
871    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
872        write!(f, "{}({} items)", P::POLICY_NAME, self.children.len())
873    }
874}
875
876// Implement IntoIterator to allow for loops over Container
877impl<'a, P: ContainerPolicy> IntoIterator for &'a Container<P> {
878    type Item = &'a ContentItem;
879    type IntoIter = std::slice::Iter<'a, ContentItem>;
880
881    fn into_iter(self) -> Self::IntoIter {
882        self.children.iter()
883    }
884}
885
886impl<'a, P: ContainerPolicy> IntoIterator for &'a mut Container<P> {
887    type Item = &'a mut ContentItem;
888    type IntoIter = std::slice::IterMut<'a, ContentItem>;
889
890    fn into_iter(self) -> Self::IntoIter {
891        self.children.iter_mut()
892    }
893}
894
895#[cfg(test)]
896mod tests {
897    use super::super::list::ListItem;
898    use super::super::paragraph::Paragraph;
899    use super::super::session::Session;
900    use super::super::typed_content::{ContentElement, ListContent, SessionContent};
901    use super::*;
902
903    // ========================================================================
904    // BASIC CONTAINER CREATION
905    // ========================================================================
906
907    #[test]
908    fn test_session_container_creation() {
909        let container = SessionContainer::empty();
910        assert_eq!(container.len(), 0);
911        assert!(container.is_empty());
912    }
913
914    #[test]
915    fn test_general_container_creation() {
916        let container = GeneralContainer::empty();
917        assert_eq!(container.len(), 0);
918        assert!(container.is_empty());
919    }
920
921    #[test]
922    fn test_list_container_creation() {
923        let container = ListContainer::empty();
924        assert_eq!(container.len(), 0);
925        assert!(container.is_empty());
926    }
927
928    #[test]
929    fn test_verbatim_container_creation() {
930        let container = VerbatimContainer::empty();
931        assert_eq!(container.len(), 0);
932        assert!(container.is_empty());
933    }
934
935    #[test]
936    fn test_container_with_items() {
937        let para = Paragraph::from_line("Test".to_string());
938        let container = SessionContainer::from_typed(vec![SessionContent::Element(
939            ContentElement::Paragraph(para),
940        )]);
941        assert_eq!(container.len(), 1);
942        assert!(!container.is_empty());
943    }
944
945    #[test]
946    fn test_container_push() {
947        let mut container = GeneralContainer::empty();
948        let para = Paragraph::from_line("Test".to_string());
949        container.push(ContentItem::Paragraph(para));
950        assert_eq!(container.len(), 1);
951    }
952
953    #[test]
954    fn test_container_deref() {
955        let list_item = ListItem::new("-".to_string(), "Item".to_string());
956        let container = ListContainer::from_typed(vec![ListContent::ListItem(list_item)]);
957        // Should be able to use Vec methods directly via Deref
958        assert_eq!(container.len(), 1);
959        assert!(!container.is_empty());
960    }
961
962    // ========================================================================
963    // RECURSIVE ITERATION
964    // ========================================================================
965
966    #[test]
967    fn test_iter_paragraphs_recursive() {
968        let mut inner_session = Session::with_title("Inner".to_string());
969        inner_session
970            .children
971            .push(ContentItem::Paragraph(Paragraph::from_line(
972                "Nested 2".to_string(),
973            )));
974
975        let mut outer_session = Session::with_title("Outer".to_string());
976        outer_session
977            .children
978            .push(ContentItem::Paragraph(Paragraph::from_line(
979                "Nested 1".to_string(),
980            )));
981        outer_session
982            .children
983            .push(ContentItem::Session(inner_session));
984
985        let mut container = SessionContainer::empty();
986        container.push(ContentItem::Paragraph(Paragraph::from_line(
987            "Top".to_string(),
988        )));
989        container.push(ContentItem::Session(outer_session));
990
991        assert_eq!(container.iter_paragraphs().count(), 1);
992        let paragraphs: Vec<_> = container.iter_paragraphs_recursive().collect();
993        assert_eq!(paragraphs.len(), 3);
994    }
995
996    #[test]
997    fn test_iter_sessions_recursive() {
998        let inner_session = Session::with_title("Inner".to_string());
999        let mut outer_session = Session::with_title("Outer".to_string());
1000        outer_session
1001            .children
1002            .push(ContentItem::Session(inner_session));
1003
1004        let mut container = SessionContainer::empty();
1005        container.push(ContentItem::Session(outer_session));
1006
1007        assert_eq!(container.iter_sessions().count(), 1);
1008        assert_eq!(container.iter_sessions_recursive().count(), 2);
1009    }
1010
1011    #[test]
1012    fn test_iter_all_nodes_with_depth() {
1013        let mut inner_session = Session::with_title("Inner".to_string());
1014        inner_session
1015            .children
1016            .push(ContentItem::Paragraph(Paragraph::from_line(
1017                "Deep".to_string(),
1018            )));
1019
1020        let mut outer_session = Session::with_title("Outer".to_string());
1021        outer_session
1022            .children
1023            .push(ContentItem::Session(inner_session));
1024
1025        let mut container = SessionContainer::empty();
1026        container.push(ContentItem::Paragraph(Paragraph::from_line(
1027            "Top".to_string(),
1028        )));
1029        container.push(ContentItem::Session(outer_session));
1030
1031        let nodes_with_depth: Vec<_> = container.iter_all_nodes_with_depth().collect();
1032        assert_eq!(nodes_with_depth.len(), 6);
1033        assert_eq!(nodes_with_depth[0].1, 0);
1034        assert!(nodes_with_depth[0].0.is_paragraph());
1035        assert_eq!(nodes_with_depth[1].1, 1);
1036        assert!(nodes_with_depth[1].0.is_text_line());
1037    }
1038
1039    // ========================================================================
1040    // PREDICATE-BASED SEARCH
1041    // ========================================================================
1042
1043    #[test]
1044    fn test_find_paragraphs_with_predicate() {
1045        let mut container = SessionContainer::empty();
1046        container.push(ContentItem::Paragraph(Paragraph::from_line(
1047            "Hello, world!".to_string(),
1048        )));
1049        container.push(ContentItem::Paragraph(Paragraph::from_line(
1050            "Goodbye, world!".to_string(),
1051        )));
1052        container.push(ContentItem::Paragraph(Paragraph::from_line(
1053            "Hello again!".to_string(),
1054        )));
1055
1056        let hello_paras = container.find_paragraphs(|p| p.text().starts_with("Hello"));
1057        assert_eq!(hello_paras.len(), 2);
1058
1059        let goodbye_paras = container.find_paragraphs(|p| p.text().contains("Goodbye"));
1060        assert_eq!(goodbye_paras.len(), 1);
1061    }
1062
1063    #[test]
1064    fn test_find_sessions_with_predicate() {
1065        let mut session1 = Session::with_title("Chapter 1: Introduction".to_string());
1066        session1
1067            .children
1068            .push(ContentItem::Paragraph(Paragraph::from_line(
1069                "Intro".to_string(),
1070            )));
1071        let session2 = Session::with_title("Chapter 2: Advanced".to_string());
1072        let section = Session::with_title("Section 1.1".to_string());
1073        session1.children.push(ContentItem::Session(section));
1074
1075        let mut container = SessionContainer::empty();
1076        container.push(ContentItem::Session(session1));
1077        container.push(ContentItem::Session(session2));
1078
1079        let chapters = container.find_sessions(|s| s.title.as_string().contains("Chapter"));
1080        assert_eq!(chapters.len(), 2);
1081    }
1082
1083    #[test]
1084    fn test_find_nodes_generic_predicate() {
1085        let mut session = Session::with_title("Test".to_string());
1086        session
1087            .children
1088            .push(ContentItem::Paragraph(Paragraph::from_line(
1089                "Child 1".to_string(),
1090            )));
1091        session
1092            .children
1093            .push(ContentItem::Paragraph(Paragraph::from_line(
1094                "Child 2".to_string(),
1095            )));
1096        session
1097            .children
1098            .push(ContentItem::Paragraph(Paragraph::from_line(
1099                "Child 3".to_string(),
1100            )));
1101
1102        let mut container = SessionContainer::empty();
1103        container.push(ContentItem::Paragraph(Paragraph::from_line(
1104            "Top".to_string(),
1105        )));
1106        container.push(ContentItem::Session(session));
1107
1108        let big_sessions = container.find_nodes(|node| {
1109            matches!(node, ContentItem::Session(_))
1110                && node.children().map(|c| c.len() > 2).unwrap_or(false)
1111        });
1112        assert_eq!(big_sessions.len(), 1);
1113    }
1114
1115    // ========================================================================
1116    // DEPTH-BASED QUERIES
1117    // ========================================================================
1118
1119    #[test]
1120    fn test_find_nodes_at_depth() {
1121        let mut inner = Session::with_title("Inner".to_string());
1122        inner
1123            .children
1124            .push(ContentItem::Paragraph(Paragraph::from_line(
1125                "Deep".to_string(),
1126            )));
1127        let mut outer = Session::with_title("Outer".to_string());
1128        outer.children.push(ContentItem::Session(inner));
1129
1130        let mut container = SessionContainer::empty();
1131        container.push(ContentItem::Paragraph(Paragraph::from_line(
1132            "Top".to_string(),
1133        )));
1134        container.push(ContentItem::Session(outer));
1135
1136        assert_eq!(container.find_nodes_at_depth(0).len(), 2);
1137        assert!(!container.find_nodes_at_depth(1).is_empty());
1138    }
1139
1140    #[test]
1141    fn test_find_sessions_at_depth() {
1142        let mut level2 = Session::with_title("Level 2".to_string());
1143        level2
1144            .children
1145            .push(ContentItem::Paragraph(Paragraph::from_line(
1146                "Leaf".to_string(),
1147            )));
1148        let mut level1 = Session::with_title("Level 1".to_string());
1149        level1.children.push(ContentItem::Session(level2));
1150
1151        let mut container = SessionContainer::empty();
1152        container.push(ContentItem::Session(level1));
1153
1154        let level_0: Vec<_> = container
1155            .find_nodes_at_depth(0)
1156            .into_iter()
1157            .filter_map(|n| n.as_session())
1158            .collect();
1159        assert_eq!(level_0.len(), 1);
1160
1161        let level_1: Vec<_> = container
1162            .find_nodes_at_depth(1)
1163            .into_iter()
1164            .filter_map(|n| n.as_session())
1165            .collect();
1166        assert_eq!(level_1.len(), 1);
1167    }
1168
1169    #[test]
1170    fn test_find_nodes_in_depth_range() {
1171        let mut deep = Session::with_title("Deep".to_string());
1172        deep.children
1173            .push(ContentItem::Paragraph(Paragraph::from_line(
1174                "Very deep".to_string(),
1175            )));
1176        let mut mid = Session::with_title("Mid".to_string());
1177        mid.children.push(ContentItem::Session(deep));
1178
1179        let mut container = SessionContainer::empty();
1180        container.push(ContentItem::Paragraph(Paragraph::from_line(
1181            "Root".to_string(),
1182        )));
1183        container.push(ContentItem::Session(mid));
1184
1185        assert!(!container.find_nodes_in_depth_range(0, 1).is_empty());
1186        assert!(!container.find_nodes_in_depth_range(1, 2).is_empty());
1187    }
1188
1189    #[test]
1190    fn test_find_nodes_with_depth_and_predicate() {
1191        let mut session = Session::with_title("Test Session".to_string());
1192        session
1193            .children
1194            .push(ContentItem::Paragraph(Paragraph::from_line(
1195                "Hello from nested".to_string(),
1196            )));
1197
1198        let mut container = SessionContainer::empty();
1199        container.push(ContentItem::Paragraph(Paragraph::from_line(
1200            "Hello from top".to_string(),
1201        )));
1202        container.push(ContentItem::Session(session));
1203
1204        let depth_0_hello = container.find_nodes_with_depth(0, |node| {
1205            node.as_paragraph()
1206                .map(|p| p.text().contains("Hello"))
1207                .unwrap_or(false)
1208        });
1209        assert_eq!(depth_0_hello.len(), 1);
1210    }
1211
1212    // ========================================================================
1213    // COMPREHENSIVE QUERY EXAMPLES
1214    // ========================================================================
1215
1216    #[test]
1217    fn test_comprehensive_query_api() {
1218        let mut chapter1 = Session::with_title("Chapter 1: Introduction".to_string());
1219        chapter1
1220            .children
1221            .push(ContentItem::Paragraph(Paragraph::from_line(
1222                "Hello, this is the intro.".to_string(),
1223            )));
1224
1225        let mut section1_1 = Session::with_title("Section 1.1".to_string());
1226        section1_1
1227            .children
1228            .push(ContentItem::Paragraph(Paragraph::from_line(
1229                "Nested content here.".to_string(),
1230            )));
1231        chapter1.children.push(ContentItem::Session(section1_1));
1232
1233        let mut chapter2 = Session::with_title("Chapter 2: Advanced".to_string());
1234        chapter2
1235            .children
1236            .push(ContentItem::Paragraph(Paragraph::from_line(
1237                "Advanced topics.".to_string(),
1238            )));
1239
1240        let mut container = SessionContainer::empty();
1241        container.push(ContentItem::Paragraph(Paragraph::from_line(
1242            "Preamble".to_string(),
1243        )));
1244        container.push(ContentItem::Session(chapter1));
1245        container.push(ContentItem::Session(chapter2));
1246
1247        assert_eq!(container.iter_paragraphs_recursive().count(), 4);
1248        assert_eq!(container.iter_sessions_recursive().count(), 3);
1249
1250        let hello_paragraphs: Vec<_> = container
1251            .iter_paragraphs_recursive()
1252            .filter(|p| p.text().contains("Hello"))
1253            .collect();
1254        assert_eq!(hello_paragraphs.len(), 1);
1255
1256        let nested_sessions: Vec<_> = container
1257            .iter_all_nodes_with_depth()
1258            .filter(|(node, depth)| node.is_session() && *depth >= 1)
1259            .collect();
1260        assert_eq!(nested_sessions.len(), 1);
1261    }
1262
1263    // ========================================================================
1264    // FIRST/EXPECT METHODS
1265    // ========================================================================
1266
1267    #[test]
1268    fn test_first_methods() {
1269        let mut container = SessionContainer::empty();
1270        container.push(ContentItem::Paragraph(Paragraph::from_line(
1271            "First para".to_string(),
1272        )));
1273
1274        assert!(container.first_paragraph().is_some());
1275        assert!(container.first_session().is_none());
1276    }
1277
1278    #[test]
1279    fn test_expect_paragraph() {
1280        let mut container = SessionContainer::empty();
1281        container.push(ContentItem::Paragraph(Paragraph::from_line(
1282            "Test".to_string(),
1283        )));
1284
1285        let para = container.expect_paragraph();
1286        assert_eq!(para.text(), "Test");
1287    }
1288
1289    #[test]
1290    #[should_panic(expected = "No paragraph found in container")]
1291    fn test_expect_paragraph_panics() {
1292        let container = SessionContainer::empty();
1293        container.expect_paragraph();
1294    }
1295
1296    // ========================================================================
1297    // COUNT BY TYPE
1298    // ========================================================================
1299
1300    #[test]
1301    fn test_count_by_type() {
1302        let mut container = SessionContainer::empty();
1303        container.push(ContentItem::Paragraph(Paragraph::from_line(
1304            "Para 1".to_string(),
1305        )));
1306        container.push(ContentItem::Paragraph(Paragraph::from_line(
1307            "Para 2".to_string(),
1308        )));
1309        container.push(ContentItem::Session(Session::with_title(
1310            "Session".to_string(),
1311        )));
1312
1313        let (paragraphs, sessions, lists, verbatim) = container.count_by_type();
1314        assert_eq!(paragraphs, 2);
1315        assert_eq!(sessions, 1);
1316        assert_eq!(lists, 0);
1317        assert_eq!(verbatim, 0);
1318    }
1319
1320    // ========================================================================
1321    // ELEMENT_AT ERROR PATHS
1322    // ========================================================================
1323
1324    #[test]
1325    fn test_element_at_position_outside_document() {
1326        use crate::lex::ast::range::Position;
1327
1328        let mut container = SessionContainer::empty();
1329        container.push(ContentItem::Paragraph(Paragraph::from_line(
1330            "Test".to_string(),
1331        )));
1332
1333        let far_position = Position::new(1000, 1000);
1334        assert!(container.element_at(far_position).is_none());
1335    }
1336
1337    #[test]
1338    fn test_element_at_empty_container() {
1339        use crate::lex::ast::range::Position;
1340
1341        let container = SessionContainer::empty();
1342        let position = Position::new(1, 1);
1343        assert!(container.element_at(position).is_none());
1344    }
1345
1346    #[test]
1347    fn test_find_nodes_at_position_no_results() {
1348        use crate::lex::ast::range::Position;
1349
1350        let container = SessionContainer::empty();
1351        let position = Position::new(1, 1);
1352        let nodes = container.find_nodes_at_position(position);
1353        assert!(nodes.is_empty());
1354    }
1355
1356    #[test]
1357    fn test_format_at_position_no_nodes() {
1358        use crate::lex::ast::range::Position;
1359
1360        let container = SessionContainer::empty();
1361        let position = Position::new(1, 1);
1362        let output = container.format_at_position(position);
1363        assert_eq!(output, "No AST nodes at this position");
1364    }
1365}