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