Skip to main content

yaml_edit/
yaml.rs

1//! Lossless YAML parser and editor.
2
3use crate::{
4    error_recovery::{ErrorBuilder, ErrorRecoveryContext, ParseContext, RecoveryStrategy},
5    lex::{lex, SyntaxKind},
6    parse::Parse,
7    ParseErrorKind, PositionedParseError,
8};
9use rowan::ast::AstNode;
10use rowan::GreenNodeBuilder;
11use std::path::Path;
12use std::str::FromStr;
13
14/// The raw result of parsing a YAML file, before it is wrapped in a [`YamlFile`] node.
15///
16/// Contains the green CST root and any errors encountered during parsing.
17/// Most callers should use [`YamlFile::parse`] instead, which returns a [`Parse<YamlFile>`]
18/// wrapper with the same information in a more ergonomic form.
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub(crate) struct ParsedYaml {
21    /// The immutable green-tree root produced by the parser.
22    pub(crate) green_node: rowan::GreenNode,
23    /// Human-readable error messages for syntax errors encountered during parsing.
24    pub(crate) errors: Vec<String>,
25    /// Structured parse errors with source positions.
26    pub(crate) positioned_errors: Vec<PositionedParseError>,
27}
28
29// Import Lang, SyntaxNode, and ast_node! macro from nodes module
30use crate::nodes::ast_node;
31pub use crate::nodes::{Lang, SyntaxNode};
32
33// Re-export extracted AST nodes from nodes module
34pub use crate::nodes::{
35    Alias, Directive, Document, Mapping, MappingEntry, Scalar, ScalarConversionError, Sequence,
36    TaggedNode,
37};
38
39ast_node!(
40    YamlFile,
41    ROOT,
42    "A YAML file containing one or more documents"
43);
44
45impl YamlFile {
46    /// Capture an independent snapshot of this YAML file.
47    ///
48    /// The returned value shares the underlying immutable green-node data
49    /// with `self` at the time of the call, but lives in its own mutable
50    /// tree: subsequent mutations to `self` do not propagate to the snapshot.
51    /// Pair with [`Self::tree_eq`] to detect later mutations.
52    pub fn snapshot(&self) -> Self {
53        YamlFile(SyntaxNode::new_root_mut(self.0.green().into_owned()))
54    }
55
56    /// Returns true iff the syntax trees of `self` and `other` are
57    /// value-equal. An O(1) pointer-identity fast path makes this free for
58    /// trees that still share state with a recent `snapshot()`.
59    pub fn tree_eq(&self, other: &Self) -> bool {
60        let a = self.0.green();
61        let b = other.0.green();
62        let a_ref: &rowan::GreenNodeData = &a;
63        let b_ref: &rowan::GreenNodeData = &b;
64        std::ptr::eq(a_ref as *const _, b_ref as *const _) || a_ref == b_ref
65    }
66}
67
68/// Trait for value nodes (Mapping, Sequence, Scalar) with inline detection
69pub trait ValueNode: rowan::ast::AstNode<Language = Lang> {
70    /// Returns whether this value should be rendered inline
71    fn is_inline(&self) -> bool;
72}
73
74impl ValueNode for Mapping {
75    fn is_inline(&self) -> bool {
76        // Check if this is a flow-style mapping (empty or has braces)
77        if self.0.children_with_tokens().any(|c| {
78            c.as_token()
79                .map(|t| t.kind() == SyntaxKind::LEFT_BRACE || t.kind() == SyntaxKind::RIGHT_BRACE)
80                .unwrap_or(false)
81        }) {
82            return true;
83        }
84        false
85    }
86}
87
88impl ValueNode for Sequence {
89    fn is_inline(&self) -> bool {
90        // Check if this is a flow-style sequence (has brackets)
91        if self.0.children_with_tokens().any(|c| {
92            c.as_token()
93                .map(|t| {
94                    t.kind() == SyntaxKind::LEFT_BRACKET || t.kind() == SyntaxKind::RIGHT_BRACKET
95                })
96                .unwrap_or(false)
97        }) {
98            return true;
99        }
100        false
101    }
102}
103
104impl ValueNode for Scalar {
105    fn is_inline(&self) -> bool {
106        // Scalars are always inline
107        true
108    }
109}
110
111// Helper functions for newline management
112
113/// Check if a syntax node ends with a newline token
114pub(crate) fn ends_with_newline(node: &SyntaxNode) -> bool {
115    node.last_token()
116        .map(|t| t.kind() == SyntaxKind::NEWLINE)
117        .unwrap_or(false)
118}
119
120/// Create a newline token and add it to the elements vector
121pub(crate) fn add_newline_token(
122    elements: &mut Vec<rowan::NodeOrToken<rowan::SyntaxNode<Lang>, rowan::SyntaxToken<Lang>>>,
123) {
124    let mut nl_builder = rowan::GreenNodeBuilder::new();
125    nl_builder.start_node(SyntaxKind::ROOT.into());
126    nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
127    nl_builder.finish_node();
128    let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
129    if let Some(token) = nl_node.first_token() {
130        elements.push(token.into());
131    }
132}
133
134/// A virtual AST node for YAML sets (!!set tagged scalars)
135#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub struct Set(SyntaxNode);
137
138impl Set {
139    /// Cast a SyntaxNode to a Set only if it's a TAGGED_NODE with !!set tag
140    pub fn cast(node: SyntaxNode) -> Option<Self> {
141        if node.kind() == SyntaxKind::TAGGED_NODE {
142            if let Some(tagged_node) = TaggedNode::cast(node.clone()) {
143                if tagged_node.tag().as_deref() == Some("!!set") {
144                    return Some(Set(node));
145                }
146            }
147        }
148        None
149    }
150
151    /// Return the inner `Mapping` of this set (the !!set body).
152    fn inner_mapping(&self) -> Option<Mapping> {
153        self.0.children().find_map(Mapping::cast)
154    }
155
156    /// Iterate over the set members, preserving formatting.
157    ///
158    /// Each member is yielded as a [`YamlNode`](crate::YamlNode) wrapping the
159    /// key content node (the scalar, mapping, or sequence that forms the set
160    /// member). The iteration order follows the document order.
161    ///
162    /// To compare members semantically (ignoring quoting style), use
163    /// [`yaml_eq`](crate::yaml_eq) on the returned nodes.
164    ///
165    /// Note: `!!set` entries appear in two CST layouts: explicit-key `? item`
166    /// (KEY/VALUE as direct MAPPING children) and implicit `item: null`
167    /// (KEY/VALUE inside MAPPING_ENTRY). Both are handled.
168    pub fn members(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
169        // Sets are `!!set` tagged scalars whose body is a mapping where each
170        // key is a set member and values are null.
171        //
172        // Two CST layouts arise in practice:
173        //
174        // 1. Explicit-key notation (`? apple`): KEY and VALUE appear as direct
175        //    children of the MAPPING node (no MAPPING_ENTRY wrapper).
176        //
177        // 2. Implicit-key notation (`apple: null`): KEY and VALUE are wrapped
178        //    inside MAPPING_ENTRY children of the MAPPING node.
179        //
180        // We detect which layout is in use by checking for MAPPING_ENTRY
181        // children first, then falling back to bare KEY children.
182        self.inner_mapping().into_iter().flat_map(|m| {
183            let mapping_node = m.syntax().clone();
184            let has_entries = mapping_node
185                .children()
186                .any(|n| n.kind() == SyntaxKind::MAPPING_ENTRY);
187
188            if has_entries {
189                // Layout 2: MAPPING_ENTRY → KEY → content
190                mapping_node
191                    .children()
192                    .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
193                    .filter_map(|entry| {
194                        entry
195                            .children()
196                            .find(|n| n.kind() == SyntaxKind::KEY)
197                            .and_then(|key| key.children().next())
198                            .and_then(crate::as_yaml::YamlNode::from_syntax)
199                    })
200                    .collect::<Vec<_>>()
201            } else {
202                // Layout 1: bare KEY → content (explicit `?` syntax)
203                mapping_node
204                    .children()
205                    .filter(|n| n.kind() == SyntaxKind::KEY)
206                    .filter_map(|key| {
207                        key.children()
208                            .next()
209                            .and_then(crate::as_yaml::YamlNode::from_syntax)
210                    })
211                    .collect::<Vec<_>>()
212            }
213        })
214    }
215
216    /// Get the number of members in the set.
217    pub fn len(&self) -> usize {
218        self.members().count()
219    }
220
221    /// Check if the set is empty.
222    pub fn is_empty(&self) -> bool {
223        self.members().next().is_none()
224    }
225
226    /// Check if the set contains a specific value.
227    pub fn contains(&self, value: impl crate::AsYaml) -> bool {
228        self.members()
229            .any(|member| crate::as_yaml::yaml_eq(&member, &value))
230    }
231}
232
233// CST abstraction layer - hides wrapper node complexity
234
235/// Extract the actual content from a VALUE or KEY wrapper node
236fn extract_content_node(wrapper: &SyntaxNode) -> Option<SyntaxNode> {
237    use crate::lex::SyntaxKind;
238    match wrapper.kind() {
239        SyntaxKind::VALUE | SyntaxKind::KEY => wrapper.children().next(),
240        _ => Some(wrapper.clone()),
241    }
242}
243
244/// Smart cast that handles wrapper nodes automatically
245fn smart_cast<T: AstNode<Language = Lang>>(node: SyntaxNode) -> Option<T> {
246    if let Some(content) = extract_content_node(&node) {
247        T::cast(content)
248    } else {
249        None
250    }
251}
252
253/// Extract a Scalar from any node (handles wrappers automatically)
254pub(crate) fn extract_scalar(node: &SyntaxNode) -> Option<Scalar> {
255    smart_cast(node.clone())
256}
257
258/// Extract a Mapping from any node (handles wrappers automatically)
259pub(crate) fn extract_mapping(node: &SyntaxNode) -> Option<Mapping> {
260    smart_cast(node.clone())
261}
262
263/// Extract a Sequence from any node (handles wrappers automatically)
264pub(crate) fn extract_sequence(node: &SyntaxNode) -> Option<Sequence> {
265    smart_cast(node.clone())
266}
267
268/// Extract a TaggedNode from any node (handles wrappers automatically)
269pub(crate) fn extract_tagged_node(node: &SyntaxNode) -> Option<TaggedNode> {
270    smart_cast(node.clone())
271}
272
273/// Copy a syntax node and all its children recursively to a builder.
274pub(crate) fn copy_node_to_builder(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
275    builder.start_node(node.kind().into());
276    add_node_children_to(builder, node);
277    builder.finish_node();
278}
279
280// Helper function to recursively add node children to a builder
281pub(crate) fn add_node_children_to(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
282    for child in node.children_with_tokens() {
283        match child {
284            rowan::NodeOrToken::Node(child_node) => {
285                builder.start_node(child_node.kind().into());
286                add_node_children_to(builder, &child_node);
287                builder.finish_node();
288            }
289            rowan::NodeOrToken::Token(token) => {
290                builder.token(token.kind().into(), token.text());
291            }
292        }
293    }
294}
295
296// Debug helper to dump CST structure
297pub(crate) fn dump_cst_to_string(node: &SyntaxNode, indent: usize) -> String {
298    let mut result = String::new();
299    let indent_str = "  ".repeat(indent);
300
301    for child in node.children_with_tokens() {
302        match child {
303            rowan::NodeOrToken::Node(n) => {
304                result.push_str(&format!("{}{:?}\n", indent_str, n.kind()));
305                result.push_str(&dump_cst_to_string(&n, indent + 1));
306            }
307            rowan::NodeOrToken::Token(t) => {
308                result.push_str(&format!("{}  {:?} {:?}\n", indent_str, t.kind(), t.text()));
309            }
310        }
311    }
312    result
313}
314
315impl Default for YamlFile {
316    fn default() -> Self {
317        Self::new()
318    }
319}
320
321impl YamlFile {
322    /// Create a new empty YAML document.
323    pub fn new() -> YamlFile {
324        let mut builder = GreenNodeBuilder::new();
325        builder.start_node(SyntaxKind::ROOT.into());
326        builder.finish_node();
327        YamlFile(SyntaxNode::new_root_mut(builder.finish()))
328    }
329
330    /// Parse YAML text, returning a Parse result
331    pub fn parse(text: &str) -> Parse<YamlFile> {
332        Parse::parse_yaml(text)
333    }
334
335    /// Parse YAML from a file path
336    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<YamlFile, crate::YamlError> {
337        let contents = std::fs::read_to_string(path)?;
338        Self::from_str(&contents)
339    }
340
341    /// Parse YAML text, allowing syntax errors.
342    ///
343    /// Returns the parsed tree even if there are parse errors, along with
344    /// a list of error messages. This allows for error-resilient tooling
345    /// that can work with partial or invalid input.
346    ///
347    /// # Example
348    /// ```
349    /// use yaml_edit::YamlFile;
350    ///
351    /// let (yaml_file, errors) = YamlFile::from_str_relaxed("key: [unclosed");
352    /// // Tree is usable even with errors
353    /// assert!(!errors.is_empty());
354    /// assert!(yaml_file.document().is_some());
355    /// ```
356    pub fn from_str_relaxed(s: &str) -> (YamlFile, Vec<String>) {
357        let parsed = YamlFile::parse(s);
358        let errors = parsed.errors();
359        (parsed.tree(), errors)
360    }
361
362    /// Parse YAML from a file path, allowing syntax errors.
363    ///
364    /// Returns the parsed tree even if there are parse errors, along with
365    /// a list of error messages. I/O errors are still returned as `Err`.
366    pub fn from_path_relaxed<P: AsRef<Path>>(
367        path: P,
368    ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
369        let contents = std::fs::read_to_string(path)?;
370        Ok(Self::from_str_relaxed(&contents))
371    }
372
373    /// Read YAML from a `Read` object, allowing syntax errors.
374    ///
375    /// Returns the parsed tree even if there are parse errors, along with
376    /// a list of error messages. I/O errors are still returned as `Err`.
377    pub fn read_relaxed<R: std::io::Read>(
378        mut r: R,
379    ) -> Result<(YamlFile, Vec<String>), std::io::Error> {
380        let mut buf = String::new();
381        r.read_to_string(&mut buf)?;
382        Ok(Self::from_str_relaxed(&buf))
383    }
384
385    /// Get all documents in this YAML file
386    pub fn documents(&self) -> impl Iterator<Item = Document> {
387        self.0.children().filter_map(Document::cast)
388    }
389
390    /// Get the first document in this YAML file, or `None` if there are none.
391    ///
392    /// Most YAML files have exactly one document. Use [`documents`](Self::documents)
393    /// to iterate over all documents in a multi-document file.
394    pub fn document(&self) -> Option<Document> {
395        self.documents().next()
396    }
397
398    /// Ensure this `YamlFile` contains at least one document, creating an empty mapping document if needed.
399    ///
400    /// Returns the first document.
401    pub fn ensure_document(&self) -> Document {
402        if self.documents().next().is_none() {
403            // No document exists, add an empty one
404            let doc = Document::new_mapping();
405            self.push_document(doc);
406        }
407        self.documents()
408            .next()
409            .expect("Document should exist after ensuring")
410    }
411
412    /// Iterate over all YAML directives (e.g. `%YAML 1.2`) in this file.
413    pub fn directives(&self) -> impl Iterator<Item = Directive> {
414        self.0.children().filter_map(Directive::cast)
415    }
416
417    /// Prepend a YAML directive to this file.
418    ///
419    /// `directive_text` should be the full directive line without a trailing
420    /// newline, e.g. `"%YAML 1.2"` or `"%TAG ! tag:example.com,2000:app/"`.
421    /// The directive is inserted before all existing content.
422    ///
423    /// Note: the parser does not currently enforce that directives appear
424    /// before any document node; callers are responsible for ordering.
425    pub fn add_directive(&self, directive_text: &str) {
426        // Create directive node
427        let mut builder = GreenNodeBuilder::new();
428        builder.start_node(SyntaxKind::DIRECTIVE.into());
429        builder.token(SyntaxKind::DIRECTIVE.into(), directive_text);
430        builder.finish_node();
431        let directive_node = SyntaxNode::new_root_mut(builder.finish());
432
433        // Insert at the beginning using splice_children with interior mutability
434        self.0.splice_children(0..0, vec![directive_node.into()]);
435    }
436
437    /// Add a new document to the end of this YAML file
438    pub fn push_document(&self, document: Document) {
439        let children_count = self.0.children_with_tokens().count();
440
441        // Just insert the document node using splice_children with interior mutability
442        self.0
443            .splice_children(children_count..children_count, vec![document.0.into()]);
444    }
445
446    /// Set a key-value pair in the first document's mapping.
447    ///
448    /// If the key exists its value is replaced; if not, a new entry is appended.
449    /// Does nothing if the `YamlFile` contains no documents or the first document is
450    /// not a mapping. Use [`ensure_document`](Self::ensure_document) first if you
451    /// need to guarantee a document exists.
452    ///
453    /// Mutates in place despite `&self` (see crate docs on interior mutability).
454    pub fn set(&self, key: impl crate::AsYaml, value: impl crate::AsYaml) {
455        if let Some(doc) = self.document() {
456            doc.set(key, value);
457        }
458    }
459
460    /// Insert a key-value pair immediately after `after_key` in the first document.
461    ///
462    /// Delegates to [`Document::insert_after`], which in turn calls
463    /// [`Mapping::insert_after`]. If `key` already exists it is updated
464    /// in-place rather than moved. Returns `false` if `after_key` is not found
465    /// or the document is not a mapping.
466    ///
467    /// Mutates in place despite `&self` (see crate docs on interior mutability).
468    pub fn insert_after(
469        &self,
470        after_key: impl crate::AsYaml,
471        key: impl crate::AsYaml,
472        value: impl crate::AsYaml,
473    ) -> bool {
474        if let Some(doc) = self.document() {
475            doc.insert_after(after_key, key, value)
476        } else {
477            false
478        }
479    }
480
481    /// Insert a key-value pair immediately before `before_key` in the first document.
482    ///
483    /// Delegates to [`Document::insert_before`], which in turn calls
484    /// [`Mapping::insert_before`]. If `key` already exists it is updated
485    /// in-place rather than moved. Returns `false` if `before_key` is not found
486    /// or the document is not a mapping.
487    ///
488    /// Mutates in place despite `&self` (see crate docs on interior mutability).
489    pub fn insert_before(
490        &self,
491        before_key: impl crate::AsYaml,
492        key: impl crate::AsYaml,
493        value: impl crate::AsYaml,
494    ) -> bool {
495        if let Some(doc) = self.document() {
496            doc.insert_before(before_key, key, value)
497        } else {
498            false
499        }
500    }
501
502    /// Move a key-value pair to immediately after `after_key` in the first document.
503    ///
504    /// Delegates to [`Document::move_after`]. If `key` already exists it is
505    /// **removed** from its current position and re-inserted after `after_key`.
506    /// Returns `false` if `after_key` is not found or the document is not a mapping.
507    ///
508    /// Use [`insert_after`](Self::insert_after) if you want an existing entry to be
509    /// updated in-place rather than moved.
510    ///
511    /// Mutates in place despite `&self` (see crate docs on interior mutability).
512    pub fn move_after(
513        &self,
514        after_key: impl crate::AsYaml,
515        key: impl crate::AsYaml,
516        value: impl crate::AsYaml,
517    ) -> bool {
518        if let Some(doc) = self.document() {
519            doc.move_after(after_key, key, value)
520        } else {
521            false
522        }
523    }
524
525    /// Move a key-value pair to immediately before `before_key` in the first document.
526    ///
527    /// Delegates to [`Document::move_before`]. If `key` already exists it is
528    /// **removed** from its current position and re-inserted before `before_key`.
529    /// Returns `false` if `before_key` is not found or the document is not a mapping.
530    ///
531    /// Use [`insert_before`](Self::insert_before) if you want an existing entry to be
532    /// updated in-place rather than moved.
533    ///
534    /// Mutates in place despite `&self` (see crate docs on interior mutability).
535    pub fn move_before(
536        &self,
537        before_key: impl crate::AsYaml,
538        key: impl crate::AsYaml,
539        value: impl crate::AsYaml,
540    ) -> bool {
541        if let Some(doc) = self.document() {
542            doc.move_before(before_key, key, value)
543        } else {
544            false
545        }
546    }
547
548    /// Insert a key-value pair at a specific index (0-based) in the first document.
549    ///
550    /// Delegates to [`Document::insert_at_index`]. If `key` already exists it
551    /// is updated in-place rather than moved. If `index` is out of bounds the
552    /// entry is appended at the end. If the document has no mapping yet, one is
553    /// created automatically. This method always succeeds; it never returns an error.
554    ///
555    /// Mutates in place despite `&self` (see crate docs on interior mutability).
556    pub fn insert_at_index(
557        &self,
558        index: usize,
559        key: impl crate::AsYaml,
560        value: impl crate::AsYaml,
561    ) {
562        if let Some(doc) = self.document() {
563            doc.insert_at_index(index, key, value);
564            // Mutations happen directly on the document, no need to replace
565        } else {
566            // If no document exists, create one without the --- marker for consistency
567            // with normal parsed YAML
568            let mut builder = GreenNodeBuilder::new();
569            builder.start_node(SyntaxKind::DOCUMENT.into());
570            builder.start_node(SyntaxKind::MAPPING.into());
571            builder.finish_node(); // End MAPPING
572            builder.finish_node(); // End DOCUMENT
573            let doc = Document(SyntaxNode::new_root_mut(builder.finish()));
574
575            // Add the document to the ROOT
576            self.0.splice_children(0..0, vec![doc.0.into()]);
577
578            // Now get the document again and insert
579            if let Some(doc) = self.document() {
580                doc.insert_at_index(index, key, value);
581            }
582        }
583    }
584}
585
586impl FromStr for YamlFile {
587    type Err = crate::YamlError;
588
589    fn from_str(s: &str) -> Result<Self, Self::Err> {
590        let parsed = YamlFile::parse(s);
591        if !parsed.positioned_errors().is_empty() {
592            let first = &parsed.positioned_errors()[0];
593            let lc = crate::byte_offset_to_line_column(s, first.range.start as usize);
594            return Err(crate::YamlError::Parse {
595                message: first.message.clone(),
596                line: Some(lc.line),
597                column: Some(lc.column),
598            });
599        }
600        Ok(parsed.tree())
601    }
602}
603
604/// Maximum nesting depth for flow collections. Beyond this, the parser
605/// returns an error rather than risking stack overflow or unbounded RSS
606/// growth from pathological input like `{{{{...}}}}`.
607const MAX_FLOW_DEPTH: usize = 256;
608
609/// Internal parser state
610struct Parser {
611    tokens: Vec<(SyntaxKind, String)>,
612    current_token_index: usize,
613    builder: GreenNodeBuilder<'static>,
614    errors: Vec<String>,
615    positioned_errors: Vec<PositionedParseError>,
616    in_flow_context: bool,
617    /// Error recovery context for better error messages
618    error_context: ErrorRecoveryContext,
619    /// Track if we're parsing a value (to prevent nested implicit mappings)
620    in_value_context: bool,
621    /// Track the current line's indentation level for plain scalar continuation
622    current_line_indent: usize,
623    /// Current depth of nested flow collections ([...] / {...}).
624    flow_depth: usize,
625}
626
627impl Parser {
628    fn new(text: &str) -> Self {
629        let lexed = lex(text);
630        let mut tokens = Vec::new();
631
632        for (kind, token_text) in lexed {
633            tokens.push((kind, token_text.to_string()));
634        }
635
636        // Reverse tokens so we can use pop() to get the next token
637        let token_count = tokens.len();
638        tokens.reverse();
639
640        Self {
641            tokens,
642            current_token_index: token_count,
643            builder: GreenNodeBuilder::new(),
644            errors: Vec::new(),
645            positioned_errors: Vec::new(),
646            in_flow_context: false,
647            error_context: ErrorRecoveryContext::new(text.to_string()),
648            in_value_context: false,
649            current_line_indent: 0,
650            flow_depth: 0,
651        }
652    }
653
654    fn parse(mut self) -> ParsedYaml {
655        self.builder.start_node(SyntaxKind::ROOT.into());
656
657        // Handle BOM (Byte Order Mark) at the start of file
658        // BOM is allowed per YAML spec and should be processed transparently
659        if self.current() == Some(SyntaxKind::BOM) {
660            self.bump(); // Add BOM to tree but continue parsing
661        }
662
663        self.skip_ws_and_newlines();
664
665        // Parse any directives at the beginning
666        while self.current() == Some(SyntaxKind::DIRECTIVE) {
667            self.parse_directive();
668            self.skip_ws_and_newlines();
669        }
670
671        // Parse documents
672        // Always parse at least one document
673        if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
674            self.parse_document();
675            self.skip_ws_and_newlines();
676
677            // Parse additional documents (can have directives before each)
678            while self.current() == Some(SyntaxKind::DOC_START)
679                || self.current() == Some(SyntaxKind::DIRECTIVE)
680            {
681                // Parse any directives before this document
682                while self.current() == Some(SyntaxKind::DIRECTIVE) {
683                    self.parse_directive();
684                    self.skip_ws_and_newlines();
685                }
686
687                // Parse the document if we have content
688                if self.current() == Some(SyntaxKind::DOC_START)
689                    || (self.current().is_some() && self.current() != Some(SyntaxKind::EOF))
690                {
691                    self.parse_document();
692                    self.skip_ws_and_newlines();
693                } else {
694                    break;
695                }
696            }
697        }
698
699        // Consume any remaining tokens as ERROR nodes
700        // A lenient parser should consume all input, not leave it unparsed
701        while self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
702            self.builder.start_node(SyntaxKind::ERROR.into());
703
704            // Consume tokens until we hit EOF or a document/directive marker
705            while self.current().is_some()
706                && self.current() != Some(SyntaxKind::EOF)
707                && self.current() != Some(SyntaxKind::DOC_START)
708                && self.current() != Some(SyntaxKind::DIRECTIVE)
709            {
710                self.bump();
711            }
712
713            self.builder.finish_node();
714
715            // If we hit a document/directive marker, try to parse it
716            if self.current() == Some(SyntaxKind::DOC_START)
717                || self.current() == Some(SyntaxKind::DIRECTIVE)
718            {
719                // Parse any directives
720                while self.current() == Some(SyntaxKind::DIRECTIVE) {
721                    self.parse_directive();
722                    self.skip_ws_and_newlines();
723                }
724
725                // Parse document if present
726                if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
727                    self.parse_document();
728                    self.skip_ws_and_newlines();
729                }
730            }
731        }
732
733        self.builder.finish_node();
734
735        ParsedYaml {
736            green_node: self.builder.finish(),
737            errors: self.errors,
738            positioned_errors: self.positioned_errors,
739        }
740    }
741
742    fn parse_document(&mut self) {
743        self.builder.start_node(SyntaxKind::DOCUMENT.into());
744
745        // Handle document start marker
746        if self.current() == Some(SyntaxKind::DOC_START) {
747            self.bump();
748            self.skip_ws_and_newlines();
749        }
750
751        // Parse the document content
752        if self.current().is_some()
753            && self.current() != Some(SyntaxKind::DOC_END)
754            && self.current() != Some(SyntaxKind::DOC_START)
755        {
756            self.parse_value();
757        }
758
759        // Handle document end marker
760        if self.current() == Some(SyntaxKind::DOC_END) {
761            self.bump();
762
763            // Check for content after document end marker (spec violation)
764            self.skip_whitespace();
765            if self.current().is_some()
766                && self.current() != Some(SyntaxKind::NEWLINE)
767                && self.current() != Some(SyntaxKind::EOF)
768                && self.current() != Some(SyntaxKind::DOC_START)
769                && self.current() != Some(SyntaxKind::DIRECTIVE)
770            {
771                // Found content after DOC_END - wrap it in an ERROR node
772                self.builder.start_node(SyntaxKind::ERROR.into());
773                while self.current().is_some()
774                    && self.current() != Some(SyntaxKind::NEWLINE)
775                    && self.current() != Some(SyntaxKind::EOF)
776                    && self.current() != Some(SyntaxKind::DOC_START)
777                    && self.current() != Some(SyntaxKind::DIRECTIVE)
778                {
779                    self.bump();
780                }
781                self.builder.finish_node();
782            }
783        }
784
785        self.builder.finish_node();
786    }
787
788    fn parse_value(&mut self) {
789        self.parse_value_with_base_indent(0);
790    }
791
792    fn parse_value_with_base_indent(&mut self, base_indent: usize) {
793        match self.current() {
794            Some(SyntaxKind::COMMENT) => {
795                // Preserve the comment and continue parsing the actual value
796                self.bump(); // consume and preserve the comment
797                self.skip_ws_and_newlines(); // skip any whitespace/newlines after comment
798                                             // Now parse the actual value
799                self.parse_value_with_base_indent(base_indent);
800            }
801            Some(SyntaxKind::DASH) if !self.in_flow_context => {
802                self.parse_sequence_with_base_indent(base_indent)
803            }
804            Some(SyntaxKind::ANCHOR) => {
805                self.bump(); // consume and emit anchor token to CST
806                self.skip_whitespace();
807                self.parse_value_with_base_indent(base_indent);
808            }
809            Some(SyntaxKind::REFERENCE) => self.parse_alias(),
810            Some(SyntaxKind::TAG) => self.parse_tagged_value(),
811            Some(SyntaxKind::MERGE_KEY) => {
812                // Merge key is always a mapping
813                self.parse_mapping_with_base_indent(base_indent);
814            }
815            Some(SyntaxKind::QUESTION) => {
816                // Explicit key indicator - parse complex mapping
817                self.parse_explicit_key_mapping();
818            }
819            Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
820            Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
821            Some(
822                SyntaxKind::STRING
823                | SyntaxKind::INT
824                | SyntaxKind::FLOAT
825                | SyntaxKind::BOOL
826                | SyntaxKind::NULL,
827            ) => {
828                // In flow context, always parse as scalar
829                // In block context, check if it's a mapping key
830                // But not if we're already in a value context (prevents implicit nested mappings)
831                if !self.in_flow_context && !self.in_value_context && self.is_mapping_key() {
832                    self.parse_mapping_with_base_indent(base_indent);
833                } else {
834                    self.parse_scalar();
835                }
836            }
837            Some(SyntaxKind::LEFT_BRACKET) => {
838                // Check if this is a complex key in a mapping
839                // But not if we're already in a value context
840                if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
841                {
842                    self.parse_complex_key_mapping();
843                } else {
844                    self.parse_flow_sequence();
845                }
846            }
847            Some(SyntaxKind::LEFT_BRACE) => {
848                // Check if this is a complex key in a mapping
849                // But not if we're already in a value context
850                if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
851                {
852                    self.parse_complex_key_mapping();
853                } else {
854                    self.parse_flow_mapping();
855                }
856            }
857            Some(SyntaxKind::INDENT) => {
858                // We have an indented block - consume the indent and see what follows
859                self.bump(); // consume INDENT
860                self.parse_value(); // parse whatever comes after the indent
861            }
862            Some(SyntaxKind::NEWLINE) => {
863                // Check if next line has indented content
864                self.bump(); // consume newline
865                if self.current() == Some(SyntaxKind::INDENT) {
866                    let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
867                    self.bump(); // consume indent
868                    self.parse_value_with_base_indent(indent_level);
869                } else {
870                    // No indented content means empty/null value - create empty scalar
871                    self.builder.start_node(SyntaxKind::SCALAR.into());
872                    self.builder.finish_node();
873                }
874            }
875            _ => self.parse_scalar(),
876        }
877    }
878
879    fn parse_alias(&mut self) {
880        // Create an alias node and consume the reference token
881        // The token itself already contains the full "*alias_name" text
882        self.builder.start_node(SyntaxKind::ALIAS.into());
883        if self.current() == Some(SyntaxKind::REFERENCE) {
884            self.bump(); // This preserves the original "*alias_name" token
885        }
886        self.builder.finish_node();
887    }
888
889    fn parse_scalar(&mut self) {
890        self.builder.start_node(SyntaxKind::SCALAR.into());
891
892        // Handle quotes
893        if matches!(
894            self.current(),
895            Some(SyntaxKind::QUOTE | SyntaxKind::SINGLE_QUOTE)
896        ) {
897            let quote_type = self
898                .current()
899                .expect("current token is Some: checked by matches! guard above");
900            self.bump(); // opening quote
901
902            // Consume all tokens until the closing quote
903            while self.current().is_some() && self.current() != Some(quote_type) {
904                self.bump();
905            }
906
907            if self.current() == Some(quote_type) {
908                self.bump(); // closing quote
909            } else {
910                let expected_quote = if quote_type == SyntaxKind::QUOTE {
911                    "\""
912                } else {
913                    "'"
914                };
915                let error_msg = self.create_detailed_error(
916                    "Unterminated quoted string",
917                    &format!("closing quote {}", expected_quote),
918                    self.current_text(),
919                );
920                self.add_error_and_recover(
921                    error_msg,
922                    quote_type,
923                    ParseErrorKind::UnterminatedString,
924                );
925            }
926        } else {
927            // Handle typed scalar tokens from lexer
928            if matches!(
929                self.current(),
930                Some(
931                    SyntaxKind::STRING
932                        | SyntaxKind::UNTERMINATED_STRING
933                        | SyntaxKind::INT
934                        | SyntaxKind::FLOAT
935                        | SyntaxKind::BOOL
936                        | SyntaxKind::NULL
937                )
938            ) {
939                // Check for unterminated string and add error
940                if self.current() == Some(SyntaxKind::UNTERMINATED_STRING) {
941                    self.add_error(
942                        "Unterminated quoted string".to_string(),
943                        ParseErrorKind::UnterminatedString,
944                    );
945                }
946                if !self.in_flow_context {
947                    // For plain scalars in block context, handle multi-line plain scalars
948                    // per YAML spec: continuation lines must be more indented than the scalar's starting line
949                    //
950                    // Use current_line_indent which tracks the actual line indentation.
951                    // CRITICAL: For inline scalars in sequence items (where indent==0 because the
952                    // INDENT token was already consumed), we MUST NOT try continuation because we
953                    // can't distinguish between continuation and the next mapping key.
954                    let scalar_indent = self.current_line_indent;
955
956                    while let Some(kind) = self.current() {
957                        if kind == SyntaxKind::COMMENT {
958                            // Stop at comments
959                            break;
960                        }
961
962                        if kind == SyntaxKind::NEWLINE {
963                            // Check if next line continues the scalar (more indented)
964                            if self.is_plain_scalar_continuation(scalar_indent) {
965                                // Fold the newline - consume it and following whitespace
966                                self.bump(); // consume NEWLINE
967
968                                // Skip INDENT and WHITESPACE on next line
969                                while matches!(
970                                    self.current(),
971                                    Some(SyntaxKind::INDENT | SyntaxKind::WHITESPACE)
972                                ) {
973                                    self.bump();
974                                }
975
976                                // Continue consuming scalar content on next line
977                                continue;
978                            } else {
979                                // Next line is not a continuation - stop here
980                                break;
981                            }
982                        }
983
984                        // In block context, stop at flow collection delimiters
985                        if matches!(
986                            kind,
987                            SyntaxKind::LEFT_BRACKET
988                                | SyntaxKind::LEFT_BRACE
989                                | SyntaxKind::RIGHT_BRACKET
990                                | SyntaxKind::RIGHT_BRACE
991                                | SyntaxKind::COMMA
992                        ) {
993                            break;
994                        }
995
996                        // Check ahead to see if next token is a comment
997                        if kind == SyntaxKind::WHITESPACE {
998                            // Look ahead to see if a comment follows
999                            if self.tokens.len() >= 2 {
1000                                let next_kind = self.tokens[self.tokens.len() - 2].0;
1001                                if next_kind == SyntaxKind::COMMENT {
1002                                    // Don't consume this whitespace, it precedes a comment
1003                                    break;
1004                                }
1005                            }
1006                        }
1007
1008                        self.bump();
1009                    }
1010                } else {
1011                    // In flow context, consume tokens until we hit a delimiter
1012                    // This handles multi-word keys like "omitted value"
1013                    // Plain scalars in flow context can span multiple lines (YAML 1.2 spec)
1014
1015                    // Check if this is a quoted string (STRING token starting with quote)
1016                    // Quoted strings are complete in a single token and should not consume
1017                    // trailing newlines/whitespace
1018                    let is_quoted_string = if let Some(SyntaxKind::STRING) = self.current() {
1019                        self.current_text()
1020                            .map(|text| text.starts_with('"') || text.starts_with('\''))
1021                            .unwrap_or(false)
1022                    } else {
1023                        false
1024                    };
1025
1026                    self.bump(); // Consume the initial typed token
1027
1028                    // For quoted strings, we're done - the token contains the complete value.
1029                    // For plain scalars, keep consuming for multi-word/multi-line scalars.
1030                    if !is_quoted_string {
1031                        while let Some(kind) = self.current() {
1032                            // Check for flow delimiters and comments (but not NEWLINE - plain scalars can span lines)
1033                            if matches!(
1034                                kind,
1035                                SyntaxKind::COMMA
1036                                    | SyntaxKind::RIGHT_BRACE
1037                                    | SyntaxKind::RIGHT_BRACKET
1038                                    | SyntaxKind::COMMENT
1039                            ) {
1040                                break;
1041                            }
1042
1043                            // NEWLINE in flow context: consume it and continue reading the scalar
1044                            // The scalar continues on the next line
1045                            if kind == SyntaxKind::NEWLINE {
1046                                self.bump(); // consume the newline
1047                                             // Skip any indentation/whitespace that follows
1048                                while matches!(
1049                                    self.current(),
1050                                    Some(SyntaxKind::WHITESPACE | SyntaxKind::INDENT)
1051                                ) {
1052                                    self.bump();
1053                                }
1054                                // Continue with the main loop to consume more scalar content
1055                                continue;
1056                            }
1057
1058                            // Stop at trailing whitespace before delimiters
1059                            // For "[ a , b ]", stop at whitespace before comma
1060                            // For "{omitted value:,}", consume whitespace between words
1061                            if kind == SyntaxKind::WHITESPACE {
1062                                // Peek at what comes after the whitespace
1063                                // tokens are popped from end, so earlier indices are further ahead
1064                                if self.tokens.len() >= 2 {
1065                                    // Look at the token after this whitespace
1066                                    let after_whitespace = self.tokens[self.tokens.len() - 2].0;
1067                                    if matches!(
1068                                        after_whitespace,
1069                                        SyntaxKind::COMMA
1070                                            | SyntaxKind::RIGHT_BRACE
1071                                            | SyntaxKind::RIGHT_BRACKET
1072                                            | SyntaxKind::NEWLINE
1073                                            | SyntaxKind::COMMENT
1074                                    ) {
1075                                        // Whitespace followed by delimiter or comment - stop here (don't consume whitespace)
1076                                        break;
1077                                    }
1078                                    // Otherwise whitespace is between words - continue to consume it
1079                                }
1080                            }
1081
1082                            // Handle colons: stop if colon is followed by delimiter
1083                            if kind == SyntaxKind::COLON && self.tokens.len() >= 2 {
1084                                let next_kind = self.tokens[self.tokens.len() - 2].0;
1085                                if matches!(
1086                                    next_kind,
1087                                    SyntaxKind::COMMA
1088                                        | SyntaxKind::RIGHT_BRACE
1089                                        | SyntaxKind::RIGHT_BRACKET
1090                                        | SyntaxKind::WHITESPACE
1091                                        | SyntaxKind::NEWLINE
1092                                ) {
1093                                    // Colon followed by delimiter - this is key-value separator
1094                                    break;
1095                                }
1096                            }
1097
1098                            self.bump();
1099                        }
1100                    }
1101                }
1102            } else {
1103                // Fallback: consume tokens until we hit structure
1104                while let Some(kind) = self.current() {
1105                    if matches!(
1106                        kind,
1107                        SyntaxKind::NEWLINE
1108                            | SyntaxKind::DASH
1109                            | SyntaxKind::COMMENT
1110                            | SyntaxKind::DOC_START
1111                            | SyntaxKind::DOC_END
1112                    ) {
1113                        break;
1114                    }
1115
1116                    // In flow context, colons are allowed in scalars (for IPv6, URLs, etc.)
1117                    // In block context, stop at colons as they indicate mapping structure
1118                    if kind == SyntaxKind::COLON {
1119                        if self.in_flow_context {
1120                            // In flow context, check if this colon is followed by a delimiter
1121                            // If so, it's a key-value separator, not part of the scalar
1122                            if self.tokens.len() >= 2 {
1123                                let next_kind = self.tokens[self.tokens.len() - 2].0;
1124                                if matches!(
1125                                    next_kind,
1126                                    SyntaxKind::COMMA
1127                                        | SyntaxKind::RIGHT_BRACE
1128                                        | SyntaxKind::RIGHT_BRACKET
1129                                        | SyntaxKind::WHITESPACE
1130                                        | SyntaxKind::NEWLINE
1131                                ) {
1132                                    // Colon followed by delimiter - stop here
1133                                    break;
1134                                }
1135                            }
1136                            // Otherwise, allow colons in scalars (URLs, etc.) - continue consuming
1137                        } else {
1138                            // In block context, stop at colons (mapping structure)
1139                            break;
1140                        }
1141                    }
1142
1143                    // In flow context, stop at flow collection delimiters
1144                    if self.in_flow_context
1145                        && matches!(
1146                            kind,
1147                            SyntaxKind::LEFT_BRACKET
1148                                | SyntaxKind::RIGHT_BRACKET
1149                                | SyntaxKind::LEFT_BRACE
1150                                | SyntaxKind::RIGHT_BRACE
1151                                | SyntaxKind::COMMA
1152                        )
1153                    {
1154                        break;
1155                    }
1156                    self.bump();
1157                }
1158            }
1159        }
1160
1161        self.builder.finish_node();
1162    }
1163
1164    fn parse_tagged_value(&mut self) {
1165        // Peek at the tag to determine what kind of collection to parse
1166        let tag_text = self.peek_tag_text();
1167
1168        match tag_text {
1169            Some("!!set") => self.parse_tagged_set(),
1170            Some("!!omap") => self.parse_tagged_omap(),
1171            Some("!!pairs") => self.parse_tagged_pairs(),
1172            _ => {
1173                // Default tagged value behavior - tags can be applied to scalars, mappings, or sequences
1174                self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1175                self.bump(); // TAG token
1176
1177                // Skip any whitespace after the tag
1178                while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1179                    self.bump();
1180                }
1181
1182                // Parse whatever value follows the tag (scalar, flow mapping, flow sequence, etc.)
1183                self.parse_value();
1184
1185                self.builder.finish_node();
1186            }
1187        }
1188    }
1189
1190    fn peek_tag_text(&self) -> Option<&str> {
1191        self.tokens
1192            .last()
1193            .filter(|(kind, _)| *kind == SyntaxKind::TAG)
1194            .map(|(_, text)| text.as_str())
1195    }
1196
1197    fn parse_tagged_set(&mut self) {
1198        self.parse_tagged_collection(true); // true = parse as mapping
1199    }
1200
1201    fn parse_tagged_omap(&mut self) {
1202        self.parse_tagged_collection(false); // false = parse as sequence
1203    }
1204
1205    fn parse_tagged_pairs(&mut self) {
1206        self.parse_tagged_collection(false); // false = parse as sequence
1207    }
1208
1209    fn parse_tagged_collection(&mut self, is_mapping: bool) {
1210        self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1211
1212        // Consume the tag
1213        self.bump(); // TAG token
1214
1215        // Skip any whitespace after the tag
1216        while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1217            self.bump();
1218        }
1219
1220        // Parse the following structure based on type
1221        match self.current() {
1222            Some(SyntaxKind::LEFT_BRACE) if is_mapping => self.parse_flow_mapping(),
1223            Some(SyntaxKind::LEFT_BRACKET) if !is_mapping => self.parse_flow_sequence(),
1224            Some(SyntaxKind::NEWLINE) => {
1225                self.bump(); // consume newline
1226                             // Check if next token is indent (for indented content)
1227                if self.current() == Some(SyntaxKind::INDENT) {
1228                    self.bump(); // consume indent
1229                }
1230                if is_mapping {
1231                    self.parse_mapping();
1232                } else {
1233                    self.parse_sequence();
1234                }
1235            }
1236            _ => {
1237                if is_mapping {
1238                    self.parse_mapping();
1239                } else {
1240                    self.parse_sequence();
1241                }
1242            }
1243        }
1244
1245        self.builder.finish_node();
1246    }
1247
1248    fn parse_literal_block_scalar(&mut self) {
1249        self.builder.start_node(SyntaxKind::SCALAR.into());
1250        self.bump(); // consume PIPE
1251        self.parse_block_scalar_header();
1252        self.parse_block_scalar_content();
1253        self.builder.finish_node();
1254    }
1255
1256    fn parse_folded_block_scalar(&mut self) {
1257        self.builder.start_node(SyntaxKind::SCALAR.into());
1258        self.bump(); // consume GREATER
1259        self.parse_block_scalar_header();
1260        self.parse_block_scalar_content();
1261        self.builder.finish_node();
1262    }
1263
1264    fn parse_block_scalar_header(&mut self) {
1265        // Parse optional indentation indicator (1-9) and chomping indicator (+, -)
1266        // Format: |<indent><chomp> or |<chomp><indent>
1267        // Examples: |2, |-, |+, |2-, |-2, |2+, |+2
1268
1269        while let Some(kind) = self.current() {
1270            match kind {
1271                SyntaxKind::NEWLINE | SyntaxKind::COMMENT => break,
1272                SyntaxKind::INT => {
1273                    // Indentation indicator (1-9)
1274                    if let Some(text) = self.current_text() {
1275                        if text.len() == 1
1276                            && text
1277                                .chars()
1278                                .next()
1279                                .expect("text is non-empty: len == 1 checked above")
1280                                .is_ascii_digit()
1281                        {
1282                            self.bump(); // Consume the digit
1283                        } else {
1284                            // Not a single digit, stop
1285                            break;
1286                        }
1287                    } else {
1288                        break;
1289                    }
1290                }
1291                SyntaxKind::STRING => {
1292                    // Could be chomping indicator or other text
1293                    if let Some(text) = self.current_text() {
1294                        if text == "+" || text == "-" {
1295                            self.bump(); // Consume chomping indicator
1296                        } else {
1297                            // Some other text, stop parsing header
1298                            break;
1299                        }
1300                    } else {
1301                        break;
1302                    }
1303                }
1304                SyntaxKind::WHITESPACE => {
1305                    // Whitespace before comment or newline
1306                    self.bump();
1307                }
1308                _ => {
1309                    // Unknown token, stop parsing header
1310                    break;
1311                }
1312            }
1313        }
1314
1315        // Consume optional comment
1316        if self.current() == Some(SyntaxKind::COMMENT) {
1317            self.bump();
1318        }
1319
1320        // Consume the newline after the header
1321        if self.current() == Some(SyntaxKind::NEWLINE) {
1322            self.bump();
1323        }
1324    }
1325
1326    fn parse_block_scalar_content(&mut self) {
1327        // Consume all indented content that follows
1328        let mut last_was_newline = false;
1329        let mut base_indent: Option<usize> = None;
1330        let mut first_content_indent: Option<usize> = None;
1331
1332        while let Some(kind) = self.current() {
1333            // Detect first content indentation to use as base
1334            if kind == SyntaxKind::INDENT && first_content_indent.is_none() {
1335                first_content_indent = self.current_text().map(|t| t.len());
1336            }
1337
1338            // Set base_indent after seeing first INDENT token
1339            if base_indent.is_none() && first_content_indent.is_some() {
1340                base_indent = first_content_indent;
1341            }
1342
1343            // Check if we've reached unindented content BEFORE consuming
1344            if self.is_at_unindented_content_for_block_scalar(last_was_newline, base_indent) {
1345                break;
1346            }
1347
1348            match kind {
1349                // Stop at document markers
1350                SyntaxKind::DOC_START | SyntaxKind::DOC_END => break,
1351                // Track newlines to detect line starts
1352                SyntaxKind::NEWLINE => {
1353                    self.bump();
1354                    last_was_newline = true;
1355                    continue;
1356                }
1357                // Continue consuming content and whitespace
1358                _ => {
1359                    self.bump();
1360                    last_was_newline = false;
1361                }
1362            }
1363        }
1364    }
1365
1366    fn is_at_unindented_content_for_block_scalar(
1367        &self,
1368        after_newline: bool,
1369        base_indent: Option<usize>,
1370    ) -> bool {
1371        // Check if we've reached content at the beginning of a line (unindented)
1372        // Only check for structural tokens if we're at the start of a line
1373        if after_newline {
1374            // After a newline, check if the next token is unindented
1375            let current = self.current();
1376
1377            // COLON or QUESTION at start of line means end of block scalar
1378            if matches!(
1379                current,
1380                Some(SyntaxKind::COLON) | Some(SyntaxKind::QUESTION)
1381            ) {
1382                return true;
1383            }
1384
1385            // If we have base_indent, check if current line has less indentation
1386            if let Some(base) = base_indent {
1387                if current == Some(SyntaxKind::INDENT) {
1388                    if let Some(text) = self.current_text() {
1389                        if text.len() < base {
1390                            // Current line has less indentation than base - end of block scalar
1391                            return true;
1392                        }
1393                    }
1394                }
1395            }
1396
1397            // If we don't see INDENT, we've reached unindented content
1398            if current != Some(SyntaxKind::INDENT)
1399                && current != Some(SyntaxKind::WHITESPACE)
1400                && current != Some(SyntaxKind::NEWLINE)
1401                && current != Some(SyntaxKind::COMMENT)
1402            {
1403                // This is unindented content at the start of a line
1404                return true;
1405            }
1406        }
1407        false
1408    }
1409
1410    fn parse_mapping(&mut self) {
1411        self.parse_mapping_with_base_indent(0);
1412    }
1413
1414    fn parse_mapping_with_base_indent(&mut self, base_indent: usize) {
1415        self.builder.start_node(SyntaxKind::MAPPING.into());
1416        self.error_context.push_context(ParseContext::Mapping);
1417
1418        while self.current().is_some() {
1419            let tokens_before_iter = self.tokens.len();
1420            // Skip whitespace, break on dedent
1421            if self.skip_whitespace_only_with_dedent_check(base_indent) {
1422                break;
1423            }
1424
1425            // Emit comments as children of MAPPING
1426            loop {
1427                if self.current() == Some(SyntaxKind::COMMENT) {
1428                    // At root level (base_indent=0) all comments belong here since
1429                    // there's no parent scope, even if indented.
1430                    if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1431                        break;
1432                    }
1433                    self.bump();
1434                    if self.current() == Some(SyntaxKind::NEWLINE) {
1435                        self.bump();
1436                    }
1437                    if self.skip_whitespace_only_with_dedent_check(base_indent) {
1438                        break;
1439                    }
1440                } else {
1441                    break;
1442                }
1443            }
1444
1445            // Check dedent via tracked line indentation (covers the case where
1446            // MAPPING_ENTRY consumed its trailing NEWLINE before we could detect
1447            // the dedent in skip_whitespace_only_with_dedent_check).
1448            if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1449                break;
1450            }
1451
1452            // No mapping key found — exit
1453            if !self.is_mapping_key() && !self.is_complex_mapping_key() {
1454                break;
1455            }
1456
1457            // Check for complex keys (sequences or mappings as keys)
1458            if self.current() == Some(SyntaxKind::LEFT_BRACKET)
1459                || self.current() == Some(SyntaxKind::LEFT_BRACE)
1460            {
1461                // Start a MAPPING_ENTRY to wrap this key-value pair
1462                self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1463
1464                self.builder.start_node(SyntaxKind::KEY.into());
1465                if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
1466                    self.parse_flow_sequence();
1467                } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
1468                    self.parse_flow_mapping();
1469                }
1470                self.builder.finish_node();
1471
1472                self.skip_ws_and_newlines();
1473
1474                if self.current() == Some(SyntaxKind::COLON) {
1475                    self.bump();
1476                    self.skip_whitespace();
1477
1478                    self.builder.start_node(SyntaxKind::VALUE.into());
1479                    if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1480                        self.parse_value();
1481                    } else if self.current() == Some(SyntaxKind::NEWLINE) {
1482                        self.bump();
1483                        if self.current() == Some(SyntaxKind::INDENT) {
1484                            self.bump();
1485                            self.parse_value();
1486                        }
1487                    }
1488                    self.builder.finish_node();
1489                } else {
1490                    let error_msg = self.create_detailed_error(
1491                        "Missing colon in mapping",
1492                        "':' after key",
1493                        self.current_text(),
1494                    );
1495                    self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1496                }
1497
1498                // Finish the MAPPING_ENTRY node
1499                self.builder.finish_node();
1500            }
1501            // Check for explicit key indicator
1502            else if self.current() == Some(SyntaxKind::QUESTION) {
1503                // Start a MAPPING_ENTRY to wrap this key-value pair
1504                self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1505
1506                // Parse explicit key
1507                self.bump(); // consume '?'
1508                self.skip_whitespace();
1509
1510                self.builder.start_node(SyntaxKind::KEY.into());
1511                if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1512                    self.parse_value();
1513                }
1514                self.builder.finish_node();
1515
1516                self.skip_ws_and_newlines();
1517
1518                // Parse value if there's a colon
1519                if self.current() == Some(SyntaxKind::COLON) {
1520                    self.bump(); // consume ':'
1521                    self.skip_whitespace();
1522
1523                    self.builder.start_node(SyntaxKind::VALUE.into());
1524                    if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1525                        self.parse_value();
1526                    } else if self.current() == Some(SyntaxKind::NEWLINE) {
1527                        self.bump(); // consume newline
1528                        if self.current() == Some(SyntaxKind::INDENT) {
1529                            self.bump(); // consume indent
1530                            self.parse_value();
1531                        }
1532                    }
1533                    self.builder.finish_node();
1534                } else {
1535                    // No value, just a key - create explicit null value
1536                    self.builder.start_node(SyntaxKind::VALUE.into());
1537                    self.builder.start_node(SyntaxKind::SCALAR.into());
1538                    self.builder.token(SyntaxKind::NULL.into(), "");
1539                    self.builder.finish_node();
1540                    self.builder.finish_node();
1541                }
1542
1543                // Finish the MAPPING_ENTRY node
1544                self.builder.finish_node();
1545            } else {
1546                self.parse_mapping_key_value_pair();
1547            }
1548
1549            // Progress guard: if no token was consumed this iteration we
1550            // would loop forever (e.g. when is_mapping_key() is fooled by a
1551            // delimiter such as `}` followed by `:`, and synthetic-token
1552            // recovery never advances).
1553            if self.tokens.len() == tokens_before_iter {
1554                let unexpected = self.current_text().unwrap_or("").to_string();
1555                self.add_error(
1556                    format!("Unexpected token in mapping: {:?}", unexpected),
1557                    ParseErrorKind::Other,
1558                );
1559                self.bump();
1560            }
1561        }
1562
1563        self.builder.finish_node();
1564        self.error_context.pop_context();
1565    }
1566
1567    fn parse_sequence(&mut self) {
1568        self.parse_sequence_with_base_indent(0);
1569    }
1570
1571    fn parse_sequence_with_base_indent(&mut self, base_indent: usize) {
1572        self.builder.start_node(SyntaxKind::SEQUENCE.into());
1573        self.error_context.push_context(ParseContext::Sequence);
1574
1575        while self.current().is_some() {
1576            // Skip whitespace, break on dedent
1577            if self.skip_whitespace_only_with_dedent_check(base_indent) {
1578                break;
1579            }
1580
1581            // Emit comments as children of SEQUENCE
1582            loop {
1583                if self.current() == Some(SyntaxKind::COMMENT) {
1584                    // At root level (base_indent=0) all comments belong here since
1585                    // there's no parent scope, even if indented.
1586                    if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1587                        break;
1588                    }
1589                    self.bump();
1590                    if self.current() == Some(SyntaxKind::NEWLINE) {
1591                        self.bump();
1592                    }
1593                    if self.skip_whitespace_only_with_dedent_check(base_indent) {
1594                        break;
1595                    }
1596                } else {
1597                    break;
1598                }
1599            }
1600
1601            // Check dedent via tracked line indentation (covers the case where
1602            // SEQUENCE_ENTRY consumed its trailing NEWLINE before we could detect
1603            // the dedent in skip_whitespace_only_with_dedent_check).
1604            if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1605                break;
1606            }
1607
1608            // No dash — exit
1609            if self.current() != Some(SyntaxKind::DASH) {
1610                break;
1611            }
1612            // Start SEQUENCE_ENTRY node to wrap the entire item
1613            self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1614
1615            // Record the dash's line indentation for the item value parsing
1616            let item_indent = self.current_line_indent;
1617
1618            self.bump(); // consume dash
1619            self.skip_whitespace();
1620
1621            if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1622                // Use item's line indent so nested mappings parse at the right level
1623                self.parse_value_with_base_indent(item_indent);
1624            } else if self.current() == Some(SyntaxKind::NEWLINE) {
1625                // Check if next line is indented (nested content for sequence item)
1626                self.bump(); // consume newline
1627                if self.current() == Some(SyntaxKind::INDENT) {
1628                    let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
1629                    self.bump(); // consume indent
1630                                 // Parse the indented content as the sequence item value
1631                    self.parse_value_with_base_indent(indent_level);
1632                }
1633            }
1634
1635            // Block-style SEQUENCE_ENTRY owns its NEWLINE terminator (DESIGN.md)
1636            if self.current() == Some(SyntaxKind::NEWLINE) {
1637                self.bump();
1638            }
1639
1640            // Finish SEQUENCE_ENTRY node
1641            self.builder.finish_node();
1642        }
1643
1644        self.builder.finish_node();
1645        self.error_context.pop_context();
1646    }
1647
1648    /// Checks if the upcoming tokens form an implicit mapping pattern (key: value).
1649    ///
1650    /// This scans forward through the token buffer to detect if there's a colon at
1651    /// depth 0 (not nested inside brackets/braces) before hitting a comma or closing bracket.
1652    ///
1653    /// Scans from current token forward through upcoming tokens.
1654    ///
1655    /// # Examples
1656    /// - `[ 'key' : value ]` → true (colon at depth 0)
1657    /// - `[ value ]` → false (no colon before closing bracket)
1658    /// - `[ [a, b]: value ]` → true (colon after nested collection completes)
1659    /// - `[ {a: 1}, b ]` → false (colon is inside braces, not at depth 0)
1660    fn next_flow_element_is_implicit_mapping(&self) -> bool {
1661        // Chain current token with upcoming tokens (no allocation needed)
1662        let tokens = std::iter::once(self.current().unwrap_or(SyntaxKind::EOF))
1663            .chain(self.upcoming_tokens());
1664        has_implicit_mapping_pattern(tokens)
1665    }
1666
1667    /// Parse an implicit flow mapping (key: value without braces).
1668    /// Used inside flow sequences: [ key: value ] is valid YAML.
1669    fn parse_implicit_flow_mapping(&mut self) {
1670        self.builder.start_node(SyntaxKind::MAPPING.into());
1671        self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1672
1673        // Parse key
1674        self.builder.start_node(SyntaxKind::KEY.into());
1675        self.parse_value();
1676        self.builder.finish_node();
1677
1678        self.skip_ws_and_newlines();
1679
1680        // Consume colon
1681        if self.current() == Some(SyntaxKind::COLON) {
1682            self.bump();
1683            self.skip_ws_and_newlines();
1684        }
1685
1686        // Parse value
1687        self.builder.start_node(SyntaxKind::VALUE.into());
1688        // Check if value is omitted (implicit null)
1689        if matches!(
1690            self.current(),
1691            Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACKET)
1692        ) {
1693            // Omitted value - leave VALUE node empty
1694        } else {
1695            self.parse_value();
1696        }
1697        self.builder.finish_node();
1698
1699        self.builder.finish_node(); // MAPPING_ENTRY
1700        self.builder.finish_node(); // MAPPING
1701    }
1702}
1703
1704/// Standalone helper to detect implicit mapping pattern in flow collections.
1705/// Takes an iterator of SyntaxKind tokens (in reverse order, as stored in Parser).
1706/// Returns true if there's a colon at depth 0 before any comma or closing bracket.
1707fn has_implicit_mapping_pattern(tokens: impl Iterator<Item = SyntaxKind>) -> bool {
1708    let mut depth = 0;
1709
1710    for kind in tokens {
1711        match kind {
1712            // Opening brackets/braces increase nesting depth
1713            SyntaxKind::LEFT_BRACE | SyntaxKind::LEFT_BRACKET => {
1714                depth += 1;
1715            }
1716            // Closing brackets/braces decrease nesting depth
1717            SyntaxKind::RIGHT_BRACE | SyntaxKind::RIGHT_BRACKET => {
1718                if depth == 0 {
1719                    // Closing bracket at our level - end of element without finding colon
1720                    return false;
1721                }
1722                depth -= 1;
1723            }
1724            // At depth 0 (not inside nested collections), check for colon or separator
1725            SyntaxKind::COLON if depth == 0 => {
1726                // Found colon at our level - this is an implicit mapping
1727                return true;
1728            }
1729            SyntaxKind::COMMA if depth == 0 => {
1730                // Found separator at our level - not a mapping
1731                return false;
1732            }
1733            // Skip whitespace, newlines, and other tokens
1734            _ => {}
1735        }
1736    }
1737
1738    // Reached end of tokens without finding colon or separator
1739    false
1740}
1741
1742impl Parser {
1743    fn parse_flow_sequence(&mut self) {
1744        self.builder.start_node(SyntaxKind::SEQUENCE.into());
1745        self.error_context.push_context(ParseContext::FlowSequence);
1746
1747        if self.flow_depth >= MAX_FLOW_DEPTH {
1748            self.add_error(
1749                format!(
1750                    "Flow collection nested too deeply (limit {})",
1751                    MAX_FLOW_DEPTH
1752                ),
1753                ParseErrorKind::Other,
1754            );
1755            // Consume everything up to a closing delimiter to recover, then
1756            // bail out without recursing further.
1757            while let Some(kind) = self.current() {
1758                if matches!(
1759                    kind,
1760                    SyntaxKind::RIGHT_BRACKET | SyntaxKind::DOC_START | SyntaxKind::DOC_END
1761                ) {
1762                    break;
1763                }
1764                self.bump();
1765            }
1766            if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1767                self.bump();
1768            }
1769            self.builder.finish_node();
1770            self.error_context.pop_context();
1771            return;
1772        }
1773        self.flow_depth += 1;
1774
1775        self.bump(); // consume [
1776        self.skip_ws_and_newlines(); // Support comments and newlines in flow sequences
1777
1778        let prev_flow = self.in_flow_context;
1779        self.in_flow_context = true;
1780
1781        while self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1782            let tokens_before = self.tokens.len();
1783
1784            // Start SEQUENCE_ENTRY node to wrap the item
1785            self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1786
1787            // Check if this element is an implicit mapping (key: value)
1788            // Per YAML spec, [ key: value ] is valid - a sequence containing a mapping
1789            if self.next_flow_element_is_implicit_mapping() {
1790                // Parse as implicit flow mapping
1791                self.parse_implicit_flow_mapping();
1792            } else {
1793                // Parse as regular value
1794                self.parse_value();
1795            }
1796
1797            self.skip_ws_and_newlines(); // Support comments after values
1798
1799            // Flow-style SEQUENCE_ENTRY owns its COMMA terminator (except last entry)
1800            if self.current() == Some(SyntaxKind::COMMA) {
1801                self.bump();
1802                self.skip_ws_and_newlines(); // Support comments after commas
1803            }
1804
1805            self.builder.finish_node(); // Finish SEQUENCE_ENTRY
1806
1807            if self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1808                // No comma found and not at closing bracket
1809                // Check if we should break to avoid infinite loops
1810                if matches!(
1811                    self.current(),
1812                    Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1813                ) {
1814                    // These tokens indicate we've left the flow sequence context or hit invalid syntax
1815                    break;
1816                }
1817            }
1818
1819            // Guarantee progress: if no token was consumed this iteration we
1820            // would loop forever (e.g. on stray `}` inside `[...]`). Report
1821            // the unexpected token and skip it.
1822            if self.tokens.len() == tokens_before {
1823                let unexpected = self.current_text().unwrap_or("").to_string();
1824                self.add_error(
1825                    format!("Unexpected token in flow sequence: {:?}", unexpected),
1826                    ParseErrorKind::Other,
1827                );
1828                self.bump();
1829            }
1830        }
1831
1832        self.in_flow_context = prev_flow;
1833
1834        if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1835            self.bump();
1836        } else {
1837            let error_msg = self.create_detailed_error(
1838                "Unclosed flow sequence",
1839                "']' to close sequence",
1840                self.current_text(),
1841            );
1842            self.add_error_and_recover(
1843                error_msg,
1844                SyntaxKind::RIGHT_BRACKET,
1845                ParseErrorKind::UnclosedFlowSequence,
1846            );
1847        }
1848
1849        self.flow_depth -= 1;
1850        self.builder.finish_node();
1851        self.error_context.pop_context();
1852    }
1853
1854    fn parse_flow_mapping(&mut self) {
1855        self.builder.start_node(SyntaxKind::MAPPING.into());
1856        self.error_context.push_context(ParseContext::FlowMapping);
1857
1858        if self.flow_depth >= MAX_FLOW_DEPTH {
1859            self.add_error(
1860                format!(
1861                    "Flow collection nested too deeply (limit {})",
1862                    MAX_FLOW_DEPTH
1863                ),
1864                ParseErrorKind::Other,
1865            );
1866            while let Some(kind) = self.current() {
1867                if matches!(
1868                    kind,
1869                    SyntaxKind::RIGHT_BRACE | SyntaxKind::DOC_START | SyntaxKind::DOC_END
1870                ) {
1871                    break;
1872                }
1873                self.bump();
1874            }
1875            if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
1876                self.bump();
1877            }
1878            self.builder.finish_node();
1879            self.error_context.pop_context();
1880            return;
1881        }
1882        self.flow_depth += 1;
1883
1884        self.bump(); // consume {
1885        self.skip_ws_and_newlines(); // Support comments and newlines in flow mappings
1886
1887        let prev_flow = self.in_flow_context;
1888        self.in_flow_context = true;
1889
1890        while self.current() != Some(SyntaxKind::RIGHT_BRACE) && self.current().is_some() {
1891            // Check for unexpected structural tokens that indicate we've left flow context
1892            if matches!(
1893                self.current(),
1894                Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1895            ) {
1896                // These tokens indicate we've exited the flow mapping or hit invalid syntax
1897                break;
1898            }
1899
1900            let tokens_before = self.tokens.len();
1901
1902            // Start MAPPING_ENTRY node to wrap the key-value pair
1903            self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1904
1905            // Parse key - wrap in KEY node
1906            self.builder.start_node(SyntaxKind::KEY.into());
1907
1908            // Handle explicit key indicator (?) in flow context
1909            if self.current() == Some(SyntaxKind::QUESTION) {
1910                self.bump(); // consume '?'
1911                self.skip_whitespace();
1912            }
1913
1914            self.parse_value();
1915            self.builder.finish_node();
1916
1917            self.skip_ws_and_newlines(); // Support comments after keys
1918
1919            if self.current() == Some(SyntaxKind::COLON) {
1920                self.bump();
1921                self.skip_ws_and_newlines(); // Support comments after colons
1922
1923                // Check if value is omitted (comma or closing brace after colon)
1924                // In YAML, `key:,` or `key:}` means key has null value
1925                if matches!(
1926                    self.current(),
1927                    Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1928                ) {
1929                    // Omitted value - create VALUE node with implicit null scalar
1930                    self.builder.start_node(SyntaxKind::VALUE.into());
1931                    self.builder.start_node(SyntaxKind::SCALAR.into());
1932                    self.builder.token(SyntaxKind::NULL.into(), "");
1933                    self.builder.finish_node(); // SCALAR
1934                    self.builder.finish_node(); // VALUE
1935                } else {
1936                    // Parse value - wrap in VALUE node
1937                    self.builder.start_node(SyntaxKind::VALUE.into());
1938                    self.parse_value();
1939                    self.builder.finish_node();
1940                }
1941            } else if matches!(
1942                self.current(),
1943                Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1944            ) {
1945                // No colon, but followed by comma or closing brace
1946                // This means the key itself has a null value (shorthand for key: null)
1947                // Create VALUE node with implicit null scalar
1948                self.builder.start_node(SyntaxKind::VALUE.into());
1949                self.builder.start_node(SyntaxKind::SCALAR.into());
1950                self.builder.token(SyntaxKind::NULL.into(), "");
1951                self.builder.finish_node(); // SCALAR
1952                self.builder.finish_node(); // VALUE
1953            } else {
1954                let error_msg = self.create_detailed_error(
1955                    "Missing colon in flow mapping",
1956                    "':' after key",
1957                    self.current_text(),
1958                );
1959                self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1960            }
1961
1962            self.skip_ws_and_newlines(); // Support comments after values
1963
1964            // Flow-style entries own their COMMA terminator (except last entry)
1965            if self.current() == Some(SyntaxKind::COMMA) {
1966                self.bump();
1967                self.skip_ws_and_newlines(); // Support comments after commas
1968            }
1969
1970            // Finish MAPPING_ENTRY node
1971            self.builder.finish_node();
1972
1973            // Guarantee progress: if no token was consumed this iteration we
1974            // would loop forever (e.g. on stray `]` inside `{...}`). Report
1975            // the unexpected token and skip it.
1976            if self.tokens.len() == tokens_before {
1977                let unexpected = self.current_text().unwrap_or("").to_string();
1978                self.add_error(
1979                    format!("Unexpected token in flow mapping: {:?}", unexpected),
1980                    ParseErrorKind::Other,
1981                );
1982                self.bump();
1983            }
1984        }
1985
1986        self.in_flow_context = prev_flow;
1987
1988        if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
1989            self.bump();
1990        } else {
1991            let error_msg = self.create_detailed_error(
1992                "Unclosed flow mapping",
1993                "'}' to close mapping",
1994                self.current_text(),
1995            );
1996            self.add_error_and_recover(
1997                error_msg,
1998                SyntaxKind::RIGHT_BRACE,
1999                ParseErrorKind::UnclosedFlowMapping,
2000            );
2001        }
2002
2003        self.flow_depth -= 1;
2004        self.builder.finish_node();
2005        self.error_context.pop_context();
2006    }
2007
2008    fn parse_directive(&mut self) {
2009        self.builder.start_node(SyntaxKind::DIRECTIVE.into());
2010
2011        if self.current() == Some(SyntaxKind::DIRECTIVE) {
2012            self.bump(); // consume the directive token
2013        } else {
2014            self.add_error("Expected directive".to_string(), ParseErrorKind::Other);
2015        }
2016
2017        self.builder.finish_node();
2018    }
2019
2020    fn parse_explicit_key_mapping(&mut self) {
2021        // Parse mapping with explicit key indicator '?'
2022        self.builder.start_node(SyntaxKind::MAPPING.into());
2023
2024        while self.current() == Some(SyntaxKind::QUESTION) {
2025            // Start a MAPPING_ENTRY to wrap this key-value pair
2026            self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2027
2028            // Parse explicit key
2029            self.bump(); // consume '?'
2030            self.skip_whitespace();
2031
2032            // Parse key - can be any value including sequences and mappings
2033            self.builder.start_node(SyntaxKind::KEY.into());
2034
2035            // Parse the first part of the key
2036            if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2037                self.parse_value();
2038            }
2039
2040            // Check if this is a multiline key (newline followed by indent)
2041            // Only for scalar keys, not sequences or mappings
2042            if self.current() == Some(SyntaxKind::NEWLINE) {
2043                // Peek ahead to see if there's an indent after the newline
2044                // Since tokens are reversed, peek at the second-to-last token
2045                if self.tokens.len() >= 2 {
2046                    let (next_kind, _) = &self.tokens[self.tokens.len() - 2];
2047                    if *next_kind == SyntaxKind::INDENT {
2048                        // Check what comes after the indent (at position len() - 3)
2049                        if self.tokens.len() >= 3 {
2050                            let (token_after_indent, _) = &self.tokens[self.tokens.len() - 3];
2051                            // If it's a DASH, this is a sequence continuation which was already
2052                            // handled by parse_value() above - don't try to parse it as multiline scalar
2053                            if *token_after_indent != SyntaxKind::DASH {
2054                                // This is a multiline scalar key continuation
2055                                self.bump(); // consume newline
2056                                self.bump(); // consume indent
2057
2058                                // Parse scalar tokens at this indentation level as part of the key
2059                                while self.current().is_some()
2060                                    && self.current() != Some(SyntaxKind::NEWLINE)
2061                                    && self.current() != Some(SyntaxKind::COLON)
2062                                {
2063                                    let before = self.tokens.len();
2064                                    self.parse_scalar();
2065                                    if self.current() == Some(SyntaxKind::WHITESPACE) {
2066                                        self.bump(); // consume whitespace between key parts
2067                                    }
2068                                    // Progress guard: parse_scalar() can return without
2069                                    // consuming tokens for kinds it doesn't handle (e.g.
2070                                    // COMMENT). Break to avoid an infinite loop.
2071                                    if self.tokens.len() == before {
2072                                        break;
2073                                    }
2074                                }
2075                            }
2076                        }
2077                    }
2078                }
2079            }
2080
2081            self.builder.finish_node();
2082
2083            self.skip_ws_and_newlines();
2084
2085            // Parse value if there's a colon
2086            if self.current() == Some(SyntaxKind::COLON) {
2087                self.bump(); // consume ':'
2088                self.skip_whitespace();
2089
2090                self.builder.start_node(SyntaxKind::VALUE.into());
2091                if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2092                    self.parse_value();
2093                } else if self.current() == Some(SyntaxKind::NEWLINE) {
2094                    // Check if next line is indented (nested content)
2095                    self.bump(); // consume newline
2096                    if self.current() == Some(SyntaxKind::INDENT) {
2097                        self.bump(); // consume indent
2098                        self.parse_value();
2099                    }
2100                }
2101                self.builder.finish_node();
2102            } else {
2103                // No value, just a key - create explicit null value
2104                self.builder.start_node(SyntaxKind::VALUE.into());
2105                self.builder.start_node(SyntaxKind::SCALAR.into());
2106                self.builder.token(SyntaxKind::NULL.into(), "");
2107                self.builder.finish_node();
2108                self.builder.finish_node();
2109            }
2110
2111            // Finish the MAPPING_ENTRY node
2112            self.builder.finish_node();
2113
2114            self.skip_ws_and_newlines();
2115
2116            // Check if there are more entries
2117            if self.current() != Some(SyntaxKind::QUESTION) && !self.is_mapping_key() {
2118                break;
2119            }
2120        }
2121
2122        // Continue parsing regular mapping entries if any
2123        while self.current().is_some() && self.is_mapping_key() {
2124            let tokens_before_iter = self.tokens.len();
2125            // is_mapping_key() returns true for QUESTION, but
2126            // parse_mapping_key_value_pair does not consume a `?` key — that
2127            // would loop forever. Re-enter explicit-key handling for `?`.
2128            if self.current() == Some(SyntaxKind::QUESTION) {
2129                self.parse_explicit_key_entries();
2130                break;
2131            }
2132            self.parse_mapping_key_value_pair();
2133            self.skip_ws_and_newlines();
2134            // Progress guard against any future case where the body consumes
2135            // nothing (e.g. recovery via synthetic-token insertion).
2136            if self.tokens.len() == tokens_before_iter {
2137                let unexpected = self.current_text().unwrap_or("").to_string();
2138                self.add_error(
2139                    format!("Unexpected token in explicit-key mapping: {:?}", unexpected),
2140                    ParseErrorKind::Other,
2141                );
2142                self.bump();
2143            }
2144        }
2145
2146        self.builder.finish_node();
2147    }
2148
2149    fn parse_complex_key_mapping(&mut self) {
2150        // Parse mapping where the key is a complex structure (sequence or mapping)
2151        self.builder.start_node(SyntaxKind::MAPPING.into());
2152
2153        // Start a MAPPING_ENTRY to wrap this key-value pair
2154        self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2155
2156        // Parse the complex key
2157        self.builder.start_node(SyntaxKind::KEY.into());
2158        if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2159            self.parse_flow_sequence();
2160        } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2161            self.parse_flow_mapping();
2162        }
2163        self.builder.finish_node();
2164
2165        self.skip_ws_and_newlines(); // Allow newlines between key and colon
2166
2167        // Expect colon
2168        if self.current() == Some(SyntaxKind::COLON) {
2169            self.bump();
2170            self.skip_whitespace();
2171
2172            // Parse value
2173            self.builder.start_node(SyntaxKind::VALUE.into());
2174            if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2175                self.parse_value();
2176            } else if self.current() == Some(SyntaxKind::NEWLINE) {
2177                self.bump(); // consume newline
2178                if self.current() == Some(SyntaxKind::INDENT) {
2179                    self.bump(); // consume indent
2180                    self.parse_value();
2181                }
2182            }
2183            self.builder.finish_node();
2184        } else {
2185            let error_msg = self.create_detailed_error(
2186                "Missing colon in complex mapping",
2187                "':' after complex key",
2188                self.current_text(),
2189            );
2190            self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2191        }
2192
2193        // Finish the first MAPPING_ENTRY node
2194        self.builder.finish_node();
2195
2196        self.skip_ws_and_newlines();
2197
2198        // Continue parsing more entries if they exist
2199        while self.current().is_some() {
2200            let tokens_before_iter = self.tokens.len();
2201            if self.current() == Some(SyntaxKind::QUESTION) {
2202                // Switch to explicit key parsing
2203                self.parse_explicit_key_entries();
2204                break;
2205            } else if self.is_complex_mapping_key()
2206                || (self.is_mapping_key() && self.current() != Some(SyntaxKind::QUESTION))
2207            {
2208                // Start a MAPPING_ENTRY for this additional entry
2209                self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2210
2211                // Parse another entry
2212                self.builder.start_node(SyntaxKind::KEY.into());
2213
2214                if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2215                    self.parse_flow_sequence();
2216                } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2217                    self.parse_flow_mapping();
2218                } else if matches!(
2219                    self.current(),
2220                    Some(
2221                        SyntaxKind::STRING
2222                            | SyntaxKind::INT
2223                            | SyntaxKind::FLOAT
2224                            | SyntaxKind::BOOL
2225                            | SyntaxKind::NULL
2226                            | SyntaxKind::MERGE_KEY
2227                    )
2228                ) {
2229                    self.bump();
2230                }
2231                self.builder.finish_node();
2232
2233                self.skip_whitespace();
2234
2235                if self.current() == Some(SyntaxKind::COLON) {
2236                    self.bump();
2237                    self.skip_whitespace();
2238
2239                    self.builder.start_node(SyntaxKind::VALUE.into());
2240                    if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2241                        self.parse_value();
2242                    } else if self.current() == Some(SyntaxKind::NEWLINE) {
2243                        self.bump();
2244                        if self.current() == Some(SyntaxKind::INDENT) {
2245                            self.bump();
2246                            self.parse_value();
2247                        }
2248                    }
2249                    self.builder.finish_node();
2250                }
2251
2252                // Finish the MAPPING_ENTRY node
2253                self.builder.finish_node();
2254
2255                self.skip_ws_and_newlines();
2256            } else {
2257                break;
2258            }
2259
2260            // Progress guard: if is_mapping_key() returned true but nothing
2261            // consumed the current token (e.g. `]:` at top level), break to
2262            // avoid an infinite loop.
2263            if self.tokens.len() == tokens_before_iter {
2264                let unexpected = self.current_text().unwrap_or("").to_string();
2265                self.add_error(
2266                    format!("Unexpected token in complex mapping: {:?}", unexpected),
2267                    ParseErrorKind::Other,
2268                );
2269                self.bump();
2270            }
2271        }
2272
2273        self.builder.finish_node();
2274    }
2275
2276    fn parse_explicit_key_entries(&mut self) {
2277        // Helper to continue parsing explicit key entries within a mapping
2278        while self.current() == Some(SyntaxKind::QUESTION) {
2279            // Start a MAPPING_ENTRY to wrap this key-value pair
2280            self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2281
2282            self.bump(); // consume '?'
2283            self.skip_whitespace();
2284
2285            self.builder.start_node(SyntaxKind::KEY.into());
2286            if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2287                self.parse_value();
2288            }
2289            self.builder.finish_node();
2290
2291            self.skip_ws_and_newlines();
2292
2293            if self.current() == Some(SyntaxKind::COLON) {
2294                self.bump();
2295                self.skip_whitespace();
2296
2297                self.builder.start_node(SyntaxKind::VALUE.into());
2298                if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2299                    self.parse_value();
2300                } else if self.current() == Some(SyntaxKind::NEWLINE) {
2301                    self.bump();
2302                    if self.current() == Some(SyntaxKind::INDENT) {
2303                        self.bump();
2304                        self.parse_value();
2305                    }
2306                }
2307                self.builder.finish_node();
2308            } else {
2309                // No value, just a key - create explicit null value
2310                self.builder.start_node(SyntaxKind::VALUE.into());
2311                self.builder.start_node(SyntaxKind::SCALAR.into());
2312                self.builder.token(SyntaxKind::NULL.into(), "");
2313                self.builder.finish_node();
2314                self.builder.finish_node();
2315            }
2316
2317            // Finish the MAPPING_ENTRY node
2318            self.builder.finish_node();
2319
2320            self.skip_ws_and_newlines();
2321        }
2322    }
2323
2324    fn is_complex_mapping_key(&self) -> bool {
2325        // Check if a flow sequence or mapping is used as a key
2326        if !matches!(
2327            self.current(),
2328            Some(SyntaxKind::LEFT_BRACKET) | Some(SyntaxKind::LEFT_BRACE)
2329        ) {
2330            return false;
2331        }
2332
2333        // Look ahead to find matching closing bracket/brace and then check for colon
2334        let mut depth = 0;
2335        let start_kind = self.current();
2336        let close_kind = match start_kind {
2337            Some(SyntaxKind::LEFT_BRACKET) => SyntaxKind::RIGHT_BRACKET,
2338            Some(SyntaxKind::LEFT_BRACE) => SyntaxKind::RIGHT_BRACE,
2339            _ => return false,
2340        };
2341
2342        let mut found_close = false;
2343        for kind in self.upcoming_tokens() {
2344            if !found_close {
2345                if Some(kind) == start_kind {
2346                    depth += 1;
2347                } else if kind == close_kind {
2348                    if depth == 0 {
2349                        // Found matching close
2350                        found_close = true;
2351                    } else {
2352                        depth -= 1;
2353                    }
2354                }
2355            } else {
2356                // We've found the closing bracket/brace, now look for colon
2357                match kind {
2358                    SyntaxKind::WHITESPACE | SyntaxKind::INDENT => continue,
2359                    SyntaxKind::COLON => return true,
2360                    _ => return false,
2361                }
2362            }
2363        }
2364        false
2365    }
2366
2367    fn parse_mapping_value(&mut self) {
2368        // When parsing the value part of a mapping, be more conservative about
2369        // interpreting content as nested mappings. Only parse as mapping if
2370        // it's clearly a structured value, otherwise parse as scalar.
2371        match self.current() {
2372            Some(SyntaxKind::DASH) if !self.in_flow_context => self.parse_sequence(),
2373            Some(SyntaxKind::ANCHOR) => {
2374                self.bump(); // consume and emit anchor token to CST
2375                self.skip_whitespace();
2376                self.parse_value_with_base_indent(0);
2377            }
2378            Some(SyntaxKind::REFERENCE) => self.parse_alias(),
2379            Some(SyntaxKind::TAG) => self.parse_tagged_value(),
2380            Some(SyntaxKind::QUESTION) => {
2381                // Explicit key indicator - parse complex mapping
2382                self.parse_explicit_key_mapping();
2383            }
2384            Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
2385            Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
2386            Some(SyntaxKind::LEFT_BRACKET) => {
2387                // Check if this is a complex key in a mapping
2388                if !self.in_flow_context && self.is_complex_mapping_key() {
2389                    self.parse_complex_key_mapping();
2390                } else {
2391                    self.parse_flow_sequence();
2392                }
2393            }
2394            Some(SyntaxKind::LEFT_BRACE) => {
2395                // Check if this is a complex key in a mapping
2396                if !self.in_flow_context && self.is_complex_mapping_key() {
2397                    self.parse_complex_key_mapping();
2398                } else {
2399                    self.parse_flow_mapping();
2400                }
2401            }
2402            _ => {
2403                // For all other cases in mapping values, parse as scalar
2404                // This handles URLs and other complex scalar values containing colons
2405                self.parse_scalar();
2406            }
2407        }
2408    }
2409
2410    fn is_mapping_key(&self) -> bool {
2411        // Check if this is an explicit key indicator
2412        if self.current() == Some(SyntaxKind::QUESTION) {
2413            return true;
2414        }
2415
2416        // Check if this is a merge key
2417        if self.current() == Some(SyntaxKind::MERGE_KEY) {
2418            return true;
2419        }
2420
2421        // If current token is a dash, this is not a mapping key
2422        if self.current() == Some(SyntaxKind::DASH) {
2423            return false;
2424        }
2425
2426        // Look ahead to see if there's a colon after the current token
2427        // A valid mapping key should have a colon immediately after (with only whitespace)
2428        let upcoming = self.upcoming_tokens();
2429        for kind in upcoming {
2430            match kind {
2431                SyntaxKind::COLON => {
2432                    return true;
2433                }
2434                SyntaxKind::WHITESPACE => continue,
2435                // Any other token means this is not a simple mapping key
2436                _ => {
2437                    return false;
2438                }
2439            }
2440        }
2441        false
2442    }
2443
2444    fn skip_whitespace(&mut self) {
2445        self.skip_tokens(&[SyntaxKind::WHITESPACE]);
2446    }
2447
2448    fn skip_tokens(&mut self, kinds: &[SyntaxKind]) {
2449        while let Some(current) = self.current() {
2450            if kinds.contains(&current) {
2451                self.bump();
2452            } else {
2453                break;
2454            }
2455        }
2456    }
2457
2458    /// Check if a plain scalar continues on the next line after a NEWLINE
2459    /// This looks ahead to see if the next line has content at greater indentation
2460    fn is_plain_scalar_continuation(&self, scalar_indent: usize) -> bool {
2461        // Current token should be NEWLINE. Peek ahead to see what follows.
2462        // Tokens are in reverse order, so we look at earlier indices (closer to front)
2463        let current_idx = self.tokens.len().saturating_sub(1);
2464
2465        if current_idx == 0 {
2466            return false; // No more tokens
2467        }
2468
2469        // Look at tokens after the NEWLINE
2470        // Since tokens are reversed, indices before current_idx are "ahead" in the stream
2471        let mut peek_idx = current_idx.saturating_sub(1);
2472
2473        // Skip INDENT token if present and extract indentation level
2474        let next_line_indent = self
2475            .tokens
2476            .get(peek_idx)
2477            .and_then(|(kind, text)| {
2478                if *kind == SyntaxKind::INDENT {
2479                    peek_idx = peek_idx.saturating_sub(1);
2480                    Some(text.len())
2481                } else {
2482                    None
2483                }
2484            })
2485            .unwrap_or(0);
2486
2487        // Skip WHITESPACE tokens
2488        while self
2489            .tokens
2490            .get(peek_idx)
2491            .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2492        {
2493            peek_idx = peek_idx.saturating_sub(1);
2494        }
2495
2496        // Check if we have content token using safe get()
2497        let has_content = self.tokens.get(peek_idx).is_some_and(|(kind, _)| {
2498            matches!(
2499                kind,
2500                SyntaxKind::STRING
2501                    | SyntaxKind::INT
2502                    | SyntaxKind::FLOAT
2503                    | SyntaxKind::BOOL
2504                    | SyntaxKind::NULL
2505                    | SyntaxKind::UNTERMINATED_STRING
2506            )
2507        });
2508
2509        if !has_content || next_line_indent <= scalar_indent {
2510            return false;
2511        }
2512
2513        // Check if the next line is a mapping key (has a COLON after the content)
2514        // If so, it's not a continuation - it's a new mapping key
2515        if peek_idx > 0 {
2516            let mut check_idx = peek_idx.saturating_sub(1);
2517
2518            // Skip any whitespace after the content
2519            while self
2520                .tokens
2521                .get(check_idx)
2522                .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2523            {
2524                if check_idx == 0 {
2525                    break;
2526                }
2527                check_idx = check_idx.saturating_sub(1);
2528            }
2529
2530            // If we find a COLON, this is a mapping key, not a scalar continuation
2531            if self
2532                .tokens
2533                .get(check_idx)
2534                .is_some_and(|(kind, _)| *kind == SyntaxKind::COLON)
2535            {
2536                return false;
2537            }
2538        }
2539
2540        true
2541    }
2542
2543    /// Check if the current position is dedented relative to base_indent.
2544    /// This is used when we encounter a token (like COMMENT) and need to check if it's dedented.
2545    /// Returns true if dedent detected.
2546    fn is_at_dedented_position(&self, base_indent: usize) -> bool {
2547        // Use the tracked current_line_indent instead of searching backwards through tokens.
2548        // This works because current_line_indent is updated by bump() when INDENT/NEWLINE
2549        // tokens are consumed. After skip_whitespace_only_with_dedent_check() consumes
2550        // whitespace and INDENT tokens, current_line_indent contains the correct indentation
2551        // level for the current line.
2552        if base_indent == 0 {
2553            // At root level (base_indent=0), any indentation means content doesn't belong at root
2554            self.current_line_indent > 0
2555        } else {
2556            // At nested level, check if current line indentation is less than expected
2557            self.current_line_indent < base_indent
2558        }
2559    }
2560
2561    /// Skip only WHITESPACE, NEWLINE, and INDENT tokens. Returns true if dedent detected.
2562    /// Does NOT emit COMMENT tokens - caller must handle those separately.
2563    fn skip_whitespace_only_with_dedent_check(&mut self, base_indent: usize) -> bool {
2564        while self.current().is_some() {
2565            match self.current() {
2566                Some(SyntaxKind::WHITESPACE) => {
2567                    self.bump();
2568                }
2569                Some(SyntaxKind::NEWLINE) => {
2570                    self.bump();
2571                    // Check next token for indentation
2572                    match self.current() {
2573                        Some(SyntaxKind::INDENT) => {
2574                            if let Some((_, text)) = self.tokens.last() {
2575                                if text.len() < base_indent {
2576                                    // Dedent detected - don't consume the indent token
2577                                    return true;
2578                                }
2579                                if base_indent == 0 && !text.is_empty() {
2580                                    // At root level, any indentation means content doesn't belong at root
2581                                    return true;
2582                                }
2583                            }
2584                            self.bump(); // consume indent if at appropriate level
2585                        }
2586                        Some(SyntaxKind::COMMENT) => {
2587                            // COMMENT at column 0 (no INDENT after NEWLINE)
2588                            if base_indent > 0 {
2589                                // This is dedented - don't consume it
2590                                return true;
2591                            }
2592                            // base_indent==0, let caller handle the comment
2593                            return false;
2594                        }
2595                        Some(SyntaxKind::WHITESPACE) | Some(SyntaxKind::NEWLINE) => {
2596                            // More whitespace, continue loop
2597                        }
2598                        None => {
2599                            // End of input
2600                            return false;
2601                        }
2602                        _ => {
2603                            // Content at column 0
2604                            if base_indent > 0 {
2605                                return true; // dedent detected
2606                            }
2607                            // base_indent==0, let caller handle
2608                            return false;
2609                        }
2610                    }
2611                }
2612                Some(SyntaxKind::INDENT) => {
2613                    // Standalone indent token (NEWLINE was consumed by prior entry)
2614                    if let Some((_, text)) = self.tokens.last() {
2615                        if text.len() < base_indent {
2616                            return true; // dedent detected
2617                        }
2618                    }
2619                    self.bump();
2620                }
2621                _ => {
2622                    // Content or COMMENT found, stop skipping
2623                    return false;
2624                }
2625            }
2626        }
2627        false
2628    }
2629
2630    fn skip_ws_and_newlines(&mut self) {
2631        self.skip_tokens(&[
2632            SyntaxKind::WHITESPACE,
2633            SyntaxKind::NEWLINE,
2634            SyntaxKind::INDENT,
2635            SyntaxKind::COMMENT,
2636        ]);
2637    }
2638
2639    fn parse_mapping_key_value_pair(&mut self) {
2640        // Start MAPPING_ENTRY node to wrap the entire key-value pair
2641        self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2642
2643        // Parse regular key
2644        self.builder.start_node(SyntaxKind::KEY.into());
2645
2646        // Handle anchor before key (&a a:)
2647        if self.current() == Some(SyntaxKind::ANCHOR) {
2648            self.bump(); // consume and emit anchor token to CST
2649            self.skip_whitespace();
2650        }
2651
2652        if self.current() == Some(SyntaxKind::MERGE_KEY) {
2653            self.builder.start_node(SyntaxKind::SCALAR.into());
2654            self.bump(); // consume the merge key token
2655            self.builder.finish_node(); // SCALAR
2656        } else if self.current() == Some(SyntaxKind::REFERENCE) {
2657            // Handle alias as key (*b:)
2658            self.parse_alias();
2659        } else if matches!(
2660            self.current(),
2661            Some(
2662                SyntaxKind::STRING
2663                    | SyntaxKind::INT
2664                    | SyntaxKind::FLOAT
2665                    | SyntaxKind::BOOL
2666                    | SyntaxKind::NULL
2667            )
2668        ) {
2669            self.builder.start_node(SyntaxKind::SCALAR.into());
2670            self.bump(); // consume the key token
2671            self.builder.finish_node(); // SCALAR
2672        }
2673        self.builder.finish_node(); // KEY
2674
2675        self.skip_whitespace();
2676
2677        // Expect colon
2678        if self.current() == Some(SyntaxKind::COLON) {
2679            self.bump();
2680            self.skip_whitespace();
2681
2682            // Parse value - wrap in VALUE node
2683            self.builder.start_node(SyntaxKind::VALUE.into());
2684            let mut has_value = false;
2685            if self.current().is_some()
2686                && self.current() != Some(SyntaxKind::NEWLINE)
2687                && self.current() != Some(SyntaxKind::COMMENT)
2688            {
2689                // Inline value on the same line as the colon
2690                self.parse_mapping_value();
2691                has_value = true;
2692
2693                // Capture any trailing whitespace and comment on the same line (before NEWLINE)
2694                // This keeps inline comments like "value  # comment" together in the VALUE node
2695                if self.current() == Some(SyntaxKind::WHITESPACE) {
2696                    self.bump(); // emit whitespace inside VALUE
2697                }
2698                if self.current() == Some(SyntaxKind::COMMENT) {
2699                    self.bump(); // emit inline comment inside VALUE
2700                }
2701            } else if self.current() == Some(SyntaxKind::COMMENT) {
2702                // Comment after colon with no inline value
2703                // The comment belongs to the VALUE, and any indented content after it
2704                // also belongs to this VALUE (e.g., "key:  # comment\n  nested: value")
2705                self.bump(); // consume comment inside VALUE
2706
2707                if self.current() == Some(SyntaxKind::NEWLINE) {
2708                    self.bump(); // consume newline inside VALUE
2709
2710                    if self.current() == Some(SyntaxKind::INDENT) {
2711                        let indent_level =
2712                            self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2713                        self.bump(); // consume indent inside VALUE
2714                                     // Parse the indented content as part of this VALUE
2715                        self.parse_value_with_base_indent(indent_level);
2716                        has_value = true;
2717                    }
2718                }
2719                // If no indented content follows the comment, has_value stays false → implicit null
2720            } else if self.current() == Some(SyntaxKind::NEWLINE) {
2721                // Check if next line is indented (nested content) or starts with a sequence
2722                self.bump(); // consume newline
2723                if self.current() == Some(SyntaxKind::INDENT) {
2724                    let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2725                    self.bump(); // consume indent
2726                                 // Parse the indented content as the value, tracking indent level
2727                    self.parse_value_with_base_indent(indent_level);
2728                    has_value = true;
2729                } else if self.current() == Some(SyntaxKind::DASH) {
2730                    // Zero-indented sequence (same indentation as key)
2731                    // This is valid YAML: the sequence is the value for the key
2732                    self.parse_sequence();
2733                    has_value = true;
2734                }
2735            }
2736
2737            // If no value present, create an implicit null scalar
2738            if !has_value {
2739                self.builder.start_node(SyntaxKind::SCALAR.into());
2740                self.builder.token(SyntaxKind::NULL.into(), "");
2741                self.builder.finish_node();
2742            }
2743
2744            self.builder.finish_node(); // VALUE
2745        } else {
2746            let error_msg = self.create_detailed_error(
2747                "Missing colon in mapping",
2748                "':' after key",
2749                self.current_text(),
2750            );
2751            self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2752        }
2753
2754        // Consume any trailing inline whitespace before closing MAPPING_ENTRY
2755        // Note: Inline comments are consumed within the VALUE node itself.
2756        // Any COMMENT token here would be on a separate line and should not
2757        // be consumed as part of this entry (it may be dedented).
2758        while self.current() == Some(SyntaxKind::WHITESPACE) {
2759            self.bump();
2760        }
2761
2762        // Block-style entries own their NEWLINE terminator (DESIGN.md)
2763        if self.current() == Some(SyntaxKind::NEWLINE) {
2764            self.bump();
2765        }
2766
2767        // Finish MAPPING_ENTRY node
2768        self.builder.finish_node();
2769    }
2770
2771    fn bump(&mut self) {
2772        if let Some((kind, text)) = self.tokens.pop() {
2773            // Track line indentation for plain scalar continuation
2774            match kind {
2775                SyntaxKind::INDENT => {
2776                    self.current_line_indent = text.len();
2777                }
2778                SyntaxKind::NEWLINE => {
2779                    // Reset to 0 until we see the next INDENT
2780                    self.current_line_indent = 0;
2781                }
2782                _ => {}
2783            }
2784
2785            self.builder.token(kind.into(), &text);
2786            if self.current_token_index > 0 {
2787                self.current_token_index -= 1;
2788            }
2789            // Update error context position
2790            self.error_context.advance(text.len());
2791        }
2792    }
2793
2794    fn current(&self) -> Option<SyntaxKind> {
2795        self.tokens.last().map(|(kind, _)| *kind)
2796    }
2797
2798    fn current_text(&self) -> Option<&str> {
2799        self.tokens.last().map(|(_, text)| text.as_str())
2800    }
2801
2802    /// Iterator over upcoming tokens starting from the next token (not current)
2803    fn upcoming_tokens(&self) -> impl Iterator<Item = SyntaxKind> + '_ {
2804        // Since tokens are in reverse order (last is current), we need to iterate
2805        // from the second-to-last token backwards to the beginning
2806        let len = self.tokens.len();
2807        (0..len.saturating_sub(1))
2808            .rev()
2809            .map(move |i| self.tokens[i].0)
2810    }
2811
2812    fn add_error(&mut self, message: String, kind: ParseErrorKind) {
2813        // Create positioned error with line/column info
2814        let token_len = self.current_text().map(|s| s.len()).unwrap_or(1);
2815        let positioned_error = self.error_context.create_error(message, token_len, kind);
2816
2817        self.errors.push(positioned_error.message.clone());
2818        self.positioned_errors.push(positioned_error);
2819    }
2820
2821    /// Add an error with recovery
2822    fn add_error_and_recover(
2823        &mut self,
2824        message: String,
2825        expected: SyntaxKind,
2826        kind: ParseErrorKind,
2827    ) {
2828        self.add_error(message, kind);
2829
2830        // Determine recovery strategy
2831        let found = self.current();
2832        let strategy = self.error_context.suggest_recovery(expected, found);
2833
2834        match strategy {
2835            RecoveryStrategy::SkipToken => {
2836                // Skip the problematic token
2837                if self.current().is_some() {
2838                    self.bump();
2839                }
2840            }
2841            RecoveryStrategy::SkipToEndOfLine => {
2842                // Skip to end of line
2843                while self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2844                    self.bump();
2845                }
2846            }
2847            RecoveryStrategy::InsertToken(kind) => {
2848                // Insert synthetic token
2849                self.builder.token(kind.into(), "");
2850            }
2851            RecoveryStrategy::SyncToSafePoint => {
2852                // Find next safe synchronization point
2853                let sync_point = self
2854                    .error_context
2855                    .find_sync_point(&self.tokens, self.tokens.len() - self.current_token_index);
2856                let tokens_to_skip = sync_point - (self.tokens.len() - self.current_token_index);
2857                for _ in 0..tokens_to_skip {
2858                    if self.current().is_some() {
2859                        self.bump();
2860                    }
2861                }
2862            }
2863        }
2864    }
2865
2866    /// Create a detailed error message with helpful suggestions
2867    fn create_detailed_error(
2868        &self,
2869        base_message: &str,
2870        expected: &str,
2871        found: Option<&str>,
2872    ) -> String {
2873        let mut builder = ErrorBuilder::new(base_message);
2874        builder = builder.expected(expected);
2875
2876        if let Some(found_str) = found {
2877            builder = builder.found(found_str);
2878        } else if let Some(token) = self.current_text() {
2879            builder = builder.found(format!("'{}'", token));
2880        } else {
2881            builder = builder.found("end of input");
2882        }
2883
2884        // Add context
2885        let context = match self.error_context.current_context() {
2886            ParseContext::Mapping => "in mapping",
2887            ParseContext::Sequence => "in sequence",
2888            ParseContext::FlowMapping => "in flow mapping",
2889            ParseContext::FlowSequence => "in flow sequence",
2890            ParseContext::BlockScalar => "in block scalar",
2891            ParseContext::QuotedString => "in quoted string",
2892            _ => "at document level",
2893        };
2894        builder = builder.context(context);
2895
2896        // Add helpful suggestions based on the error type
2897        let suggestion = self.get_error_suggestion(base_message, expected, found);
2898        if let Some(suggestion_text) = suggestion {
2899            builder = builder.suggestion(suggestion_text);
2900        }
2901
2902        builder.build()
2903    }
2904
2905    /// Generate helpful suggestions for common errors
2906    fn get_error_suggestion(
2907        &self,
2908        base_message: &str,
2909        expected: &str,
2910        found: Option<&str>,
2911    ) -> Option<String> {
2912        if base_message.contains("Unterminated quoted string") {
2913            return Some(
2914                "Add closing quote or check for unescaped quotes within the string".to_string(),
2915            );
2916        }
2917
2918        if base_message.contains("Missing colon") || expected.contains("':'") {
2919            return Some("Add ':' after the key, or check for proper indentation".to_string());
2920        }
2921
2922        if base_message.contains("Unclosed flow sequence") {
2923            return Some(
2924                "Add ']' to close the array, or check for missing commas between elements"
2925                    .to_string(),
2926            );
2927        }
2928
2929        if base_message.contains("Unclosed flow mapping") {
2930            return Some(
2931                "Add '}' to close the object, or check for missing commas between key-value pairs"
2932                    .to_string(),
2933            );
2934        }
2935
2936        if let Some(found_text) = found {
2937            if found_text.contains('\n') {
2938                return Some(
2939                    "Unexpected newline - check indentation and YAML structure".to_string(),
2940                );
2941            }
2942
2943            if found_text.contains('\t') {
2944                return Some(
2945                    "Tabs are not allowed in YAML - use spaces for indentation".to_string(),
2946                );
2947            }
2948        }
2949
2950        None
2951    }
2952}
2953
2954/// Parse YAML text
2955pub(crate) fn parse(text: &str) -> ParsedYaml {
2956    let parser = Parser::new(text);
2957    parser.parse()
2958}
2959
2960// Editing methods for Sequence
2961
2962#[cfg(test)]
2963mod tests {
2964    use super::*;
2965    use crate::builder::{MappingBuilder, SequenceBuilder};
2966    use crate::scalar::ScalarValue;
2967    use crate::value::YamlValue; // For special collections tests
2968
2969    #[test]
2970    fn test_simple_mapping() {
2971        let yaml = "key: value";
2972        let parsed = YamlFile::from_str(yaml).unwrap();
2973        let doc = parsed.document().unwrap();
2974        let mapping = doc.as_mapping().unwrap();
2975
2976        // Basic structure test
2977        assert_eq!(parsed.to_string().trim(), "key: value");
2978
2979        // Test get functionality
2980        let value = mapping.get("key");
2981        assert!(value.is_some());
2982    }
2983
2984    #[test]
2985    fn test_simple_sequence() {
2986        let yaml = "- item1\n- item2";
2987        let parsed = YamlFile::from_str(yaml);
2988        assert!(parsed.is_ok());
2989    }
2990
2991    #[test]
2992    fn test_complex_yaml() {
2993        let yaml = r#"
2994name: my-app
2995version: 1.0.0
2996dependencies:
2997  - serde
2998  - tokio
2999config:
3000  port: 8080
3001  enabled: true
3002"#;
3003        let parsed = YamlFile::from_str(yaml).unwrap();
3004        assert_eq!(parsed.documents().count(), 1);
3005
3006        let doc = parsed.document().unwrap();
3007        assert!(doc.as_mapping().is_some());
3008    }
3009
3010    #[test]
3011    fn test_multiple_documents() {
3012        let yaml = r#"---
3013doc: first
3014---
3015doc: second
3016...
3017"#;
3018        let parsed = YamlFile::from_str(yaml).unwrap();
3019        assert_eq!(parsed.documents().count(), 2);
3020    }
3021
3022    #[test]
3023    fn test_flow_styles() {
3024        let yaml = r#"
3025array: [1, 2, 3]
3026object: {key: value, another: 42}
3027"#;
3028        let parsed = YamlFile::from_str(yaml).unwrap();
3029        assert!(parsed.document().is_some());
3030    }
3031
3032    #[test]
3033    fn test_scalar_types_parsing() {
3034        let yaml = r#"
3035string: hello
3036integer: 42
3037float: 3.14
3038bool_true: true
3039bool_false: false
3040null_value: null
3041tilde: ~
3042"#;
3043        let parsed = YamlFile::from_str(yaml).unwrap();
3044        let doc = parsed.document().unwrap();
3045        let mapping = doc.as_mapping().unwrap();
3046
3047        // All keys should be accessible
3048        assert!(mapping.get("string").is_some());
3049        assert!(mapping.get("integer").is_some());
3050        assert!(mapping.get("float").is_some());
3051        assert!(mapping.get("bool_true").is_some());
3052        assert!(mapping.get("bool_false").is_some());
3053        assert!(mapping.get("null_value").is_some());
3054        assert!(mapping.get("tilde").is_some());
3055    }
3056
3057    #[test]
3058    fn test_preserve_formatting() {
3059        let yaml = r#"# Comment at start
3060key:   value    # inline comment
3061
3062# Another comment
3063list:
3064  - item1   
3065  - item2
3066"#;
3067        let parsed = YamlFile::from_str(yaml).unwrap();
3068
3069        let doc = parsed.document().unwrap();
3070        let mapping = doc.as_mapping().unwrap();
3071        assert_eq!(
3072            mapping.get("key").unwrap().as_scalar().unwrap().as_string(),
3073            "value"
3074        );
3075        let list = mapping.get_sequence("list").unwrap();
3076        assert_eq!(list.len(), 2);
3077        // Note: to_string() preserves trailing spaces, so we check content with trim
3078        let items: Vec<String> = list
3079            .values()
3080            .map(|v| v.to_string().trim().to_string())
3081            .collect();
3082        assert_eq!(items, vec!["item1", "item2"]);
3083
3084        // Verify exact lossless round-trip
3085        let output = parsed.to_string();
3086        assert_eq!(output, yaml);
3087    }
3088
3089    #[test]
3090    fn test_quoted_strings() {
3091        let yaml = r#"
3092single: 'single quoted'
3093double: "double quoted"
3094plain: unquoted
3095"#;
3096        let parsed = YamlFile::from_str(yaml).unwrap();
3097        let doc = parsed.document().unwrap();
3098        let mapping = doc.as_mapping().unwrap();
3099
3100        assert!(mapping.get("single").is_some());
3101        assert!(mapping.get("double").is_some());
3102        assert!(mapping.get("plain").is_some());
3103    }
3104
3105    #[test]
3106    fn test_nested_structures() {
3107        let yaml = r#"
3108root:
3109  nested:
3110    deeply:
3111      value: 42
3112  list:
3113    - item1
3114    - item2
3115"#;
3116        let parsed = YamlFile::from_str(yaml).unwrap();
3117        assert!(parsed.document().is_some());
3118    }
3119
3120    #[test]
3121    fn test_empty_values() {
3122        let yaml = r#"
3123empty_string: ""
3124empty_after_colon:
3125another_key: value
3126"#;
3127        let parsed = YamlFile::from_str(yaml).unwrap();
3128        let doc = parsed.document().unwrap();
3129        let mapping = doc.as_mapping().unwrap();
3130
3131        assert!(mapping.get("empty_string").is_some());
3132        assert!(mapping.get("another_key").is_some());
3133    }
3134
3135    #[test]
3136    fn test_special_characters() {
3137        let yaml = r#"
3138special: "line1\nline2"
3139unicode: "emoji 😀"
3140escaped: 'it\'s escaped'
3141"#;
3142        let result = YamlFile::from_str(yaml);
3143        // Should parse without panicking
3144        assert!(result.is_ok());
3145    }
3146
3147    // Editing tests
3148
3149    #[test]
3150    fn test_error_handling() {
3151        // Invalid YAML should return error
3152        let yaml = "key: value\n  invalid indentation for key";
3153        let result = YamlFile::from_str(yaml);
3154        // For now, just check it doesn't panic
3155        let _ = result;
3156    }
3157
3158    // Directive tests
3159
3160    #[test]
3161    fn test_anchor_exact_output() {
3162        let yaml = "key: &anchor value\nref: *anchor";
3163        let parsed = YamlFile::from_str(yaml).unwrap();
3164        let output = parsed.to_string();
3165
3166        // Test exact output to ensure no duplication
3167        assert_eq!(output, "key: &anchor value\nref: *anchor");
3168    }
3169
3170    #[test]
3171    fn test_anchor_with_different_value_types() {
3172        let yaml = r#"string_anchor: &str_val "hello"
3173int_anchor: &int_val 42
3174bool_anchor: &bool_val true
3175null_anchor: &null_val null
3176str_ref: *str_val
3177int_ref: *int_val
3178bool_ref: *bool_val
3179null_ref: *null_val"#;
3180
3181        let parsed = YamlFile::from_str(yaml);
3182        assert!(
3183            parsed.is_ok(),
3184            "Should parse anchors with different value types"
3185        );
3186
3187        let yaml_doc = parsed.unwrap();
3188
3189        let doc = yaml_doc.document().unwrap();
3190        let mapping = doc.as_mapping().unwrap();
3191
3192        // Check anchor definitions
3193        assert_eq!(
3194            mapping
3195                .get("string_anchor")
3196                .unwrap()
3197                .as_scalar()
3198                .unwrap()
3199                .as_string(),
3200            "hello"
3201        );
3202        assert_eq!(mapping.get("int_anchor").unwrap().to_i64(), Some(42));
3203        assert_eq!(mapping.get("bool_anchor").unwrap().to_bool(), Some(true));
3204        assert!(mapping.get("null_anchor").unwrap().as_scalar().is_some());
3205
3206        // Check alias references
3207        let str_ref = mapping.get("str_ref").unwrap();
3208        assert!(str_ref.is_alias());
3209        assert_eq!(str_ref.as_alias().unwrap().name(), "str_val");
3210
3211        let int_ref = mapping.get("int_ref").unwrap();
3212        assert!(int_ref.is_alias());
3213        assert_eq!(int_ref.as_alias().unwrap().name(), "int_val");
3214
3215        let bool_ref = mapping.get("bool_ref").unwrap();
3216        assert!(bool_ref.is_alias());
3217        assert_eq!(bool_ref.as_alias().unwrap().name(), "bool_val");
3218
3219        let null_ref = mapping.get("null_ref").unwrap();
3220        assert!(null_ref.is_alias());
3221        assert_eq!(null_ref.as_alias().unwrap().name(), "null_val");
3222
3223        // Verify exact round-trip preservation
3224        let output = yaml_doc.to_string();
3225        assert_eq!(output, yaml);
3226    }
3227
3228    #[test]
3229    fn test_undefined_alias_parses_successfully() {
3230        let yaml = "key: *undefined";
3231        let parse_result = Parse::parse_yaml(yaml);
3232
3233        // Parser should NOT validate undefined aliases - that's semantic analysis
3234        // The parser just builds the CST
3235        assert!(
3236            !parse_result.has_errors(),
3237            "Parser should not validate undefined aliases"
3238        );
3239
3240        // The alias should be preserved in the output
3241        let output = parse_result.tree().to_string();
3242        assert_eq!(output.trim(), "key: *undefined");
3243    }
3244
3245    #[test]
3246    fn test_anchor_names_with_alphanumeric_chars() {
3247        // Test valid anchor names with underscores and numbers (YAML spec compliant)
3248        let yaml1 = "key1: &anchor_123 val1\nref1: *anchor_123";
3249        let parsed1 = YamlFile::from_str(yaml1);
3250        assert!(
3251            parsed1.is_ok(),
3252            "Should parse anchors with underscores and numbers"
3253        );
3254
3255        let file1 = parsed1.unwrap();
3256        let doc1 = file1.document().unwrap();
3257        let map1 = doc1.as_mapping().unwrap();
3258        assert_eq!(
3259            map1.get("key1").unwrap().as_scalar().unwrap().as_string(),
3260            "val1"
3261        );
3262        assert!(map1.get("ref1").unwrap().is_alias());
3263        assert_eq!(
3264            map1.get("ref1").unwrap().as_alias().unwrap().name(),
3265            "anchor_123"
3266        );
3267        assert_eq!(file1.to_string(), yaml1);
3268
3269        let yaml2 = "key2: &AnchorName val2\nref2: *AnchorName";
3270        let parsed2 = YamlFile::from_str(yaml2);
3271        assert!(parsed2.is_ok(), "Should parse anchors with mixed case");
3272
3273        let file2 = parsed2.unwrap();
3274        let doc2 = file2.document().unwrap();
3275        let map2 = doc2.as_mapping().unwrap();
3276        assert_eq!(
3277            map2.get("key2").unwrap().as_scalar().unwrap().as_string(),
3278            "val2"
3279        );
3280        assert!(map2.get("ref2").unwrap().is_alias());
3281        assert_eq!(
3282            map2.get("ref2").unwrap().as_alias().unwrap().name(),
3283            "AnchorName"
3284        );
3285        assert_eq!(file2.to_string(), yaml2);
3286
3287        let yaml3 = "key3: &anchor123abc val3\nref3: *anchor123abc";
3288        let parsed3 = YamlFile::from_str(yaml3);
3289        assert!(
3290            parsed3.is_ok(),
3291            "Should parse anchors with letters and numbers"
3292        );
3293
3294        let file3 = parsed3.unwrap();
3295        let doc3 = file3.document().unwrap();
3296        let map3 = doc3.as_mapping().unwrap();
3297        assert_eq!(
3298            map3.get("key3").unwrap().as_scalar().unwrap().as_string(),
3299            "val3"
3300        );
3301        assert!(map3.get("ref3").unwrap().is_alias());
3302        assert_eq!(
3303            map3.get("ref3").unwrap().as_alias().unwrap().name(),
3304            "anchor123abc"
3305        );
3306        assert_eq!(file3.to_string(), yaml3);
3307    }
3308
3309    #[test]
3310    fn test_anchor_in_sequence_detailed() {
3311        let yaml = r#"items:
3312  - &first_item value1
3313  - second_item
3314  - *first_item"#;
3315
3316        let parsed = YamlFile::from_str(yaml);
3317        assert!(parsed.is_ok(), "Should parse anchors in sequences");
3318
3319        let yaml_doc = parsed.unwrap();
3320
3321        let doc = yaml_doc.document().unwrap();
3322        let mapping = doc.as_mapping().unwrap();
3323        let seq = mapping.get_sequence("items").unwrap();
3324        assert_eq!(seq.len(), 3);
3325
3326        // Check that item 0 has the anchor definition and item 2 is an alias
3327        let item0 = seq.get(0).unwrap();
3328        let item1 = seq.get(1).unwrap();
3329        let item2 = seq.get(2).unwrap();
3330
3331        // Item 0 should be a scalar with value "value1" (with anchor)
3332        assert_eq!(item0.as_scalar().unwrap().as_string(), "value1");
3333
3334        // Item 1 should be a regular scalar
3335        assert_eq!(item1.as_scalar().unwrap().as_string(), "second_item");
3336
3337        // Item 2 should be an alias
3338        assert!(item2.is_alias(), "Third item should be an alias");
3339        assert_eq!(item2.as_alias().unwrap().name(), "first_item");
3340
3341        let output = yaml_doc.to_string();
3342        assert_eq!(output, yaml);
3343    }
3344
3345    #[test]
3346    fn test_preserve_whitespace_around_anchors() {
3347        let yaml = "key:  &anchor   value  \nref:  *anchor  ";
3348        let parsed = YamlFile::from_str(yaml).unwrap();
3349
3350        let doc = parsed.document().unwrap();
3351        let mapping = doc.as_mapping().unwrap();
3352        assert_eq!(
3353            mapping
3354                .get("key")
3355                .unwrap()
3356                .as_scalar()
3357                .unwrap()
3358                .as_string()
3359                .trim(),
3360            "value"
3361        );
3362        let ref_node = mapping.get("ref").unwrap();
3363        assert!(ref_node.is_alias());
3364        assert_eq!(ref_node.as_alias().unwrap().name(), "anchor");
3365
3366        // Verify exact round-trip (preserves whitespace)
3367        let output = parsed.to_string();
3368        assert_eq!(output, yaml);
3369    }
3370
3371    #[test]
3372    fn test_literal_block_scalar_basic() {
3373        let yaml = r#"literal: |
3374  Line 1
3375  Line 2
3376  Line 3
3377"#;
3378        let parsed = YamlFile::from_str(yaml);
3379        assert!(parsed.is_ok(), "Should parse basic literal block scalar");
3380
3381        let yaml_doc = parsed.unwrap();
3382        let output = yaml_doc.to_string();
3383
3384        // Should preserve the literal block scalar format exactly
3385        assert_eq!(output, yaml);
3386    }
3387
3388    #[test]
3389    fn test_folded_block_scalar_basic() {
3390        let yaml = r#"folded: >
3391  This is a very long line that will be folded
3392  into a single line in the output
3393  but preserves paragraph breaks.
3394
3395  This is a new paragraph.
3396"#;
3397        let parsed = YamlFile::from_str(yaml);
3398        assert!(parsed.is_ok(), "Should parse basic folded block scalar");
3399
3400        let yaml_doc = parsed.unwrap();
3401        let output = yaml_doc.to_string();
3402
3403        // Should preserve the folded block scalar format exactly
3404        assert_eq!(output, yaml);
3405    }
3406
3407    #[test]
3408    fn test_literal_block_scalar_with_chomping_indicators() {
3409        // Test strip indicator (-)
3410        let yaml1 = r#"strip: |-
3411  Line 1
3412  Line 2
3413
3414"#;
3415        let parsed1 = YamlFile::from_str(yaml1);
3416        assert!(
3417            parsed1.is_ok(),
3418            "Should parse literal block scalar with strip indicator"
3419        );
3420
3421        let file1 = parsed1.unwrap();
3422        let doc1 = file1.document().unwrap();
3423        let mapping1 = doc1.as_mapping().unwrap();
3424        let value1 = mapping1
3425            .get("strip")
3426            .unwrap()
3427            .as_scalar()
3428            .unwrap()
3429            .as_string();
3430        assert_eq!(value1, "Line 1\nLine 2");
3431
3432        let output1 = file1.to_string();
3433        assert_eq!(output1, yaml1);
3434
3435        // Test keep indicator (+)
3436        let yaml2 = r#"keep: |+
3437  Line 1
3438  Line 2
3439
3440"#;
3441        let parsed2 = YamlFile::from_str(yaml2);
3442        assert!(
3443            parsed2.is_ok(),
3444            "Should parse literal block scalar with keep indicator"
3445        );
3446
3447        let file2 = parsed2.unwrap();
3448        let doc2 = file2.document().unwrap();
3449        let mapping2 = doc2.as_mapping().unwrap();
3450        let value2 = mapping2
3451            .get("keep")
3452            .unwrap()
3453            .as_scalar()
3454            .unwrap()
3455            .as_string();
3456        assert_eq!(value2, "Line 1\nLine 2\n\n");
3457
3458        let output2 = file2.to_string();
3459        assert_eq!(output2, yaml2);
3460    }
3461
3462    #[test]
3463    fn test_folded_block_scalar_with_chomping_indicators() {
3464        // Test strip indicator (-)
3465        let yaml1 = r#"strip: >-
3466  Folded content that should
3467  be stripped of final newlines
3468"#;
3469        let parsed1 = YamlFile::from_str(yaml1);
3470        assert!(
3471            parsed1.is_ok(),
3472            "Should parse folded block scalar with strip indicator"
3473        );
3474
3475        let file1 = parsed1.unwrap();
3476        let doc1 = file1.document().unwrap();
3477        let mapping1 = doc1.as_mapping().unwrap();
3478        let value1 = mapping1
3479            .get("strip")
3480            .unwrap()
3481            .as_scalar()
3482            .unwrap()
3483            .as_string();
3484        assert_eq!(
3485            value1,
3486            "Folded content that should be stripped of final newlines"
3487        );
3488
3489        let output1 = file1.to_string();
3490        assert_eq!(output1, yaml1);
3491
3492        // Test keep indicator (+)
3493        let yaml2 = r#"keep: >+
3494  Folded content that should
3495  keep all final newlines
3496
3497"#;
3498        let parsed2 = YamlFile::from_str(yaml2);
3499        assert!(
3500            parsed2.is_ok(),
3501            "Should parse folded block scalar with keep indicator"
3502        );
3503
3504        let file2 = parsed2.unwrap();
3505        let doc2 = file2.document().unwrap();
3506        let mapping2 = doc2.as_mapping().unwrap();
3507        let value2 = mapping2
3508            .get("keep")
3509            .unwrap()
3510            .as_scalar()
3511            .unwrap()
3512            .as_string();
3513        assert_eq!(
3514            value2,
3515            "Folded content that should keep all final newlines\n\n"
3516        );
3517
3518        let output2 = file2.to_string();
3519        assert_eq!(output2, yaml2);
3520    }
3521
3522    #[test]
3523    fn test_block_scalar_with_explicit_indentation() {
3524        let yaml1 = r#"explicit: |2
3525    Two space indent
3526    Another line
3527"#;
3528        let parsed1 = YamlFile::from_str(yaml1)
3529            .expect("Should parse literal block scalar with explicit indentation");
3530
3531        let doc1 = parsed1.document().expect("Should have document");
3532        let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3533        let scalar1 = mapping1
3534            .get("explicit")
3535            .expect("Should have 'explicit' key");
3536        assert_eq!(
3537            scalar1.as_scalar().unwrap().as_string(),
3538            "Two space indent\nAnother line\n"
3539        );
3540
3541        let output1 = parsed1.to_string();
3542        assert_eq!(output1, yaml1);
3543
3544        let yaml2 = r#"folded_explicit: >3
3545      Three space indent
3546      Another folded line
3547"#;
3548        let parsed2 = YamlFile::from_str(yaml2)
3549            .expect("Should parse folded block scalar with explicit indentation");
3550
3551        let doc2 = parsed2.document().expect("Should have document");
3552        let mapping2 = doc2.as_mapping().expect("Should be a mapping");
3553        let scalar2 = mapping2
3554            .get("folded_explicit")
3555            .expect("Should have 'folded_explicit' key");
3556        assert_eq!(
3557            scalar2.as_scalar().unwrap().as_string(),
3558            "Three space indent Another folded line\n"
3559        );
3560
3561        let output2 = parsed2.to_string();
3562        assert_eq!(output2, yaml2);
3563    }
3564
3565    #[test]
3566    fn test_block_scalar_in_mapping() {
3567        let yaml = r#"description: |
3568  This is a multi-line
3569  description that should
3570  preserve line breaks.
3571
3572  It can have multiple paragraphs too.
3573
3574summary: >
3575  This is a summary that
3576  should be folded into
3577  a single line.
3578
3579version: "1.0"
3580"#;
3581        let parsed =
3582            YamlFile::from_str(yaml).expect("Should parse block scalars in mapping context");
3583
3584        let doc = parsed.document().expect("Should have document");
3585        let mapping = doc.as_mapping().expect("Should be a mapping");
3586
3587        let description = mapping
3588            .get("description")
3589            .expect("Should have 'description' key");
3590        assert_eq!(
3591            description.as_scalar().unwrap().as_string(),
3592            "This is a multi-line\ndescription that should\npreserve line breaks.\n\nIt can have multiple paragraphs too.\n"
3593        );
3594
3595        let summary = mapping.get("summary").expect("Should have 'summary' key");
3596        assert_eq!(
3597            summary.as_scalar().unwrap().as_string(),
3598            "This is a summary that should be folded into a single line.\n"
3599        );
3600
3601        let version = mapping.get("version").expect("Should have 'version' key");
3602        assert_eq!(version.as_scalar().unwrap().as_string(), "1.0");
3603
3604        let output = parsed.to_string();
3605        assert_eq!(output, yaml);
3606    }
3607
3608    #[test]
3609    fn test_mixed_block_and_regular_scalars() {
3610        let yaml = r#"config:
3611  name: "My App"
3612  description: |
3613    This application does many things:
3614    - Feature 1
3615    - Feature 2
3616    - Feature 3
3617  summary: >
3618    A brief summary that spans
3619    multiple lines but should
3620    be folded together.
3621  version: 1.0
3622  enabled: true
3623"#;
3624        let parsed =
3625            YamlFile::from_str(yaml).expect("Should parse mixed block and regular scalars");
3626
3627        let doc = parsed.document().expect("Should have document");
3628        let mapping = doc.as_mapping().expect("Should be a mapping");
3629        let config_node = mapping.get("config").expect("Should have 'config' key");
3630        let config = config_node
3631            .as_mapping()
3632            .expect("Should be a nested mapping");
3633
3634        assert_eq!(
3635            config.get("name").unwrap().as_scalar().unwrap().as_string(),
3636            "My App"
3637        );
3638        assert_eq!(
3639            config
3640                .get("description")
3641                .unwrap()
3642                .as_scalar()
3643                .unwrap()
3644                .as_string(),
3645            "This application does many things:\n- Feature 1\n- Feature 2\n- Feature 3\n"
3646        );
3647        assert_eq!(
3648            config
3649                .get("summary")
3650                .unwrap()
3651                .as_scalar()
3652                .unwrap()
3653                .as_string(),
3654            "A brief summary that spans multiple lines but should be folded together.\n"
3655        );
3656        assert_eq!(
3657            config
3658                .get("version")
3659                .unwrap()
3660                .as_scalar()
3661                .unwrap()
3662                .as_string(),
3663            "1.0"
3664        );
3665        assert_eq!(config.get("enabled").unwrap().to_bool(), Some(true));
3666
3667        let output = parsed.to_string();
3668        assert_eq!(output, yaml);
3669    }
3670
3671    #[test]
3672    fn test_block_scalar_edge_cases() {
3673        // Edge case: block scalar where the next line becomes its content
3674        // When a block scalar has no indented content, the next line at the same level
3675        // is treated as content, not as a new key
3676        let yaml1 = r#"empty_literal: |
3677empty_folded: >
3678"#;
3679        let parsed1 = YamlFile::from_str(yaml1).expect("Should parse this edge case");
3680
3681        // Verify API access - the "empty_folded: >" line is the CONTENT of empty_literal!
3682        let doc1 = parsed1.document().expect("Should have document");
3683        let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3684        assert_eq!(mapping1.len(), 1, "Should have only one key");
3685        assert_eq!(
3686            mapping1
3687                .get("empty_literal")
3688                .unwrap()
3689                .as_scalar()
3690                .unwrap()
3691                .as_string(),
3692            "empty_folded: >\n"
3693        );
3694
3695        assert_eq!(parsed1.to_string(), yaml1);
3696
3697        // Block scalar with only whitespace
3698        let yaml2 = r#"whitespace: |
3699
3700
3701"#;
3702        let parsed2 =
3703            YamlFile::from_str(yaml2).expect("Should parse block scalar with only whitespace");
3704
3705        assert_eq!(parsed2.to_string(), yaml2);
3706
3707        // Block scalar followed immediately by another key
3708        let yaml3 = r#"first: |
3709  Content
3710second: value
3711"#;
3712        let parsed3 =
3713            YamlFile::from_str(yaml3).expect("Should parse block scalar followed by other keys");
3714
3715        let doc3 = parsed3.document().expect("Should have document");
3716        let mapping3 = doc3.as_mapping().expect("Should be a mapping");
3717        assert_eq!(
3718            mapping3
3719                .get("first")
3720                .unwrap()
3721                .as_scalar()
3722                .unwrap()
3723                .as_string(),
3724            "Content\n"
3725        );
3726        assert_eq!(
3727            mapping3
3728                .get("second")
3729                .unwrap()
3730                .as_scalar()
3731                .unwrap()
3732                .as_string(),
3733            "value"
3734        );
3735
3736        let output3 = parsed3.to_string();
3737        assert_eq!(output3, yaml3);
3738    }
3739
3740    #[test]
3741    fn test_literal_block_scalar_advanced_formatting() {
3742        let yaml = r#"poem: |
3743  Roses are red,
3744  Violets are blue,
3745  YAML is great,
3746  And so are you!
3747
3748  This is another stanza
3749  with different content.
3750    And this line has extra indentation.
3751  Back to normal indentation.
3752
3753  Final stanza.
3754"#;
3755        let parsed = YamlFile::from_str(yaml).expect("Should parse complex literal block scalar");
3756
3757        let doc = parsed.document().expect("Should have document");
3758        let mapping = doc.as_mapping().expect("Should be a mapping");
3759        let poem = mapping.get("poem").expect("Should have 'poem' key");
3760        let expected_content = "Roses are red,\nViolets are blue,\nYAML is great,\nAnd so are you!\n\nThis is another stanza\nwith different content.\n  And this line has extra indentation.\nBack to normal indentation.\n\nFinal stanza.\n";
3761        assert_eq!(poem.as_scalar().unwrap().as_string(), expected_content);
3762
3763        let output = parsed.to_string();
3764        assert_eq!(output, yaml);
3765    }
3766
3767    #[test]
3768    fn test_folded_block_scalar_paragraph_handling() {
3769        let yaml = r#"description: >
3770  This is the first paragraph that should
3771  be folded into a single line when processed
3772  by a YAML parser.
3773
3774  This is a second paragraph that should
3775  also be folded but kept separate from
3776  the first paragraph.
3777
3778
3779  This is a third paragraph after
3780  multiple blank lines.
3781
3782  Final paragraph.
3783"#;
3784        let parsed =
3785            YamlFile::from_str(yaml).expect("Should parse folded block scalar with paragraphs");
3786
3787        let doc = parsed.document().expect("Should have document");
3788        let mapping = doc.as_mapping().expect("Should be a mapping");
3789        let description = mapping
3790            .get("description")
3791            .expect("Should have 'description' key");
3792        let expected_content = "This is the first paragraph that should be folded into a single line when processed by a YAML parser.\nThis is a second paragraph that should also be folded but kept separate from the first paragraph.\nThis is a third paragraph after multiple blank lines.\nFinal paragraph.\n";
3793        assert_eq!(
3794            description.as_scalar().unwrap().as_string(),
3795            expected_content
3796        );
3797
3798        let output = parsed.to_string();
3799        assert_eq!(output, yaml);
3800    }
3801
3802    #[test]
3803    fn test_block_scalars_with_special_characters() {
3804        let yaml = r#"special_chars: |
3805  Line with colons: key: value
3806  Line with dashes - and more - dashes
3807  Line with quotes "double" and 'single'
3808  Line with brackets [array] and braces {object}
3809  Line with pipes | and greater than >
3810  Line with at @ and hash # symbols
3811  Line with percent % and exclamation !
3812
3813backslash_test: >
3814  This line has a backslash \ in it
3815  And this line has multiple \\ backslashes
3816
3817unicode_test: |
3818  This line has unicode: 你好世界
3819  And emojis: 🚀 🎉 ✨
3820"#;
3821        let parsed =
3822            YamlFile::from_str(yaml).expect("Should parse block scalars with special characters");
3823
3824        let doc = parsed.document().expect("Should have document");
3825        let mapping = doc.as_mapping().expect("Should be a mapping");
3826
3827        let special_chars = mapping
3828            .get("special_chars")
3829            .expect("Should have 'special_chars' key");
3830        assert_eq!(
3831            special_chars.as_scalar().unwrap().as_string(),
3832            "Line with colons: key: value\nLine with dashes - and more - dashes\nLine with quotes \"double\" and 'single'\nLine with brackets [array] and braces {object}\nLine with pipes | and greater than >\nLine with at @ and hash # symbols\nLine with percent % and exclamation !\n"
3833        );
3834
3835        let backslash_test = mapping
3836            .get("backslash_test")
3837            .expect("Should have 'backslash_test' key");
3838        assert_eq!(
3839            backslash_test.as_scalar().unwrap().as_string(),
3840            "This line has a backslash \\ in it And this line has multiple \\\\ backslashes\n"
3841        );
3842
3843        let unicode_test = mapping
3844            .get("unicode_test")
3845            .expect("Should have 'unicode_test' key");
3846        assert_eq!(
3847            unicode_test.as_scalar().unwrap().as_string(),
3848            "This line has unicode: 你好世界\nAnd emojis: 🚀 🎉 ✨\n"
3849        );
3850
3851        let output = parsed.to_string();
3852        assert_eq!(output, yaml);
3853    }
3854
3855    #[test]
3856    fn test_block_scalar_chomping_detailed() {
3857        // Test clip indicator (default - no explicit indicator)
3858        let yaml_clip = r#"clip: |
3859  Line 1
3860  Line 2
3861
3862"#;
3863        let parsed_clip =
3864            YamlFile::from_str(yaml_clip).expect("Should parse block scalar with default clipping");
3865
3866        // Verify API access - clip removes trailing newlines except one
3867        let doc_clip = parsed_clip.document().expect("Should have document");
3868        let mapping_clip = doc_clip.as_mapping().expect("Should be a mapping");
3869        assert_eq!(
3870            mapping_clip
3871                .get("clip")
3872                .unwrap()
3873                .as_scalar()
3874                .unwrap()
3875                .as_string(),
3876            "Line 1\nLine 2\n"
3877        );
3878
3879        assert_eq!(parsed_clip.to_string(), yaml_clip);
3880
3881        // Test strip indicator (-)
3882        let yaml_strip = r#"strip: |-
3883  Line 1
3884  Line 2
3885
3886
3887
3888"#;
3889        let parsed_strip =
3890            YamlFile::from_str(yaml_strip).expect("Should parse block scalar with strip indicator");
3891
3892        // Verify API access - strip removes all trailing newlines
3893        let doc_strip = parsed_strip.document().expect("Should have document");
3894        let mapping_strip = doc_strip.as_mapping().expect("Should be a mapping");
3895        assert_eq!(
3896            mapping_strip
3897                .get("strip")
3898                .unwrap()
3899                .as_scalar()
3900                .unwrap()
3901                .as_string(),
3902            "Line 1\nLine 2"
3903        );
3904
3905        assert_eq!(parsed_strip.to_string(), yaml_strip);
3906
3907        // Test keep indicator (+)
3908        let yaml_keep = r#"keep: |+
3909  Line 1
3910  Line 2
3911
3912
3913
3914"#;
3915        let parsed_keep =
3916            YamlFile::from_str(yaml_keep).expect("Should parse block scalar with keep indicator");
3917
3918        // Verify API access - keep preserves all trailing newlines
3919        let doc_keep = parsed_keep.document().expect("Should have document");
3920        let mapping_keep = doc_keep.as_mapping().expect("Should be a mapping");
3921        assert_eq!(
3922            mapping_keep
3923                .get("keep")
3924                .unwrap()
3925                .as_scalar()
3926                .unwrap()
3927                .as_string(),
3928            "Line 1\nLine 2\n\n\n\n"
3929        );
3930
3931        assert_eq!(parsed_keep.to_string(), yaml_keep);
3932    }
3933
3934    #[test]
3935    fn test_block_scalar_explicit_indentation_detailed() {
3936        // Test individual cases to isolate the issue
3937        let yaml1 = r#"indent1: |1
3938 Single space indent
3939"#;
3940        let parsed1 = YamlFile::from_str(yaml1);
3941        assert!(parsed1.is_ok(), "Should parse |1 block scalar");
3942        let output1 = parsed1.unwrap().to_string();
3943        assert_eq!(output1, yaml1);
3944
3945        let yaml2 = r#"indent2: |2
3946  Two space indent
3947"#;
3948        let parsed2 = YamlFile::from_str(yaml2);
3949        assert!(parsed2.is_ok(), "Should parse |2 block scalar");
3950        let output2 = parsed2.unwrap().to_string();
3951        assert_eq!(output2, yaml2);
3952
3953        let yaml3 = r#"folded_indent: >2
3954  Two space folded
3955  content spans lines
3956"#;
3957        let parsed3 = YamlFile::from_str(yaml3);
3958        assert!(parsed3.is_ok(), "Should parse >2 folded block scalar");
3959        let output3 = parsed3.unwrap().to_string();
3960        assert_eq!(output3, yaml3);
3961    }
3962
3963    #[test]
3964    fn test_block_scalar_combined_indicators() {
3965        let yaml = r#"strip_with_indent: |2-
3966  Content with explicit indent
3967  and strip chomping
3968
3969
3970keep_with_indent: >3+
3971   Content with explicit indent
3972   and keep chomping
3973
3974
3975
3976folded_strip: >-
3977  Folded content
3978  with strip indicator
3979
3980literal_keep: |+
3981  Literal content
3982  with keep indicator
3983
3984
3985"#;
3986        let parsed =
3987            YamlFile::from_str(yaml).expect("Should parse block scalars with combined indicators");
3988
3989        let doc = parsed.document().expect("Should have document");
3990        let mapping = doc.as_mapping().expect("Should be a mapping");
3991
3992        assert_eq!(
3993            mapping
3994                .get("strip_with_indent")
3995                .unwrap()
3996                .as_scalar()
3997                .unwrap()
3998                .as_string(),
3999            "Content with explicit indent\nand strip chomping"
4000        );
4001        assert_eq!(
4002            mapping
4003                .get("keep_with_indent")
4004                .unwrap()
4005                .as_scalar()
4006                .unwrap()
4007                .as_string(),
4008            "Content with explicit indent and keep chomping\n\n\n\n"
4009        );
4010        assert_eq!(
4011            mapping
4012                .get("folded_strip")
4013                .unwrap()
4014                .as_scalar()
4015                .unwrap()
4016                .as_string(),
4017            "Folded content with strip indicator"
4018        );
4019        assert_eq!(
4020            mapping
4021                .get("literal_keep")
4022                .unwrap()
4023                .as_scalar()
4024                .unwrap()
4025                .as_string(),
4026            "Literal content\nwith keep indicator\n\n\n"
4027        );
4028
4029        let output = parsed.to_string();
4030        assert_eq!(output, yaml);
4031    }
4032
4033    #[test]
4034    fn test_block_scalar_whitespace_and_empty() {
4035        // Block scalar with only whitespace lines
4036        let yaml1 = r#"whitespace_only: |
4037
4038
4039
4040"#;
4041        let parsed1 =
4042            YamlFile::from_str(yaml1).expect("Should handle block scalar with only whitespace");
4043
4044        let doc1 = parsed1.document().expect("Should have document");
4045        let mapping1 = doc1.as_mapping().expect("Should be a mapping");
4046        assert_eq!(
4047            mapping1
4048                .get("whitespace_only")
4049                .unwrap()
4050                .as_scalar()
4051                .unwrap()
4052                .as_string(),
4053            "\n"
4054        );
4055
4056        assert_eq!(parsed1.to_string(), yaml1);
4057
4058        // Block scalar with mixed indentation
4059        let yaml2 = r#"mixed_indent: |
4060  Normal line
4061    Indented line
4062  Back to normal
4063      More indented
4064  Normal again
4065"#;
4066        let parsed2 = YamlFile::from_str(yaml2).expect("Should handle mixed indentation levels");
4067
4068        let doc2 = parsed2.document().expect("Should have document");
4069        let mapping2 = doc2.as_mapping().expect("Should be a mapping");
4070        assert_eq!(
4071            mapping2
4072                .get("mixed_indent")
4073                .unwrap()
4074                .as_scalar()
4075                .unwrap()
4076                .as_string(),
4077            "Normal line\n  Indented line\nBack to normal\n    More indented\nNormal again\n"
4078        );
4079
4080        assert_eq!(parsed2.to_string(), yaml2);
4081
4082        // Block scalar followed immediately by another mapping
4083        let yaml3 = r#"first: |
4084  Content
4085immediate: value
4086another: |
4087  More content
4088final: end
4089"#;
4090        let parsed3 =
4091            YamlFile::from_str(yaml3).expect("Should handle multiple block scalars in mapping");
4092
4093        let doc3 = parsed3.document().expect("Should have document");
4094        let mapping3 = doc3.as_mapping().expect("Should be a mapping");
4095        assert_eq!(
4096            mapping3
4097                .get("first")
4098                .unwrap()
4099                .as_scalar()
4100                .unwrap()
4101                .as_string(),
4102            "Content\n"
4103        );
4104        assert_eq!(
4105            mapping3
4106                .get("immediate")
4107                .unwrap()
4108                .as_scalar()
4109                .unwrap()
4110                .as_string(),
4111            "value"
4112        );
4113        assert_eq!(
4114            mapping3
4115                .get("another")
4116                .unwrap()
4117                .as_scalar()
4118                .unwrap()
4119                .as_string(),
4120            "More content\n"
4121        );
4122        assert_eq!(
4123            mapping3
4124                .get("final")
4125                .unwrap()
4126                .as_scalar()
4127                .unwrap()
4128                .as_string(),
4129            "end"
4130        );
4131
4132        let output3 = parsed3.to_string();
4133        assert_eq!(output3, yaml3);
4134    }
4135
4136    #[test]
4137    fn test_block_scalar_with_comments() {
4138        let yaml = r#"# Main configuration
4139config: |  # This is a literal block
4140  # This comment is inside the block
4141  line1: value1
4142  # Another internal comment
4143  line2: value2
4144
4145# Outside comment
4146other: >  # Folded block comment
4147  This content spans
4148  # This hash is part of the content, not a comment
4149  multiple lines
4150"#;
4151        let parsed = YamlFile::from_str(yaml).expect("Should parse block scalars with comments");
4152
4153        let doc = parsed.document().expect("Should have document");
4154        let mapping = doc.as_mapping().expect("Should be a mapping");
4155
4156        // The config block scalar includes content until it hits a less-indented line.
4157        // The "# Outside comment" line at column 0 is currently still consumed as
4158        // part of the block scalar text (a separate parser-level limitation), but
4159        // `as_string()` now preserves its bytes rather than blindly chopping
4160        // `base_indent` bytes off the start (which had been mangling multi-byte
4161        // characters and dropping the leading `#`).
4162        assert_eq!(
4163            mapping.get("config").unwrap().as_scalar().unwrap().as_string(),
4164            "# This comment is inside the block\nline1: value1\n# Another internal comment\nline2: value2\n\n# Outside comment\n"
4165        );
4166
4167        assert_eq!(
4168            mapping
4169                .get("other")
4170                .unwrap()
4171                .as_scalar()
4172                .unwrap()
4173                .as_string(),
4174            "This content spans # This hash is part of the content, not a comment multiple lines\n"
4175        );
4176
4177        let output = parsed.to_string();
4178        assert_eq!(output, yaml);
4179    }
4180
4181    #[test]
4182    fn test_block_scalar_empty_and_minimal() {
4183        let yaml = r#"empty_literal: |
4184
4185empty_folded: >
4186
4187minimal_literal: |
4188  x
4189
4190minimal_folded: >
4191  y
4192
4193just_newlines: |
4194
4195
4196
4197just_spaces: |
4198
4199
4200
4201"#;
4202        let parsed =
4203            YamlFile::from_str(yaml).expect("Should handle empty and minimal block scalars");
4204
4205        let doc = parsed.document().expect("Should have document");
4206        let mapping = doc.as_mapping().expect("Should be a mapping");
4207
4208        assert_eq!(
4209            mapping
4210                .get("empty_literal")
4211                .unwrap()
4212                .as_scalar()
4213                .unwrap()
4214                .as_string(),
4215            "\n"
4216        );
4217        assert_eq!(
4218            mapping
4219                .get("empty_folded")
4220                .unwrap()
4221                .as_scalar()
4222                .unwrap()
4223                .as_string(),
4224            "\n"
4225        );
4226        assert_eq!(
4227            mapping
4228                .get("minimal_literal")
4229                .unwrap()
4230                .as_scalar()
4231                .unwrap()
4232                .as_string(),
4233            "x\n"
4234        );
4235        assert_eq!(
4236            mapping
4237                .get("minimal_folded")
4238                .unwrap()
4239                .as_scalar()
4240                .unwrap()
4241                .as_string(),
4242            "y\n"
4243        );
4244        assert_eq!(
4245            mapping
4246                .get("just_newlines")
4247                .unwrap()
4248                .as_scalar()
4249                .unwrap()
4250                .as_string(),
4251            "\n"
4252        );
4253        assert_eq!(
4254            mapping
4255                .get("just_spaces")
4256                .unwrap()
4257                .as_scalar()
4258                .unwrap()
4259                .as_string(),
4260            "\n"
4261        );
4262
4263        let output = parsed.to_string();
4264        assert_eq!(output, yaml);
4265    }
4266
4267    #[test]
4268    fn test_block_scalar_with_document_markers() {
4269        let yaml = r#"---
4270doc1: |
4271  This is the first document
4272  with a literal block scalar.
4273
4274next_key: value
4275---
4276doc2: >
4277  This is the second document
4278  with a folded block scalar.
4279
4280another_key: another_value
4281...
4282"#;
4283        let parsed =
4284            YamlFile::from_str(yaml).expect("Should parse block scalars with document markers");
4285
4286        // Verify API access - first document
4287        let doc = parsed.document().expect("Should have first document");
4288        let mapping = doc.as_mapping().expect("Should be a mapping");
4289
4290        assert_eq!(
4291            mapping
4292                .get("doc1")
4293                .unwrap()
4294                .as_scalar()
4295                .unwrap()
4296                .as_string(),
4297            "This is the first document\nwith a literal block scalar.\n"
4298        );
4299        assert_eq!(
4300            mapping
4301                .get("next_key")
4302                .unwrap()
4303                .as_scalar()
4304                .unwrap()
4305                .as_string(),
4306            "value"
4307        );
4308
4309        // Verify exact round-trip (preserves document markers and all documents)
4310        let output = parsed.to_string();
4311        assert_eq!(output, yaml);
4312    }
4313
4314    #[test]
4315    fn test_block_scalar_formatting_preservation() {
4316        let original = r#"preserve_me: |
4317  Line with    multiple    spaces
4318  Line with	tabs	here
4319  Line with trailing spaces
4320
4321  Empty line above and below
4322
4323  Final line
4324"#;
4325        let parsed = YamlFile::from_str(original).expect("Should preserve exact formatting");
4326
4327        let doc = parsed.document().expect("Should have document");
4328        let mapping = doc.as_mapping().expect("Should be a mapping");
4329
4330        let preserve_me = mapping
4331            .get("preserve_me")
4332            .expect("Should have 'preserve_me' key");
4333        let expected = "Line with    multiple    spaces\nLine with\ttabs\there\nLine with trailing spaces\n\nEmpty line above and below\n\nFinal line\n";
4334        assert_eq!(preserve_me.as_scalar().unwrap().as_string(), expected);
4335
4336        // Verify exact round-trip (the output should be identical to input - lossless)
4337        let output = parsed.to_string();
4338        assert_eq!(output, original);
4339    }
4340
4341    #[test]
4342    fn test_block_scalar_complex_yaml_content() {
4343        let yaml = r#"yaml_content: |
4344  # This block contains YAML-like content
4345  nested:
4346    - item: value
4347    - item: another
4348
4349  mapping:
4350    key1: |
4351      Even more nested literal content
4352    key2: value
4353
4354  anchors: &anchor
4355    anchor_content: data
4356
4357  reference: *anchor
4358
4359quoted_yaml: >
4360  This folded block contains
4361  YAML structures: {key: value, array: [1, 2, 3]}
4362  that should be treated as plain text.
4363"#;
4364        let parsed = YamlFile::from_str(yaml)
4365            .expect("Should parse block scalars containing YAML-like structures");
4366
4367        let doc = parsed.document().expect("Should have document");
4368        let mapping = doc.as_mapping().expect("Should be a mapping");
4369
4370        let expected_yaml_content = "# This block contains YAML-like content\nnested:\n  - item: value\n  - item: another\n\nmapping:\n  key1: |\n    Even more nested literal content\n  key2: value\n\nanchors: &anchor\n  anchor_content: data\n\nreference: *anchor\n";
4371        assert_eq!(
4372            mapping
4373                .get("yaml_content")
4374                .unwrap()
4375                .as_scalar()
4376                .unwrap()
4377                .as_string(),
4378            expected_yaml_content
4379        );
4380
4381        let expected_quoted_yaml = "This folded block contains YAML structures: {key: value, array: [1, 2, 3]} that should be treated as plain text.\n";
4382        assert_eq!(
4383            mapping
4384                .get("quoted_yaml")
4385                .unwrap()
4386                .as_scalar()
4387                .unwrap()
4388                .as_string(),
4389            expected_quoted_yaml
4390        );
4391
4392        let output = parsed.to_string();
4393        assert_eq!(output, yaml);
4394    }
4395
4396    #[test]
4397    fn test_block_scalar_performance_large_content() {
4398        // Test with a reasonably large block scalar
4399        let mut large_content = String::new();
4400        for i in 1..=100 {
4401            large_content.push_str(&format!(
4402                "  Line number {} with some content that makes it longer\n",
4403                i
4404            ));
4405        }
4406
4407        let yaml = format!(
4408            "large_literal: |\n{}\nlarge_folded: >\n{}\n",
4409            large_content, large_content
4410        );
4411
4412        let parsed =
4413            YamlFile::from_str(&yaml).expect("Should parse large block scalars without errors");
4414
4415        let doc = parsed.document().expect("Should have document");
4416        let mapping = doc.as_mapping().expect("Should be a mapping");
4417
4418        let literal_value = mapping
4419            .get("large_literal")
4420            .expect("Should have large_literal key")
4421            .as_scalar()
4422            .expect("Should be scalar")
4423            .as_string();
4424
4425        // Build expected literal content (literal preserves newlines exactly)
4426        let mut expected_literal = String::new();
4427        for i in 1..=100 {
4428            expected_literal.push_str(&format!(
4429                "Line number {} with some content that makes it longer\n",
4430                i
4431            ));
4432        }
4433        assert_eq!(literal_value, expected_literal);
4434
4435        let folded_value = mapping
4436            .get("large_folded")
4437            .expect("Should have large_folded key")
4438            .as_scalar()
4439            .expect("Should be scalar")
4440            .as_string();
4441
4442        // Build expected folded content (folded folds lines into spaces, preserves double newlines)
4443        let mut expected_folded = String::new();
4444        for i in 1..=100 {
4445            if i > 1 {
4446                expected_folded.push(' ');
4447            }
4448            expected_folded.push_str(&format!(
4449                "Line number {} with some content that makes it longer",
4450                i
4451            ));
4452        }
4453        expected_folded.push('\n');
4454        assert_eq!(folded_value, expected_folded);
4455
4456        let output = parsed.to_string();
4457        assert_eq!(output, yaml);
4458    }
4459
4460    #[test]
4461    fn test_block_scalar_error_recovery() {
4462        // Test block scalar followed by another key at same indentation level
4463        let yaml = r#"good_key: value
4464bad_block: |
4465incomplete_key
4466another_good: works
4467"#;
4468        let parsed = YamlFile::from_str(yaml).expect("Should parse");
4469
4470        let doc = parsed.document().expect("Should have document");
4471        let mapping = doc.as_mapping().expect("Should be a mapping");
4472
4473        // Check that all keys are accessible
4474        assert_eq!(
4475            mapping
4476                .get("good_key")
4477                .unwrap()
4478                .as_scalar()
4479                .unwrap()
4480                .as_string(),
4481            "value"
4482        );
4483
4484        // bad_block contains the indented line "incomplete_key"
4485        assert_eq!(
4486            mapping
4487                .get("bad_block")
4488                .unwrap()
4489                .as_scalar()
4490                .unwrap()
4491                .as_string(),
4492            "incomplete_key\n"
4493        );
4494
4495        // another_good is a separate key (not part of bad_block)
4496        assert_eq!(
4497            mapping
4498                .get("another_good")
4499                .unwrap()
4500                .as_scalar()
4501                .unwrap()
4502                .as_string(),
4503            "works"
4504        );
4505
4506        let output = parsed.to_string();
4507        assert_eq!(output, yaml);
4508    }
4509
4510    #[test]
4511    fn test_block_scalar_with_flow_structures() {
4512        let yaml = r#"mixed_styles: |
4513  This literal block contains:
4514  - A flow sequence: [1, 2, 3]
4515  - A flow mapping: {key: value, other: data}
4516  - Mixed content: [a, {nested: true}, c]
4517
4518flow_then_block:
4519  flow_seq: [item1, item2]
4520  block_literal: |
4521    This comes after flow style
4522    and should work fine.
4523  flow_map: {after: block}
4524"#;
4525        let parsed = YamlFile::from_str(yaml).expect("Should parse mixed flow and block styles");
4526
4527        let doc = parsed.document().expect("Should have document");
4528        let mapping = doc.as_mapping().expect("Should be a mapping");
4529
4530        // Verify mixed_styles is a literal block containing flow-like text
4531        let expected_mixed = "This literal block contains:\n- A flow sequence: [1, 2, 3]\n- A flow mapping: {key: value, other: data}\n- Mixed content: [a, {nested: true}, c]\n";
4532        assert_eq!(
4533            mapping
4534                .get("mixed_styles")
4535                .unwrap()
4536                .as_scalar()
4537                .unwrap()
4538                .as_string(),
4539            expected_mixed
4540        );
4541
4542        // Verify flow_then_block is a mapping
4543        let flow_then_block_value = mapping.get("flow_then_block").unwrap();
4544        let flow_then_block = flow_then_block_value.as_mapping().unwrap();
4545
4546        // Verify flow_seq is a sequence
4547        let flow_seq_value = flow_then_block.get("flow_seq").unwrap();
4548        let flow_seq = flow_seq_value.as_sequence().unwrap();
4549        assert_eq!(flow_seq.len(), 2);
4550        assert_eq!(
4551            flow_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
4552            "item1"
4553        );
4554        assert_eq!(
4555            flow_seq.get(1).unwrap().as_scalar().unwrap().as_string(),
4556            "item2"
4557        );
4558
4559        // Verify block_literal is a block scalar
4560        assert_eq!(
4561            flow_then_block
4562                .get("block_literal")
4563                .unwrap()
4564                .as_scalar()
4565                .unwrap()
4566                .as_string(),
4567            "This comes after flow style\nand should work fine.\n"
4568        );
4569
4570        // Verify flow_map is a mapping
4571        let flow_map_value = flow_then_block.get("flow_map").unwrap();
4572        let flow_map = flow_map_value.as_mapping().unwrap();
4573        assert_eq!(
4574            flow_map
4575                .get("after")
4576                .unwrap()
4577                .as_scalar()
4578                .unwrap()
4579                .as_string(),
4580            "block"
4581        );
4582
4583        let output = parsed.to_string();
4584        assert_eq!(output, yaml);
4585    }
4586
4587    #[test]
4588    fn test_block_scalar_indentation_edge_cases() {
4589        // Test with no content after block indicator
4590        let yaml1 = r#"empty: |
4591next: value"#;
4592        let parsed1 = YamlFile::from_str(yaml1);
4593        assert!(parsed1.is_ok(), "Should handle empty block followed by key");
4594
4595        // Test with inconsistent indentation that should still work
4596        let yaml2 = r#"inconsistent: |
4597  normal indent
4598    more indent  
4599  back to normal
4600      even more
4601  normal
4602"#;
4603        let parsed2 = YamlFile::from_str(yaml2);
4604        assert!(
4605            parsed2.is_ok(),
4606            "Should handle inconsistent but valid indentation"
4607        );
4608
4609        // Test with tab characters (should work in block scalars)
4610        let yaml3 = "tabs: |\n\tTab indented line\n\tAnother tab line\n";
4611        let parsed3 = YamlFile::from_str(yaml3);
4612        assert!(
4613            parsed3.is_ok(),
4614            "Should handle tab characters in block scalars"
4615        );
4616    }
4617
4618    #[test]
4619    fn test_block_scalar_with_anchors_and_aliases() {
4620        let yaml = r#"template: &template |
4621  This is a template
4622  with multiple lines
4623  that can be referenced.
4624
4625instance1: *template
4626
4627instance2:
4628  content: *template
4629  other: value
4630
4631modified: |
4632  <<: *template
4633  Additional content here
4634"#;
4635        let parsed =
4636            YamlFile::from_str(yaml).expect("Should parse block scalars with anchors and aliases");
4637
4638        let doc = parsed.document().expect("Should have document");
4639        let mapping = doc.as_mapping().expect("Should be a mapping");
4640
4641        // Verify template is a block scalar with anchor (anchor markup is not in string value)
4642        let expected_template =
4643            "This is a template\nwith multiple lines\nthat can be referenced.\n";
4644        let template_value = mapping.get("template").unwrap();
4645        assert_eq!(
4646            template_value.as_scalar().unwrap().as_string(),
4647            expected_template
4648        );
4649
4650        // Verify instance1 is an alias (not a scalar) - use API to retrieve alias info
4651        let instance1 = mapping.get("instance1").unwrap();
4652        assert!(
4653            instance1.is_alias(),
4654            "instance1 should be an alias, not a scalar"
4655        );
4656        assert_eq!(instance1.as_alias().unwrap().name(), "template");
4657
4658        // Verify instance2 is a mapping
4659        let instance2_value = mapping.get("instance2").unwrap();
4660        let instance2 = instance2_value.as_mapping().unwrap();
4661
4662        // Verify instance2.content is an alias (not a scalar)
4663        let content = instance2.get("content").unwrap();
4664        assert!(
4665            content.is_alias(),
4666            "content should be an alias, not a scalar"
4667        );
4668        assert_eq!(content.as_alias().unwrap().name(), "template");
4669
4670        // Verify instance2.other is a regular scalar
4671        assert_eq!(
4672            instance2
4673                .get("other")
4674                .unwrap()
4675                .as_scalar()
4676                .unwrap()
4677                .as_string(),
4678            "value"
4679        );
4680
4681        // Verify modified is a literal block scalar containing text that looks like YAML
4682        // (the <<: and *template are plain text, not actual merge keys/aliases)
4683        let modified = mapping.get("modified").unwrap();
4684        assert!(
4685            modified.is_scalar(),
4686            "modified should be a scalar, not an alias"
4687        );
4688        assert_eq!(
4689            modified.as_scalar().unwrap().as_string(),
4690            "<<: *template\nAdditional content here\n"
4691        );
4692
4693        let output = parsed.to_string();
4694        assert_eq!(output, yaml);
4695    }
4696
4697    #[test]
4698    fn test_block_scalar_newline_variations() {
4699        // Test with different newline styles
4700        let yaml_unix = "unix: |\n  Line 1\n  Line 2\n";
4701        let parsed_unix = YamlFile::from_str(yaml_unix).expect("Should handle Unix newlines");
4702
4703        let yaml_windows = "windows: |\r\n  Line 1\r\n  Line 2\r\n";
4704        let parsed_windows =
4705            YamlFile::from_str(yaml_windows).expect("Should handle Windows newlines");
4706
4707        // Verify API access for unix
4708        let doc_unix = parsed_unix.document().expect("Should have document");
4709        let mapping_unix = doc_unix.as_mapping().expect("Should be a mapping");
4710        assert_eq!(
4711            mapping_unix
4712                .get("unix")
4713                .unwrap()
4714                .as_scalar()
4715                .unwrap()
4716                .as_string(),
4717            "Line 1\nLine 2\n"
4718        );
4719
4720        // Verify API access for windows
4721        let doc_windows = parsed_windows.document().expect("Should have document");
4722        let mapping_windows = doc_windows.as_mapping().expect("Should be a mapping");
4723        assert_eq!(
4724            mapping_windows
4725                .get("windows")
4726                .unwrap()
4727                .as_scalar()
4728                .unwrap()
4729                .as_string(),
4730            "Line 1\nLine 2\n"
4731        );
4732
4733        assert_eq!(parsed_unix.to_string(), yaml_unix);
4734        assert_eq!(parsed_windows.to_string(), yaml_windows);
4735    }
4736
4737    #[test]
4738    fn test_block_scalar_boundary_detection() {
4739        // Test that block scalars properly end at mapping boundaries
4740        let yaml = r#"config:
4741  description: |
4742    This is a configuration
4743    with multiple lines.
4744
4745  name: "MyApp"
4746  version: 1.0
4747
4748  settings: >
4749    These are settings that
4750    span multiple lines too.
4751
4752  debug: true
4753"#;
4754        let parsed =
4755            YamlFile::from_str(yaml).expect("Should properly detect block scalar boundaries");
4756
4757        let doc = parsed.document().expect("Should have document");
4758        let mapping = doc.as_mapping().expect("Should be a mapping");
4759        let config_value = mapping.get("config").unwrap();
4760        let config = config_value.as_mapping().unwrap();
4761
4762        // Verify description is a literal block scalar
4763        assert_eq!(
4764            config
4765                .get("description")
4766                .unwrap()
4767                .as_scalar()
4768                .unwrap()
4769                .as_string(),
4770            "This is a configuration\nwith multiple lines.\n"
4771        );
4772
4773        // Verify name is a regular quoted scalar
4774        assert_eq!(
4775            config.get("name").unwrap().as_scalar().unwrap().as_string(),
4776            "MyApp"
4777        );
4778
4779        // Verify version is a numeric scalar
4780        assert_eq!(
4781            config
4782                .get("version")
4783                .unwrap()
4784                .as_scalar()
4785                .unwrap()
4786                .as_string(),
4787            "1.0"
4788        );
4789
4790        // Verify settings is a folded block scalar
4791        assert_eq!(
4792            config
4793                .get("settings")
4794                .unwrap()
4795                .as_scalar()
4796                .unwrap()
4797                .as_string(),
4798            "These are settings that span multiple lines too.\n"
4799        );
4800
4801        // Verify debug is a boolean scalar
4802        assert_eq!(
4803            config
4804                .get("debug")
4805                .unwrap()
4806                .as_scalar()
4807                .unwrap()
4808                .as_string(),
4809            "true"
4810        );
4811
4812        let output = parsed.to_string();
4813        assert_eq!(output, yaml);
4814    }
4815
4816    #[test]
4817    fn test_block_scalar_with_numeric_content() {
4818        let yaml = r#"numbers_as_text: |
4819  123
4820  45.67
4821  -89
4822  +100
4823  0xFF
4824  1e5
4825  true
4826  false
4827  null
4828
4829calculations: >
4830  The result is: 2 + 2 = 4
4831  And 10 * 5 = 50
4832  Also: 100% complete
4833"#;
4834        let parsed = YamlFile::from_str(yaml)
4835            .expect("Should parse numeric content as text in block scalars");
4836
4837        let doc = parsed.document().expect("Should have document");
4838        let mapping = doc.as_mapping().expect("Should be a mapping");
4839
4840        // Verify numbers_as_text is a literal block containing numeric-looking text
4841        let expected_numbers = "123\n45.67\n-89\n+100\n0xFF\n1e5\ntrue\nfalse\nnull\n";
4842        assert_eq!(
4843            mapping
4844                .get("numbers_as_text")
4845                .unwrap()
4846                .as_scalar()
4847                .unwrap()
4848                .as_string(),
4849            expected_numbers
4850        );
4851
4852        // Verify calculations is a folded block containing calculations text
4853        let expected_calculations =
4854            "The result is: 2 + 2 = 4 And 10 * 5 = 50 Also: 100% complete\n";
4855        assert_eq!(
4856            mapping
4857                .get("calculations")
4858                .unwrap()
4859                .as_scalar()
4860                .unwrap()
4861                .as_string(),
4862            expected_calculations
4863        );
4864
4865        let output = parsed.to_string();
4866        assert_eq!(output, yaml);
4867    }
4868
4869    #[test]
4870    fn test_block_scalar_exact_preservation() {
4871        // Test that block scalars are preserved exactly as written (lossless)
4872        let test_cases = [
4873            // Simple literal block
4874            r#"simple: |
4875  Hello World
4876"#,
4877            // Simple folded block
4878            r#"folded: >
4879  Hello World
4880"#,
4881            // With chomping indicators
4882            r#"strip: |-
4883  Content
4884
4885keep: |+
4886  Content
4887
4888"#,
4889            // With explicit indentation
4890            r#"explicit: |2
4891  Two space indent
4892"#,
4893            // Complex real-world example
4894            r#"config:
4895  script: |
4896    #!/bin/bash
4897    echo "Starting deployment"
4898
4899    for service in api web worker; do
4900        echo "Deploying $service"
4901        kubectl apply -f $service.yaml
4902    done
4903
4904  description: >
4905    This configuration defines a deployment
4906    script that will be executed during
4907    the CI/CD pipeline.
4908"#,
4909        ];
4910
4911        for (i, yaml) in test_cases.iter().enumerate() {
4912            let parsed = YamlFile::from_str(yaml);
4913            assert!(parsed.is_ok(), "Test case {} should parse successfully", i);
4914
4915            let output = parsed.unwrap().to_string();
4916            assert_eq!(
4917                output, *yaml,
4918                "Test case {} should preserve exact formatting",
4919                i
4920            );
4921        }
4922    }
4923
4924    #[test]
4925    fn test_block_scalar_chomping_exact() {
4926        let yaml_strip = r#"strip: |-
4927  Content
4928"#;
4929        let parsed_strip = YamlFile::from_str(yaml_strip).unwrap();
4930        assert_eq!(parsed_strip.to_string(), yaml_strip);
4931
4932        let yaml_keep = r#"keep: |+
4933  Content
4934
4935"#;
4936        let parsed_keep = YamlFile::from_str(yaml_keep).unwrap();
4937        assert_eq!(parsed_keep.to_string(), yaml_keep);
4938
4939        let yaml_folded_strip = r#"folded: >-
4940  Content
4941"#;
4942        let parsed_folded_strip = YamlFile::from_str(yaml_folded_strip).unwrap();
4943        assert_eq!(parsed_folded_strip.to_string(), yaml_folded_strip);
4944    }
4945
4946    #[test]
4947    fn test_block_scalar_indentation_exact() {
4948        let yaml1 = r#"indent1: |1
4949 Single space
4950"#;
4951        let parsed1 = YamlFile::from_str(yaml1).unwrap();
4952        assert_eq!(parsed1.to_string(), yaml1);
4953
4954        let yaml2 = r#"indent2: |2
4955  Two spaces
4956"#;
4957        let parsed2 = YamlFile::from_str(yaml2).unwrap();
4958        assert_eq!(parsed2.to_string(), yaml2);
4959
4960        let yaml3 = r#"combined: |3+
4961   Content with keep
4962
4963"#;
4964        let parsed3 = YamlFile::from_str(yaml3).unwrap();
4965        assert_eq!(parsed3.to_string(), yaml3);
4966    }
4967
4968    #[test]
4969    fn test_block_scalar_mapping_exact() {
4970        let yaml = r#"description: |
4971  Line 1
4972  Line 2
4973
4974summary: >
4975  Folded content
4976
4977version: "1.0"
4978"#;
4979        let parsed = YamlFile::from_str(yaml).unwrap();
4980        assert_eq!(parsed.to_string(), yaml);
4981    }
4982
4983    #[test]
4984    fn test_block_scalar_sequence_exact() {
4985        let yaml = r#"items:
4986  - |
4987    First item content
4988    with multiple lines
4989  
4990  - >
4991    Second item folded
4992    content
4993  
4994  - regular_item
4995"#;
4996        let parsed = YamlFile::from_str(yaml).unwrap();
4997        assert_eq!(parsed.to_string(), yaml);
4998    }
4999
5000    #[test]
5001    fn test_block_scalar_empty_exact() {
5002        let yaml1 = r#"empty: |
5003
5004"#;
5005        let parsed1 = YamlFile::from_str(yaml1).unwrap();
5006        assert_eq!(parsed1.to_string(), yaml1);
5007
5008        let yaml2 = r#"empty_folded: >
5009
5010"#;
5011        let parsed2 = YamlFile::from_str(yaml2).unwrap();
5012        assert_eq!(parsed2.to_string(), yaml2);
5013    }
5014
5015    #[test]
5016    fn test_empty_documents_in_stream() {
5017        // Test empty documents in multi-document stream
5018        let yaml = "---\n---\nkey: value\n---\n...\n";
5019        let parsed = YamlFile::from_str(yaml).unwrap();
5020        assert_eq!(parsed.documents().count(), 3);
5021        assert_eq!(parsed.to_string(), yaml);
5022    }
5023
5024    #[test]
5025    fn test_mixed_document_end_markers() {
5026        // Test documents with mixed end marker usage
5027        let yaml = "---\nfirst: doc\n...\n---\nsecond: doc\n---\nthird: doc\n...\n";
5028        let parsed = YamlFile::from_str(yaml).unwrap();
5029        assert_eq!(parsed.documents().count(), 3);
5030        assert_eq!(parsed.to_string(), yaml);
5031    }
5032
5033    #[test]
5034    fn test_complex_document_stream() {
5035        let yaml = r#"%YAML 1.2
5036%TAG ! tag:example.com,2000:app/
5037---
5038template: &anchor
5039  key: !custom value
5040instance:
5041  <<: *anchor
5042  extra: data
5043...
5044%YAML 1.2
5045---
5046- item1
5047- item2: nested
5048...
5049---
5050literal: |
5051  Block content
5052  Multiple lines
5053folded: >
5054  Folded content
5055  on multiple lines
5056...
5057"#;
5058        let parsed = YamlFile::from_str(yaml).unwrap();
5059        assert_eq!(parsed.documents().count(), 3);
5060        assert_eq!(parsed.to_string(), yaml);
5061    }
5062
5063    #[test]
5064    fn test_number_format_parsing() {
5065        // Test binary numbers
5066        let yaml = YamlFile::from_str("value: 0b1010").unwrap();
5067        assert_eq!(yaml.to_string().trim(), "value: 0b1010");
5068
5069        let yaml = YamlFile::from_str("value: 0B1111").unwrap();
5070        assert_eq!(yaml.to_string().trim(), "value: 0B1111");
5071
5072        // Test modern octal numbers
5073        let yaml = YamlFile::from_str("value: 0o755").unwrap();
5074        assert_eq!(yaml.to_string().trim(), "value: 0o755");
5075
5076        let yaml = YamlFile::from_str("value: 0O644").unwrap();
5077        assert_eq!(yaml.to_string().trim(), "value: 0O644");
5078
5079        // Test with signs
5080        let yaml = YamlFile::from_str("value: -0b1010").unwrap();
5081        assert_eq!(yaml.to_string().trim(), "value: -0b1010");
5082
5083        let yaml = YamlFile::from_str("value: +0o755").unwrap();
5084        assert_eq!(yaml.to_string().trim(), "value: +0o755");
5085
5086        // Test legacy formats still work
5087        let yaml = YamlFile::from_str("value: 0755").unwrap();
5088        assert_eq!(yaml.to_string().trim(), "value: 0755");
5089
5090        let yaml = YamlFile::from_str("value: 0xFF").unwrap();
5091        assert_eq!(yaml.to_string().trim(), "value: 0xFF");
5092    }
5093
5094    #[test]
5095    fn test_invalid_number_formats_as_strings() {
5096        // Invalid formats should be preserved as strings
5097        let yaml = YamlFile::from_str("value: 0b2").unwrap();
5098        assert_eq!(yaml.to_string().trim(), "value: 0b2");
5099
5100        let yaml = YamlFile::from_str("value: 0o9").unwrap();
5101        assert_eq!(yaml.to_string().trim(), "value: 0o9");
5102
5103        let yaml = YamlFile::from_str("value: 0xGH").unwrap();
5104        assert_eq!(yaml.to_string().trim(), "value: 0xGH");
5105    }
5106
5107    #[test]
5108    fn test_number_formats_in_complex_structures() {
5109        let input = r#"
5110config:
5111  permissions: 0o755
5112  flags: 0b11010
5113  color: 0xFF00FF
5114  count: 42"#;
5115
5116        let yaml = YamlFile::from_str(input).unwrap();
5117
5118        let doc = yaml.document().expect("Should have document");
5119        let mapping = doc.as_mapping().expect("Should be a mapping");
5120        let config_value = mapping.get("config").unwrap();
5121        let config = config_value.as_mapping().unwrap();
5122
5123        assert_eq!(
5124            config
5125                .get("permissions")
5126                .unwrap()
5127                .as_scalar()
5128                .unwrap()
5129                .as_string(),
5130            "0o755"
5131        );
5132        assert_eq!(
5133            config
5134                .get("flags")
5135                .unwrap()
5136                .as_scalar()
5137                .unwrap()
5138                .as_string(),
5139            "0b11010"
5140        );
5141        assert_eq!(
5142            config
5143                .get("color")
5144                .unwrap()
5145                .as_scalar()
5146                .unwrap()
5147                .as_string(),
5148            "0xFF00FF"
5149        );
5150        assert_eq!(
5151            config
5152                .get("count")
5153                .unwrap()
5154                .as_scalar()
5155                .unwrap()
5156                .as_string(),
5157            "42"
5158        );
5159
5160        let output = yaml.to_string();
5161        assert_eq!(output, input);
5162    }
5163
5164    #[test]
5165    fn test_editing_operations() {
5166        // Test basic editing operations
5167        let yaml = YamlFile::from_str("name: old-name\nversion: 1.0.0").unwrap();
5168        if let Some(doc) = yaml.document() {
5169            doc.set("name", "new-name");
5170            doc.set("version", "2.0.0");
5171
5172            // Verify values can be retrieved via API
5173            assert_eq!(doc.get_string("name"), Some("new-name".to_string()));
5174            assert_eq!(doc.get_string("version"), Some("2.0.0".to_string()));
5175
5176            // Verify exact round-trip after edits
5177            let output = doc.to_string();
5178            assert_eq!(output, "name: new-name\nversion: 2.0.0");
5179        }
5180    }
5181
5182    #[test]
5183    fn test_timestamp_parsing_and_validation() {
5184        use crate::scalar::{ScalarType, ScalarValue};
5185
5186        // Test various timestamp formats are recognized as timestamps
5187        let test_cases = vec![
5188            ("2001-12-14 21:59:43.10 -5", true), // Space-separated with timezone
5189            ("2001-12-15T02:59:43.1Z", true),    // ISO 8601 with Z
5190            ("2002-12-14", true),                // Date only
5191            ("2001-12-14t21:59:43.10-05:00", true), // Lowercase t
5192            ("2001-12-14 21:59:43.10", true),    // No timezone
5193            ("2001-12-14T21:59:43", true),       // No fractional seconds
5194            ("not-a-timestamp", false),          // Invalid
5195            ("2001-13-14", false),               // Invalid month
5196            ("2001-12-32", false),               // Invalid day
5197        ];
5198
5199        for (timestamp_str, should_be_valid) in test_cases {
5200            let scalar = ScalarValue::parse(timestamp_str);
5201
5202            if should_be_valid {
5203                assert_eq!(
5204                    scalar.scalar_type(),
5205                    ScalarType::Timestamp,
5206                    "Failed to recognize '{}' as timestamp",
5207                    timestamp_str
5208                );
5209                assert!(scalar.is_timestamp());
5210
5211                // Verify it preserves the original format
5212                assert_eq!(scalar.value(), timestamp_str);
5213
5214                // Test YAML parsing preserves it
5215                let yaml = format!("timestamp: {}", timestamp_str);
5216                let parsed = YamlFile::from_str(&yaml).unwrap();
5217
5218                let doc = parsed.document().expect("Should have document");
5219                let mapping = doc.as_mapping().expect("Should be a mapping");
5220                assert_eq!(
5221                    mapping
5222                        .get("timestamp")
5223                        .unwrap()
5224                        .as_scalar()
5225                        .unwrap()
5226                        .as_string(),
5227                    timestamp_str,
5228                    "Timestamp '{}' not preserved",
5229                    timestamp_str
5230                );
5231
5232                let output = parsed.to_string();
5233                assert_eq!(output, yaml);
5234            } else {
5235                assert_ne!(
5236                    scalar.scalar_type(),
5237                    ScalarType::Timestamp,
5238                    "'{}' should not be recognized as timestamp",
5239                    timestamp_str
5240                );
5241            }
5242        }
5243
5244        // Test timestamp in different contexts
5245        let yaml_with_timestamps = r#"
5246created_at: 2001-12-14 21:59:43.10 -5
5247updated_at: 2001-12-15T02:59:43.1Z
5248date_only: 2002-12-14
5249timestamps_in_array:
5250  - 2001-12-14 21:59:43.10 -5
5251  - 2001-12-15T02:59:43.1Z
5252  - 2002-12-14"#;
5253
5254        let parsed = YamlFile::from_str(yaml_with_timestamps).unwrap();
5255
5256        let doc = parsed.document().expect("Should have document");
5257        let mapping = doc.as_mapping().expect("Should be a mapping");
5258        assert_eq!(
5259            mapping
5260                .get("created_at")
5261                .unwrap()
5262                .as_scalar()
5263                .unwrap()
5264                .as_string(),
5265            "2001-12-14 21:59:43.10 -5"
5266        );
5267        assert_eq!(
5268            mapping
5269                .get("updated_at")
5270                .unwrap()
5271                .as_scalar()
5272                .unwrap()
5273                .as_string(),
5274            "2001-12-15T02:59:43.1Z"
5275        );
5276        assert_eq!(
5277            mapping
5278                .get("date_only")
5279                .unwrap()
5280                .as_scalar()
5281                .unwrap()
5282                .as_string(),
5283            "2002-12-14"
5284        );
5285
5286        let array_value = mapping.get("timestamps_in_array").unwrap();
5287        let array = array_value.as_sequence().unwrap();
5288        assert_eq!(
5289            array.get(0).unwrap().as_scalar().unwrap().as_string(),
5290            "2001-12-14 21:59:43.10 -5"
5291        );
5292        assert_eq!(
5293            array.get(1).unwrap().as_scalar().unwrap().as_string(),
5294            "2001-12-15T02:59:43.1Z"
5295        );
5296        assert_eq!(
5297            array.get(2).unwrap().as_scalar().unwrap().as_string(),
5298            "2002-12-14"
5299        );
5300
5301        let output = parsed.to_string();
5302        assert_eq!(output, yaml_with_timestamps);
5303    }
5304
5305    #[test]
5306    fn test_regex_support_in_yaml() {
5307        use crate::scalar::{ScalarType, ScalarValue};
5308
5309        // Test 1: Parse YAML with regex tags (using simpler patterns)
5310        let yaml_with_regex = r#"
5311patterns:
5312  digits: !!regex '\d+'
5313  word: !!regex '\w+'
5314  simple: !!regex 'test'"#;
5315
5316        let parsed = YamlFile::from_str(yaml_with_regex).unwrap();
5317
5318        // Verify exact round-trip preserves all regex tags
5319        let output = parsed.to_string();
5320        assert_eq!(output, yaml_with_regex);
5321
5322        // Test 2: Verify regex scalars are correctly identified
5323        let regex_scalar = ScalarValue::regex(r"^\d{4}-\d{2}-\d{2}$");
5324        assert_eq!(regex_scalar.scalar_type(), ScalarType::Regex);
5325        assert!(regex_scalar.is_regex());
5326        assert_eq!(regex_scalar.value(), r"^\d{4}-\d{2}-\d{2}$");
5327        assert_eq!(
5328            regex_scalar.to_yaml_string(),
5329            r"!!regex ^\d{4}-\d{2}-\d{2}$"
5330        );
5331
5332        // Test 3: Round-trip parsing with API verification
5333        let yaml_simple = "pattern: !!regex '\\d+'";
5334        let parsed_simple = YamlFile::from_str(yaml_simple).unwrap();
5335
5336        let doc_simple = parsed_simple.document().expect("Should have document");
5337        let mapping_simple = doc_simple.as_mapping().expect("Should be a mapping");
5338        let pattern_value = mapping_simple
5339            .get("pattern")
5340            .expect("Should have pattern key");
5341
5342        // Verify it's a tagged node with the correct tag
5343        assert!(pattern_value.is_tagged(), "pattern should be a tagged node");
5344        let tagged = pattern_value.as_tagged().expect("Should be tagged");
5345        assert_eq!(tagged.tag(), Some("!!regex".to_string()));
5346        assert_eq!(tagged.as_string(), Some("\\d+".to_string()));
5347
5348        let output_simple = parsed_simple.to_string();
5349        assert_eq!(output_simple, yaml_simple);
5350
5351        // Test 4: Complex regex patterns
5352        let complex_regex = r#"validation: !!regex '^https?://(?:[-\w.])+(?:\:[0-9]+)?'"#;
5353        let parsed_complex = YamlFile::from_str(complex_regex).unwrap();
5354
5355        let doc_complex = parsed_complex.document().expect("Should have document");
5356        let mapping_complex = doc_complex.as_mapping().expect("Should be a mapping");
5357        let validation_value = mapping_complex
5358            .get("validation")
5359            .expect("Should have validation key");
5360
5361        assert!(
5362            validation_value.is_tagged(),
5363            "validation should be a tagged node"
5364        );
5365        let tagged_complex = validation_value.as_tagged().expect("Should be tagged");
5366        assert_eq!(tagged_complex.tag(), Some("!!regex".to_string()));
5367        assert_eq!(
5368            tagged_complex.as_string(),
5369            Some("^https?://(?:[-\\w.])+(?:\\:[0-9]+)?".to_string())
5370        );
5371
5372        let output_complex = parsed_complex.to_string();
5373        assert_eq!(output_complex, complex_regex);
5374    }
5375
5376    #[test]
5377    fn test_regex_in_different_contexts() {
5378        // Test 1: Regex in sequences
5379        let yaml_sequence = r#"
5380patterns:
5381  - !!regex '\d+'
5382  - !!regex '[a-z]+'
5383  - normal_string
5384  - !!regex '.*@.*\..*'
5385"#;
5386
5387        let parsed_seq = YamlFile::from_str(yaml_sequence).unwrap();
5388
5389        let doc_seq = parsed_seq.document().expect("Should have document");
5390        let mapping_seq = doc_seq.as_mapping().expect("Should be a mapping");
5391        let patterns_value = mapping_seq
5392            .get("patterns")
5393            .expect("Should have patterns key");
5394        let patterns = patterns_value.as_sequence().expect("Should be a sequence");
5395
5396        // Verify first item is tagged with !!regex
5397        assert!(patterns.get(0).unwrap().is_tagged());
5398        assert_eq!(
5399            patterns.get(0).unwrap().as_tagged().unwrap().tag(),
5400            Some("!!regex".to_string())
5401        );
5402
5403        // Verify second item is tagged with !!regex
5404        assert!(patterns.get(1).unwrap().is_tagged());
5405        assert_eq!(
5406            patterns.get(1).unwrap().as_tagged().unwrap().tag(),
5407            Some("!!regex".to_string())
5408        );
5409
5410        // Verify third item is NOT tagged (regular string)
5411        assert!(!patterns.get(2).unwrap().is_tagged());
5412        assert_eq!(
5413            patterns.get(2).unwrap().as_scalar().unwrap().as_string(),
5414            "normal_string"
5415        );
5416
5417        // Verify fourth item is tagged with !!regex
5418        assert!(patterns.get(3).unwrap().is_tagged());
5419        assert_eq!(
5420            patterns.get(3).unwrap().as_tagged().unwrap().tag(),
5421            Some("!!regex".to_string())
5422        );
5423
5424        let output_seq = parsed_seq.to_string();
5425        assert_eq!(output_seq, yaml_sequence);
5426
5427        // Test 2: Nested mappings with regex (using simple patterns)
5428        let yaml_nested = r#"
5429validation:
5430  email: !!regex '[^@]+@[^@]+\.[a-z]+'
5431  phone: !!regex '\d{3}-\d{3}-\d{4}'
5432  config:
5433    debug_pattern: !!regex 'DEBUG:.*'
5434    nested:
5435      deep_pattern: !!regex 'ERROR'
5436"#;
5437
5438        let parsed_nested = YamlFile::from_str(yaml_nested).unwrap();
5439        // Verify exact round-trip preserves all nested structure and tags
5440        let output_nested = parsed_nested.to_string();
5441        assert_eq!(output_nested, yaml_nested);
5442
5443        // Test 3: Mixed collections
5444        let yaml_mixed = r#"
5445mixed_collection:
5446  - name: "test"
5447    patterns: [!!regex '\d+', !!regex '\w+']
5448  - patterns:
5449      simple: !!regex 'test'
5450      complex: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5451"#;
5452
5453        let parsed_mixed = YamlFile::from_str(yaml_mixed).unwrap();
5454        // Verify exact round-trip preserves all mixed collections and tags
5455        let output_mixed = parsed_mixed.to_string();
5456        assert_eq!(output_mixed, yaml_mixed);
5457
5458        // Test 4: Flow style with regex
5459        let yaml_flow =
5460            r#"inline_patterns: {email: !!regex '[^@]+@[^@]+', phone: !!regex '\d{3}-\d{4}'}"#;
5461
5462        let parsed_flow = YamlFile::from_str(yaml_flow).unwrap();
5463        // Verify exact round-trip preserves flow style and tags
5464        let output_flow = parsed_flow.to_string();
5465        assert_eq!(output_flow, yaml_flow);
5466    }
5467
5468    #[test]
5469    fn test_regex_parsing_edge_cases() {
5470        // Test 1: Regex with various quote styles (step by step)
5471        // Test various quote styles
5472        let yaml_quotes = r#"
5473patterns:
5474  single_quoted: !!regex 'pattern with spaces'
5475  double_quoted: !!regex "pattern_without_escapes"
5476  unquoted: !!regex simple_pattern
5477"#;
5478
5479        let parsed_quotes = YamlFile::from_str(yaml_quotes).unwrap();
5480
5481        let output_quotes = parsed_quotes.to_string();
5482        assert_eq!(output_quotes, yaml_quotes);
5483
5484        // Test 2: Empty and whitespace patterns
5485        let yaml_empty = r#"
5486empty: !!regex ''
5487whitespace: !!regex '   '
5488tabs: !!regex '	'
5489"#;
5490
5491        let parsed_empty =
5492            YamlFile::from_str(yaml_empty).expect("Should parse empty/whitespace regex patterns");
5493
5494        let output_empty = parsed_empty.to_string();
5495        assert_eq!(output_empty, yaml_empty);
5496
5497        // Test 3: Regex with special characters (avoiding YAML conflicts)
5498        let yaml_special = r#"special: !!regex 'pattern_with_underscores_and_123'"#;
5499
5500        let parsed_special = YamlFile::from_str(yaml_special)
5501            .expect("Should parse regex with safe special characters");
5502
5503        let output_special = parsed_special.to_string();
5504        assert_eq!(output_special, yaml_special);
5505
5506        // Test 4: Verify regex scalars maintain their properties after parsing
5507        let yaml_verify = r#"test_pattern: !!regex '\d{4}-\d{2}-\d{2}'"#;
5508        let parsed_verify = YamlFile::from_str(yaml_verify).unwrap();
5509
5510        let output_verify = parsed_verify.to_string();
5511        assert_eq!(output_verify, yaml_verify);
5512
5513        // Test 5: Multiple regex patterns in one document
5514        let yaml_multiple = r#"
5515patterns:
5516  email: !!regex '^[^\s@]+@[^\s@]+\.[^\s@]+$'
5517  phone: !!regex '^\+?[\d\s\-\(\)]{10,}$'
5518  url: !!regex '^https?://[^\s]+$'
5519  ipv4: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5520  uuid: !!regex '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
5521"#;
5522
5523        let parsed_multiple =
5524            YamlFile::from_str(yaml_multiple).expect("Should parse multiple regex patterns");
5525
5526        // Verify exact round-trip preserves all patterns and tags
5527        let output_multiple = parsed_multiple.to_string();
5528        assert_eq!(output_multiple, yaml_multiple);
5529    }
5530
5531    #[test]
5532    fn test_enhanced_comment_support() {
5533        // Test improvements: mid-line comments, comments in flow collections,
5534        // and better comment positioning preservation
5535
5536        // Test 1: Comments in flow sequences
5537        let yaml1 = r#"flow_seq: [
5538    item1, # comment after item1
5539    item2, # comment after item2
5540    item3  # comment after item3
5541]"#;
5542        let parsed1 = YamlFile::from_str(yaml1).unwrap();
5543        let output1 = parsed1.to_string();
5544
5545        assert_eq!(output1, yaml1);
5546
5547        // Test 2: Comments in flow mappings
5548        let yaml2 = r#"flow_map: {
5549    key1: val1, # comment after first pair
5550    key2: val2, # comment after second pair
5551    key3: val3  # comment after third pair
5552}"#;
5553        let parsed2 = YamlFile::from_str(yaml2).unwrap();
5554        let output2 = parsed2.to_string();
5555
5556        assert_eq!(output2, yaml2);
5557
5558        // Test 3: Mixed nested structures with comments
5559        let yaml3 = r#"config:
5560  servers: [
5561    {name: web1, port: 80},   # Web server 1
5562    {name: web2, port: 80},   # Web server 2
5563    {name: db1, port: 5432}   # Database server
5564  ] # End servers array"#;
5565        let parsed3 = YamlFile::from_str(yaml3).unwrap();
5566        let output3 = parsed3.to_string();
5567
5568        assert_eq!(output3, yaml3);
5569
5570        // Test 4: Comments between sequence items (block style)
5571        let yaml4 = r#"items:
5572  - first   # First item comment
5573  - second  # Second item comment
5574  # Comment between items
5575  - third   # Third item comment"#;
5576        let parsed4 = YamlFile::from_str(yaml4).unwrap();
5577        let output4 = parsed4.to_string();
5578
5579        assert_eq!(output4, yaml4);
5580
5581        // Test 5: Round-trip preservation (verify all can be reparsed)
5582        for yaml in [yaml1, yaml2, yaml3, yaml4] {
5583            let parsed = YamlFile::from_str(yaml).unwrap();
5584            let output = parsed.to_string();
5585            let reparsed = YamlFile::from_str(&output);
5586            assert!(reparsed.is_ok(), "Round-trip parsing should succeed");
5587        }
5588    }
5589
5590    #[test]
5591    fn test_insert_after_with_sequence() {
5592        let yaml = "name: project\nversion: 1.0.0";
5593        let parsed = YamlFile::from_str(yaml).unwrap();
5594        let doc = parsed.document().expect("Should have a document");
5595
5596        // Insert a sequence after "name"
5597        let features = SequenceBuilder::new()
5598            .item("feature1")
5599            .item("feature2")
5600            .item("feature3")
5601            .build_document()
5602            .as_sequence()
5603            .unwrap();
5604        let success = doc.insert_after("name", "features", features);
5605        assert!(success, "insert_after should succeed");
5606
5607        let output = doc.to_string();
5608
5609        // Verify exact output - standard block-style sequence format
5610        let expected = r#"name: project
5611features:
5612  - feature1
5613  - feature2
5614  - feature3
5615version: 1.0.0"#;
5616        assert_eq!(output.trim(), expected);
5617
5618        let reparsed = YamlFile::from_str(&output);
5619        assert!(reparsed.is_ok(), "Output should be valid YAML");
5620    }
5621
5622    #[test]
5623    fn test_insert_before_with_mapping() {
5624        let yaml = "name: project\nversion: 1.0.0";
5625        let parsed = YamlFile::from_str(yaml).unwrap();
5626        let doc = parsed.document().expect("Should have a document");
5627
5628        // Insert a nested mapping before "version"
5629        let database = MappingBuilder::new()
5630            .pair("host", "localhost")
5631            .pair("port", 5432)
5632            .pair("database", "mydb")
5633            .build_document()
5634            .as_mapping()
5635            .unwrap();
5636        let success = doc.insert_before("version", "database", database);
5637        assert!(success, "insert_before should succeed");
5638
5639        let output = doc.to_string();
5640
5641        // Verify exact output with proper structure and order
5642        let expected = r#"name: project
5643database:
5644  host: localhost
5645  port: 5432
5646  database: mydb
5647version: 1.0.0"#;
5648        assert_eq!(output.trim(), expected);
5649
5650        // Verify it's valid YAML and values are accessible
5651        let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5652        let reparsed_doc = reparsed.document().expect("Should have document");
5653        let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5654
5655        let db_value = reparsed_mapping
5656            .get("database")
5657            .expect("Should have database key");
5658        let db_mapping = db_value.as_mapping().expect("database should be mapping");
5659        assert_eq!(
5660            db_mapping
5661                .get("host")
5662                .unwrap()
5663                .as_scalar()
5664                .unwrap()
5665                .as_string(),
5666            "localhost"
5667        );
5668        assert_eq!(
5669            db_mapping
5670                .get("port")
5671                .unwrap()
5672                .as_scalar()
5673                .unwrap()
5674                .as_string(),
5675            "5432"
5676        );
5677    }
5678
5679    #[test]
5680    fn test_insert_at_index_with_mixed_types() {
5681        let yaml = "name: project";
5682        let parsed = YamlFile::from_str(yaml).unwrap();
5683        let doc = parsed.document().expect("Should have a document");
5684
5685        // Insert different types at various indices
5686        doc.insert_at_index(1, "version", "1.0.0");
5687        doc.insert_at_index(2, "active", true);
5688        doc.insert_at_index(3, "count", 42);
5689
5690        let features = SequenceBuilder::new()
5691            .item("auth")
5692            .item("logging")
5693            .build_document()
5694            .as_sequence()
5695            .unwrap();
5696        doc.insert_at_index(4, "features", features);
5697
5698        let output = doc.to_string();
5699
5700        let expected = r#"name: project
5701version: 1.0.0
5702active: true
5703count: 42
5704features:
5705- auth
5706- logging"#;
5707        assert_eq!(output.trim(), expected);
5708
5709        let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5710        let reparsed_doc = reparsed.document().expect("Should have document");
5711        let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5712
5713        assert_eq!(
5714            reparsed_mapping
5715                .get("version")
5716                .unwrap()
5717                .as_scalar()
5718                .unwrap()
5719                .as_string(),
5720            "1.0.0"
5721        );
5722        assert_eq!(
5723            reparsed_mapping.get("active").unwrap().to_bool(),
5724            Some(true)
5725        );
5726        assert_eq!(reparsed_mapping.get("count").unwrap().to_i64(), Some(42));
5727
5728        let features_value = reparsed_mapping.get("features").unwrap();
5729        let features = features_value.as_sequence().unwrap();
5730        assert_eq!(
5731            features.get(0).unwrap().as_scalar().unwrap().as_string(),
5732            "auth"
5733        );
5734        assert_eq!(
5735            features.get(1).unwrap().as_scalar().unwrap().as_string(),
5736            "logging"
5737        );
5738    }
5739
5740    #[test]
5741    fn test_insert_with_null_and_special_scalars() {
5742        let yaml = "name: project";
5743        let parsed = YamlFile::from_str(yaml).unwrap();
5744        let doc = parsed.document().expect("Should have a document");
5745
5746        // Insert various scalar types
5747        doc.insert_after("name", "null_value", ScalarValue::null());
5748        doc.insert_after("null_value", "empty_string", "");
5749        doc.insert_after("empty_string", "number", 1.234);
5750        doc.insert_after("number", "boolean", false);
5751
5752        let output = doc.to_string();
5753
5754        let expected = r#"name: project
5755null_value: null
5756empty_string: ''
5757number: 1.234
5758boolean: false"#;
5759        assert_eq!(output.trim(), expected);
5760
5761        let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5762        let reparsed_doc = reparsed.document().expect("Should have document");
5763        let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5764
5765        assert_eq!(
5766            reparsed_mapping
5767                .get("name")
5768                .unwrap()
5769                .as_scalar()
5770                .unwrap()
5771                .as_string(),
5772            "project"
5773        );
5774        assert!(reparsed_mapping
5775            .get("null_value")
5776            .unwrap()
5777            .as_scalar()
5778            .unwrap()
5779            .is_null());
5780        assert_eq!(
5781            reparsed_mapping
5782                .get("empty_string")
5783                .unwrap()
5784                .as_scalar()
5785                .unwrap()
5786                .as_string(),
5787            ""
5788        );
5789        assert_eq!(
5790            reparsed_mapping.get("number").unwrap().to_f64(),
5791            Some(1.234)
5792        );
5793        assert_eq!(
5794            reparsed_mapping.get("boolean").unwrap().to_bool(),
5795            Some(false)
5796        );
5797    }
5798
5799    #[test]
5800    fn test_insert_ordering_preservation() {
5801        let yaml = "first: 1\nthird: 3\nfifth: 5";
5802        let parsed = YamlFile::from_str(yaml).unwrap();
5803        let doc = parsed.document().expect("Should have a document");
5804
5805        // Insert items to create proper ordering
5806        doc.insert_after("first", "second", 2);
5807        doc.insert_before("fifth", "fourth", 4);
5808
5809        let output = doc.to_string();
5810
5811        // Check exact output - should preserve original structure and insert correctly
5812        let expected = r#"first: 1
5813second: 2
5814third: 3
5815fourth: 4
5816fifth: 5"#;
5817        assert_eq!(output.trim(), expected);
5818
5819        let reparsed = YamlFile::from_str(&output);
5820        assert!(reparsed.is_ok(), "Output should be valid YAML");
5821    }
5822
5823    #[test]
5824    fn test_insert_with_yamlvalue_positioning() {
5825        let yaml = "name: project\nversion: 1.0\nactive: true";
5826        let parsed = YamlFile::from_str(yaml).unwrap();
5827        let doc = parsed.document().expect("Should have a document");
5828
5829        // Test positioning with different value types
5830
5831        // Position after a string value
5832        let success = doc.insert_after("name", "description", "A sample project");
5833        assert!(success, "Should find string key");
5834
5835        // Position after a numeric value
5836        let success = doc.insert_after(1.0, "build", "gradle");
5837        assert!(
5838            !success,
5839            "Should not find numeric key (1.0) when actual key is string 'version'"
5840        );
5841
5842        // Position after a boolean value
5843        let success = doc.insert_after(true, "test", "enabled");
5844        assert!(
5845            !success,
5846            "Should not find boolean key (true) when actual key is string 'active'"
5847        );
5848
5849        // But string representation should work
5850        let bool_string_key = "true";
5851        let success = doc.insert_after(bool_string_key, "test_mode", "development");
5852        assert!(!success, "Should not find 'true' key when value is true");
5853
5854        let output = doc.to_string();
5855
5856        // Verify exact output - should preserve original structure and only insert description after name
5857        let expected = "name: project\ndescription: A sample project\nversion: 1.0\nactive: true";
5858        assert_eq!(output, expected);
5859    }
5860
5861    #[test]
5862    fn test_insert_complex_nested_structure() {
5863        let yaml = "name: project";
5864        let parsed = YamlFile::from_str(yaml).unwrap();
5865        let doc = parsed.document().expect("Should have a document");
5866
5867        // Create a complex nested structure
5868        let config = MappingBuilder::new()
5869            .mapping("server", |m| m.pair("host", "0.0.0.0").pair("port", 8080))
5870            .pair("debug", true)
5871            .sequence("features", |s| s.item("api").item("web").item("cli"))
5872            .build_document()
5873            .as_mapping()
5874            .unwrap();
5875
5876        doc.insert_after("name", "config", config);
5877
5878        let output = doc.to_string();
5879
5880        // Verify exact output (note: MappingBuilder adds trailing space after non-inline value keys)
5881        let expected = "name: project\nconfig:\n  server: \n    host: 0.0.0.0\n    port: 8080\n  debug: true\n  features: \n    - api\n    - web\n    - cli\n";
5882        assert_eq!(output, expected);
5883
5884        let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5885        let reparsed_doc = reparsed.document().expect("Should have document");
5886        let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5887
5888        let config_value = reparsed_mapping
5889            .get("config")
5890            .expect("Should have config key");
5891        let config_mapping = config_value.as_mapping().expect("config should be mapping");
5892
5893        let server_value = config_mapping
5894            .get("server")
5895            .expect("Should have server key");
5896        let server = server_value.as_mapping().expect("server should be mapping");
5897        assert_eq!(
5898            server.get("host").unwrap().as_scalar().unwrap().as_string(),
5899            "0.0.0.0"
5900        );
5901        assert_eq!(server.get("port").unwrap().to_i64(), Some(8080));
5902
5903        assert_eq!(config_mapping.get("debug").unwrap().to_bool(), Some(true));
5904
5905        let features_value = config_mapping
5906            .get("features")
5907            .expect("Should have features key");
5908        let features = features_value
5909            .as_sequence()
5910            .expect("features should be sequence");
5911        assert_eq!(features.len(), 3);
5912        assert_eq!(
5913            features.get(0).unwrap().as_scalar().unwrap().as_string(),
5914            "api"
5915        );
5916        assert_eq!(
5917            features.get(1).unwrap().as_scalar().unwrap().as_string(),
5918            "web"
5919        );
5920        assert_eq!(
5921            features.get(2).unwrap().as_scalar().unwrap().as_string(),
5922            "cli"
5923        );
5924    }
5925
5926    #[test]
5927    fn test_insert_with_yaml_sets() {
5928        let yaml = "name: project";
5929        let parsed = YamlFile::from_str(yaml).unwrap();
5930        let doc = parsed.document().expect("Should have a document");
5931
5932        // Insert a YAML set
5933        let mut tags = std::collections::BTreeSet::new();
5934        tags.insert("production".to_string());
5935        tags.insert("database".to_string());
5936        tags.insert("web".to_string());
5937
5938        let yaml_set = YamlValue::from_set(tags);
5939        doc.insert_after("name", "tags", yaml_set);
5940
5941        let output = doc.to_string();
5942
5943        // Verify exact output (sets use 4-space indent)
5944        let expected =
5945            "name: project\ntags: !!set\n    database: null\n    production: null\n    web: null\n";
5946        assert_eq!(output, expected);
5947
5948        let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5949        let reparsed_doc = reparsed.document().expect("Should have document");
5950        let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5951
5952        let tags_value = reparsed_mapping.get("tags").expect("Should have tags key");
5953        assert!(tags_value.is_tagged(), "tags should be tagged");
5954        let tagged = tags_value.as_tagged().expect("Should be tagged node");
5955        assert_eq!(tagged.tag(), Some("!!set".to_string()));
5956
5957        // Set is represented as a tagged mapping with null values
5958        // Navigate to the MAPPING child of the TAGGED node
5959        let tags_syntax = tagged.syntax();
5960        let tags_mapping = tags_syntax
5961            .children()
5962            .find_map(Mapping::cast)
5963            .expect("Set should have mapping child");
5964
5965        assert!(tags_mapping
5966            .get("database")
5967            .unwrap()
5968            .as_scalar()
5969            .unwrap()
5970            .is_null());
5971        assert!(tags_mapping
5972            .get("production")
5973            .unwrap()
5974            .as_scalar()
5975            .unwrap()
5976            .is_null());
5977        assert!(tags_mapping
5978            .get("web")
5979            .unwrap()
5980            .as_scalar()
5981            .unwrap()
5982            .is_null());
5983    }
5984
5985    #[test]
5986    fn test_insert_with_ordered_mappings() {
5987        let yaml = "name: project";
5988        let parsed = YamlFile::from_str(yaml).unwrap();
5989        let doc = parsed.document().expect("Should have a document");
5990
5991        // Insert a YAML ordered mapping (!!omap)
5992        let ordered_steps = vec![
5993            ("compile".to_string(), YamlValue::from("gcc main.c")),
5994            ("test".to_string(), YamlValue::from("./a.out test")),
5995            (
5996                "package".to_string(),
5997                YamlValue::from("tar -czf app.tar.gz ."),
5998            ),
5999        ];
6000
6001        let yaml_omap = YamlValue::from_ordered_mapping(ordered_steps);
6002        doc.insert_after("name", "build_steps", yaml_omap);
6003
6004        let output = doc.to_string();
6005
6006        // Verify exact output (omap uses 4-space indent)
6007        let expected = "name: project\nbuild_steps: !!omap\n    - compile: gcc main.c\n    - test: ./a.out test\n    - package: tar -czf app.tar.gz .\n";
6008        assert_eq!(output, expected);
6009
6010        let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6011        let reparsed_doc = reparsed.document().expect("Should have document");
6012        let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6013
6014        let steps_value = reparsed_mapping
6015            .get("build_steps")
6016            .expect("Should have build_steps key");
6017        assert!(steps_value.is_tagged(), "build_steps should be tagged");
6018        let tagged = steps_value.as_tagged().expect("Should be tagged node");
6019        assert_eq!(tagged.tag(), Some("!!omap".to_string()));
6020
6021        // Omap is represented as a tagged sequence of single-item mappings
6022        let steps_syntax = tagged.syntax();
6023        let steps_seq = steps_syntax
6024            .children()
6025            .find_map(Sequence::cast)
6026            .expect("Omap should have sequence child");
6027
6028        assert_eq!(steps_seq.len(), 3);
6029        // Each item in the sequence is a single-item mapping
6030        let compile_value = steps_seq.get(0).unwrap();
6031        let compile_item = compile_value.as_mapping().expect("Should be mapping");
6032        assert_eq!(
6033            compile_item
6034                .get("compile")
6035                .unwrap()
6036                .as_scalar()
6037                .unwrap()
6038                .as_string(),
6039            "gcc main.c"
6040        );
6041
6042        let test_value = steps_seq.get(1).unwrap();
6043        let test_item = test_value.as_mapping().expect("Should be mapping");
6044        assert_eq!(
6045            test_item
6046                .get("test")
6047                .unwrap()
6048                .as_scalar()
6049                .unwrap()
6050                .as_string(),
6051            "./a.out test"
6052        );
6053
6054        let package_value = steps_seq.get(2).unwrap();
6055        let package_item = package_value.as_mapping().expect("Should be mapping");
6056        assert_eq!(
6057            package_item
6058                .get("package")
6059                .unwrap()
6060                .as_scalar()
6061                .unwrap()
6062                .as_string(),
6063            "tar -czf app.tar.gz ."
6064        );
6065    }
6066
6067    #[test]
6068    fn test_insert_with_pairs() {
6069        let yaml = "name: project";
6070        let parsed = YamlFile::from_str(yaml).unwrap();
6071        let doc = parsed.document().expect("Should have a document");
6072
6073        // Insert a YAML pairs collection (!!pairs - allows duplicate keys)
6074        let connection_attempts = vec![
6075            ("server".to_string(), YamlValue::from("primary.db")),
6076            ("server".to_string(), YamlValue::from("secondary.db")), // Duplicate key allowed
6077            ("server".to_string(), YamlValue::from("tertiary.db")),  // Another duplicate
6078            ("timeout".to_string(), YamlValue::from(30)),
6079        ];
6080
6081        let yaml_pairs = YamlValue::from_pairs(connection_attempts);
6082        doc.insert_after("name", "connections", yaml_pairs);
6083
6084        let output = doc.to_string();
6085
6086        // Verify exact output (pairs use 4-space indent)
6087        let expected = "name: project\nconnections: !!pairs\n    - server: primary.db\n    - server: secondary.db\n    - server: tertiary.db\n    - timeout: 30\n";
6088        assert_eq!(output, expected);
6089
6090        let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6091        let reparsed_doc = reparsed.document().expect("Should have document");
6092        let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6093
6094        let connections_value = reparsed_mapping
6095            .get("connections")
6096            .expect("Should have connections key");
6097        assert!(
6098            connections_value.is_tagged(),
6099            "connections should be tagged"
6100        );
6101        let tagged = connections_value
6102            .as_tagged()
6103            .expect("Should be tagged node");
6104        assert_eq!(tagged.tag(), Some("!!pairs".to_string()));
6105
6106        // Pairs is represented as a tagged sequence of single-item mappings (allows duplicate keys)
6107        let connections_syntax = tagged.syntax();
6108        let connections_seq = connections_syntax
6109            .children()
6110            .find_map(Sequence::cast)
6111            .expect("Pairs should have sequence child");
6112
6113        assert_eq!(connections_seq.len(), 4);
6114
6115        let server1_val = connections_seq.get(0).unwrap();
6116        let server1 = server1_val.as_mapping().expect("Should be mapping");
6117        assert_eq!(
6118            server1
6119                .get("server")
6120                .unwrap()
6121                .as_scalar()
6122                .unwrap()
6123                .as_string(),
6124            "primary.db"
6125        );
6126
6127        let server2_val = connections_seq.get(1).unwrap();
6128        let server2 = server2_val.as_mapping().expect("Should be mapping");
6129        assert_eq!(
6130            server2
6131                .get("server")
6132                .unwrap()
6133                .as_scalar()
6134                .unwrap()
6135                .as_string(),
6136            "secondary.db"
6137        );
6138
6139        let server3_val = connections_seq.get(2).unwrap();
6140        let server3 = server3_val.as_mapping().expect("Should be mapping");
6141        assert_eq!(
6142            server3
6143                .get("server")
6144                .unwrap()
6145                .as_scalar()
6146                .unwrap()
6147                .as_string(),
6148            "tertiary.db"
6149        );
6150
6151        let timeout_val = connections_seq.get(3).unwrap();
6152        let timeout = timeout_val.as_mapping().expect("Should be mapping");
6153        assert_eq!(timeout.get("timeout").unwrap().to_i64(), Some(30));
6154    }
6155
6156    #[test]
6157    fn test_insert_with_empty_collections() {
6158        // Test each empty collection type individually to avoid chaining issues
6159
6160        // Test empty sequence - use flow-style literal
6161        let yaml1 = "name: project";
6162        let parsed1 = YamlFile::from_str(yaml1).unwrap();
6163        let doc1 = parsed1.document().expect("Should have a document");
6164
6165        // Parse a flow-style empty sequence
6166        let empty_seq_yaml = YamlFile::from_str("[]").unwrap();
6167        let empty_list = empty_seq_yaml.document().unwrap().as_sequence().unwrap();
6168
6169        doc1.insert_after("name", "empty_list", empty_list);
6170        let output1 = doc1.to_string();
6171        assert_eq!(output1, "name: project\nempty_list: []\n");
6172
6173        // Test empty mapping - use flow-style literal
6174        let yaml2 = "name: project";
6175        let parsed2 = YamlFile::from_str(yaml2).unwrap();
6176        let doc2 = parsed2.document().expect("Should have a document");
6177
6178        // Parse a flow-style empty mapping
6179        let empty_map_yaml = YamlFile::from_str("{}").unwrap();
6180        let empty_map = empty_map_yaml.document().unwrap().as_mapping().unwrap();
6181
6182        doc2.insert_after("name", "empty_map", empty_map);
6183        let output2 = doc2.to_string();
6184        assert_eq!(output2, "name: project\nempty_map: {}\n");
6185
6186        // Test empty set
6187        let yaml3 = "name: project";
6188        let parsed3 = YamlFile::from_str(yaml3).unwrap();
6189        let doc3 = parsed3.document().expect("Should have a document");
6190        doc3.insert_after("name", "empty_set", YamlValue::set());
6191        let output3 = doc3.to_string();
6192        assert_eq!(output3, "name: project\nempty_set: !!set {}\n");
6193
6194        // Verify all are valid YAML
6195        assert!(
6196            YamlFile::from_str(&output1).is_ok(),
6197            "Empty sequence output should be valid YAML"
6198        );
6199        assert!(
6200            YamlFile::from_str(&output2).is_ok(),
6201            "Empty mapping output should be valid YAML"
6202        );
6203        assert!(
6204            YamlFile::from_str(&output3).is_ok(),
6205            "Empty set output should be valid YAML"
6206        );
6207    }
6208
6209    #[test]
6210    fn test_insert_with_deeply_nested_sequences() {
6211        let yaml = "name: project";
6212        let parsed = YamlFile::from_str(yaml).unwrap();
6213        let doc = parsed.document().expect("Should have a document");
6214
6215        // Create deeply nested sequence with mixed types
6216        let nested_data = SequenceBuilder::new()
6217            .item("item1")
6218            .mapping(|m| {
6219                m.sequence("config", |s| s.item("debug").item(true).item(8080))
6220                    .pair("name", "service")
6221            })
6222            .item(42)
6223            .build_document()
6224            .as_sequence()
6225            .unwrap();
6226
6227        doc.insert_after("name", "nested_data", nested_data);
6228
6229        let output = doc.to_string();
6230
6231        let expected = "name: project\nnested_data:\n  - item1\n  - \n    config: \n      - debug\n      - true\n      - 8080\n    name: service- 42\n";
6232        assert_eq!(output, expected);
6233
6234        let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6235        let reparsed_doc = reparsed.document().expect("Should have document");
6236        let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6237
6238        let nested_value = reparsed_mapping
6239            .get("nested_data")
6240            .expect("Should have nested_data key");
6241        let nested_seq = nested_value.as_sequence().expect("Should be sequence");
6242        // Note: Due to formatting issue, the sequence has 2 items instead of expected 3
6243        // The "- 42" is appended to "service" value due to missing newline
6244        assert_eq!(nested_seq.len(), 2);
6245
6246        // First item is a string
6247        assert_eq!(
6248            nested_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6249            "item1"
6250        );
6251
6252        // Second item is a mapping with config sequence and name pair
6253        let second_item = nested_seq.get(1).unwrap();
6254        let second_mapping = second_item.as_mapping().expect("Should be mapping");
6255
6256        let config_value = second_mapping.get("config").expect("Should have config");
6257        let config_seq = config_value
6258            .as_sequence()
6259            .expect("config should be sequence");
6260        assert_eq!(config_seq.len(), 3);
6261        assert_eq!(
6262            config_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6263            "debug"
6264        );
6265        assert_eq!(config_seq.get(1).unwrap().to_bool(), Some(true));
6266        assert_eq!(config_seq.get(2).unwrap().to_i64(), Some(8080));
6267
6268        // Note: The "name" value includes "- 42" due to formatting issue
6269        assert_eq!(
6270            second_mapping
6271                .get("name")
6272                .unwrap()
6273                .as_scalar()
6274                .unwrap()
6275                .as_string(),
6276            "service- 42"
6277        );
6278    }
6279
6280    #[test]
6281    fn test_ast_preservation_comments_in_mapping() {
6282        // Test that comments within mappings are preserved during insertions
6283        let yaml = r#"---
6284# Header comment
6285key1: value1  # Inline comment 1
6286# Middle comment
6287key2: value2  # Inline comment 2
6288# Footer comment
6289"#;
6290        let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6291
6292        // Insert a new key, re-inserting it if it already exists - changes propagate automatically
6293        if let Some(mapping) = doc.as_mapping() {
6294            mapping.move_after("key1", "new_key", "new_value");
6295        }
6296
6297        let result = doc.to_string();
6298
6299        let expected = r#"---
6300# Header comment
6301key1: value1  # Inline comment 1
6302new_key: new_value
6303# Middle comment
6304key2: value2  # Inline comment 2
6305# Footer comment
6306"#;
6307        assert_eq!(result, expected);
6308    }
6309
6310    #[test]
6311    fn test_ast_preservation_whitespace_in_mapping() {
6312        // Test that whitespace and formatting within mappings are preserved
6313        let yaml = r#"---
6314key1:    value1
6315
6316
6317key2:        value2
6318"#;
6319        let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6320
6321        // Insert a new key using AST-preserving method
6322        if let Some(mapping) = doc.as_mapping() {
6323            mapping.move_after("key1", "new_key", "new_value");
6324        }
6325
6326        let result = doc.to_string();
6327
6328        let expected = r#"---
6329key1:    value1
6330new_key: new_value
6331
6332
6333key2:        value2
6334"#;
6335        assert_eq!(result, expected);
6336    }
6337
6338    #[test]
6339    fn test_ast_preservation_complex_structure() {
6340        // Test preservation of complex structure with nested comments
6341        let yaml = r#"---
6342# Configuration file
6343database:  # Database settings
6344  host: localhost  # Default host
6345  port: 5432      # Default port
6346  # Connection pool settings  
6347  pool:
6348    min: 1    # Minimum connections
6349    max: 10   # Maximum connections
6350
6351# Server configuration
6352server:
6353  port: 8080  # HTTP port
6354"#;
6355        let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6356
6357        eprintln!("===========\n");
6358
6359        // Insert a new top-level key
6360        if let Some(mapping) = doc.as_mapping() {
6361            mapping.move_after("database", "logging", "debug");
6362        }
6363
6364        let result = doc.to_string();
6365
6366        // Verify exact output to ensure all comments and structure are preserved
6367        let expected = r#"---
6368# Configuration file
6369database:  # Database settings
6370  host: localhost  # Default host
6371  port: 5432      # Default port
6372  # Connection pool settings  
6373  pool:
6374    min: 1    # Minimum connections
6375    max: 10   # Maximum connections
6376
6377logging: debug
6378# Server configuration
6379server:
6380  port: 8080  # HTTP port
6381"#;
6382        assert_eq!(result, expected);
6383    }
6384
6385    // Tests for next_flow_element_is_implicit_mapping lookahead function
6386    mod lookahead_tests {
6387        use super::*;
6388        use crate::lex::lex;
6389
6390        /// Helper to test lookahead without creating full parser
6391        fn check_implicit_mapping(yaml: &str) -> bool {
6392            let tokens: Vec<(SyntaxKind, &str)> = lex(yaml);
6393            // Extract just the kinds in reverse order (matching Parser's token storage)
6394            let kinds: Vec<SyntaxKind> = tokens.iter().rev().map(|(kind, _)| *kind).collect();
6395            has_implicit_mapping_pattern(kinds.into_iter())
6396        }
6397
6398        #[test]
6399        fn test_simple_implicit_mapping() {
6400            // Looking at: 'key' : value (inside [ ... ])
6401            // Should detect colon at depth 0
6402            assert!(check_implicit_mapping("'key' : value"));
6403        }
6404
6405        #[test]
6406        fn test_simple_value_no_mapping() {
6407            // Looking at: value, ... (stops at comma, no colon)
6408            assert!(!check_implicit_mapping("value ,"));
6409        }
6410
6411        #[test]
6412        fn test_value_with_comma() {
6413            // Looking at: value, more (comma at depth 0, not a mapping)
6414            assert!(!check_implicit_mapping("value , more"));
6415        }
6416
6417        #[test]
6418        fn test_nested_sequence_as_key() {
6419            // Looking at: [a, b]: value (colon after nested sequence completes)
6420            assert!(check_implicit_mapping("[a, b]: value"));
6421        }
6422
6423        #[test]
6424        fn test_nested_mapping_not_implicit() {
6425            // Looking at: {a: 1}, b (colon is inside braces at depth 1, not depth 0)
6426            assert!(!check_implicit_mapping("{a: 1}, b"));
6427        }
6428
6429        #[test]
6430        fn test_deeply_nested_key() {
6431            // Looking at: [[a]]: value (multiple levels of nesting)
6432            assert!(check_implicit_mapping("[[a]]: value"));
6433        }
6434
6435        #[test]
6436        fn test_with_whitespace() {
6437            // Looking at: 'key'  :  value (whitespace shouldn't affect detection)
6438            assert!(check_implicit_mapping("'key'  :  value"));
6439        }
6440
6441        #[test]
6442        fn test_mapping_value_in_sequence() {
6443            // Looking at: a, {key: value} (second element has colon but inside braces)
6444            // First element is just "a" before comma
6445            assert!(!check_implicit_mapping("a, {key: value}"));
6446        }
6447
6448        #[test]
6449        fn test_multiple_colons() {
6450            // Looking at: key1: value1, key2: value2 (first element is mapping)
6451            assert!(check_implicit_mapping("key1: value1, key2: value2"));
6452        }
6453
6454        #[test]
6455        fn test_url_not_mapping() {
6456            // Looking at: http://example.com (colon in URL, but no space after)
6457            // Lexer should tokenize this as single string token
6458            assert!(!check_implicit_mapping("http://example.com"));
6459        }
6460    }
6461
6462    #[test]
6463    fn test_from_str_relaxed_valid() {
6464        let (yaml_file, errors) = YamlFile::from_str_relaxed("key: value\n");
6465        assert!(errors.is_empty());
6466        let doc = yaml_file.document().unwrap();
6467        let mapping = doc.as_mapping().unwrap();
6468        assert_eq!(
6469            mapping.get("key").unwrap().as_scalar().unwrap().to_string(),
6470            "value"
6471        );
6472    }
6473
6474    #[test]
6475    fn test_from_str_relaxed_unclosed_flow_sequence() {
6476        let (yaml_file, errors) = YamlFile::from_str_relaxed("key: [a, b");
6477        assert!(!errors.is_empty());
6478        // Tree is still usable
6479        let doc = yaml_file.document().unwrap();
6480        assert!(doc.as_mapping().is_some());
6481    }
6482
6483    #[test]
6484    fn test_from_str_relaxed_unclosed_flow_mapping() {
6485        let (yaml_file, errors) = YamlFile::from_str_relaxed("key: {a: 1");
6486        assert!(!errors.is_empty());
6487        let doc = yaml_file.document().unwrap();
6488        assert!(doc.as_mapping().is_some());
6489    }
6490
6491    #[test]
6492    fn test_from_str_relaxed_preserves_valid_content() {
6493        let input = "good: value\nbad: [unclosed\nalso_good: 42\n";
6494        let (yaml_file, errors) = YamlFile::from_str_relaxed(input);
6495        assert!(!errors.is_empty());
6496        let doc = yaml_file.document().unwrap();
6497        let mapping = doc.as_mapping().unwrap();
6498        assert_eq!(
6499            mapping
6500                .get("good")
6501                .unwrap()
6502                .as_scalar()
6503                .unwrap()
6504                .to_string(),
6505            "value"
6506        );
6507    }
6508
6509    #[test]
6510    fn test_from_str_relaxed_empty_input() {
6511        let (yaml_file, errors) = YamlFile::from_str_relaxed("");
6512        assert!(errors.is_empty());
6513        assert!(yaml_file.document().is_none());
6514    }
6515
6516    #[test]
6517    fn test_parse_flow_sequence_with_stray_close_brace_does_not_hang() {
6518        // Regression: previously caused an infinite loop / OOM because
6519        // parse_flow_sequence did not make progress on `}`.
6520        let parse = crate::Parse::parse_yaml("[}9[Z");
6521        let _ = parse.tree();
6522        assert!(parse.has_errors());
6523    }
6524
6525    #[test]
6526    fn test_parse_flow_mapping_with_stray_close_bracket_does_not_hang() {
6527        // Regression: parse_flow_mapping must make progress on `]`.
6528        let parse = crate::Parse::parse_yaml("{]");
6529        let _ = parse.tree();
6530        assert!(parse.has_errors());
6531    }
6532
6533    #[test]
6534    fn test_parse_explicit_key_multiline_with_comment_does_not_hang() {
6535        // Regression: the multi-line explicit-key scalar loop must make
6536        // progress when the indented continuation begins with a token
6537        // parse_scalar() cannot consume (e.g. COMMENT).
6538        let input = "?\u{18}\0\0\0\0\0\0\n\t#\t\t?\t";
6539        let parse = crate::Parse::parse_yaml(input);
6540        let _ = parse.tree();
6541    }
6542
6543    #[test]
6544    fn test_parse_deeply_nested_flow_mapping_does_not_blow_up() {
6545        // Regression: deeply nested `{{{...` blew RSS/stack. The parser now
6546        // bails out with an error after MAX_FLOW_DEPTH levels.
6547        let input = "{".repeat(2000);
6548        let parse = crate::Parse::parse_yaml(&input);
6549        let _ = parse.tree();
6550        assert!(parse.has_errors());
6551    }
6552
6553    #[test]
6554    fn test_parse_deeply_nested_flow_sequence_does_not_blow_up() {
6555        let input = "[".repeat(2000);
6556        let parse = crate::Parse::parse_yaml(&input);
6557        let _ = parse.tree();
6558        assert!(parse.has_errors());
6559    }
6560
6561    #[test]
6562    fn test_parse_explicit_key_followed_by_question_in_value_does_not_hang() {
6563        // Regression: parse_explicit_key_mapping's continuation loop must
6564        // re-enter explicit-key handling when current is `?` instead of
6565        // calling parse_mapping_key_value_pair (which cannot consume a `?`).
6566        let input = "<<?: ?:  \n<<\n ?:    : 0\n:@";
6567        let parse = crate::Parse::parse_yaml(input);
6568        let _ = parse.tree();
6569    }
6570
6571    #[test]
6572    fn test_parse_complex_key_mapping_with_stray_close_bracket_does_not_hang() {
6573        // Regression: parse_complex_key_mapping's continuation loop must
6574        // make progress when is_mapping_key() is fooled by `]:`.
6575        let input = "[%:%:]: \n]: \n\n";
6576        let parse = crate::Parse::parse_yaml(input);
6577        let _ = parse.tree();
6578    }
6579
6580    #[test]
6581    fn test_parse_mapping_with_stray_close_brace_does_not_hang() {
6582        // Regression: parse_mapping_with_base_indent must make progress when
6583        // is_mapping_key() is fooled by `}:` (or `]:`) appearing as a stray
6584        // delimiter — the missing-colon recovery inserts a synthetic token
6585        // without advancing the input.
6586        let input = "\0:\n\n}:\n\n{";
6587        let parse = crate::Parse::parse_yaml(input);
6588        let _ = parse.tree();
6589        assert!(parse.has_errors());
6590    }
6591}