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