Skip to main content

orrery_parser/
elaborate.rs

1//! Elaboration phase for the Orrery AST
2//!
3//! This module transforms the desugared AST from parser types into fully elaborated
4//! types ready for layout and rendering. It performs type resolution, validates
5//! semantic correctness, and builds the final representation.
6
7use std::{collections::HashMap, mem, rc::Rc, str::FromStr};
8
9use log::{debug, info, trace};
10
11use orrery_core::{
12    color::Color,
13    draw::{ArrowDirection, ArrowStyle, LifelineDefinition, StrokeDefinition, TextDefinition},
14    identifier::Id,
15    semantic::{
16        Activate, Block, Diagram, DiagramKind, Element, Fragment, FragmentSection, LayoutEngine,
17        Node, Note, NoteAlign, Relation, Scope,
18    },
19};
20
21use crate::{
22    builtin_types,
23    elaborate_utils::{self, StrokeAttributeExtractor, TextAttributeExtractor},
24    error::{Diagnostic, ErrorCode, Result},
25    parser_types,
26    span::{Span, Spanned},
27};
28
29/// Configuration for the elaboration phase.
30///
31/// This struct holds the default layout engine settings that are used
32/// when no explicit layout_engine attribute is specified in the diagram.
33#[derive(Debug, Clone, Default)]
34pub struct ElaborateConfig {
35    /// Default layout engine for component diagrams
36    pub component_layout: LayoutEngine,
37    /// Default layout engine for sequence diagrams
38    pub sequence_layout: LayoutEngine,
39}
40
41impl ElaborateConfig {
42    /// Creates a new [`ElaborateConfig`] with the specified layout engines.
43    pub fn new(component_layout: LayoutEngine, sequence_layout: LayoutEngine) -> Self {
44        Self {
45            component_layout,
46            sequence_layout,
47        }
48    }
49}
50
51/// Builds semantic diagrams from parser AST.
52///
53/// The builder transforms validated AST nodes into the semantic model,
54/// resolving type references and constructing the final diagram structure
55/// ready for layout and rendering.
56///
57/// # Overview
58///
59/// The elaboration process:
60/// 1. Registers built-in type definitions
61/// 2. Processes user-defined types from the AST
62/// 3. Resolves type references and validates structure
63/// 4. Constructs the semantic [`Diagram`](orrery_core::Diagram)
64pub struct Builder {
65    cfg: ElaborateConfig,
66    type_definitions: HashMap<Id, elaborate_utils::TypeDefinition>,
67}
68
69impl Builder {
70    /// Creates a new builder with the given configuration.
71    ///
72    /// Built-in in type definitions are installed for each diagram, ensuring that
73    /// each diagram has its ownpe scope.
74    ///
75    /// # Arguments
76    ///
77    /// * `cfg` - Configuration controlling elaboration behavior, including
78    ///   default layout engines for different diagram types.
79    pub fn new(cfg: ElaborateConfig) -> Self {
80        Self {
81            cfg,
82            type_definitions: HashMap::new(),
83        }
84    }
85
86    // ============================================================================
87    // Main Entry Methods
88    // ============================================================================
89
90    /// Consumes the builder and elaborates a [`FileAst`](parser_types::FileAst)
91    /// into a semantic [`Diagram`](Diagram).
92    ///
93    /// This is the public entry point for elaboration. It delegates to
94    /// [`build_diagram_from_file_ast`](Self::build_diagram_from_file_ast).
95    ///
96    /// # Arguments
97    ///
98    /// * `ast` - A parser diagram AST.
99    ///
100    /// # Returns
101    ///
102    /// The elaborated semantic diagram ready for layout and rendering.
103    ///
104    /// # Errors
105    ///
106    /// Returns an error if elaboration fails. This includes all semantic
107    /// errors in the `E3xx` range, such as undefined types, invalid
108    /// attributes, nested diagrams, or structural validation failures.
109    pub fn build(mut self, ast: &parser_types::FileAst) -> Result<Diagram> {
110        debug!("Building elaborated diagram");
111        self.build_diagram_from_file_ast(ast)
112    }
113
114    /// Builds a semantic diagram from a parsed file AST.
115    ///
116    /// Converts a [`FileAst`](parser_types::FileAst) into a [`Diagram`](Diagram)
117    /// by registering type definitions, extracting the diagram kind, processing
118    /// elements into a scope, and resolving diagram-level attributes.
119    ///
120    /// Each invocation creates an isolated type definition scope: the current
121    /// `type_definitions` are saved, replaced with fresh built-ins, and
122    /// restored after the diagram is built. This ensures that custom types
123    /// defined in an embedded diagram do not leak into its parent.
124    ///
125    /// # Errors
126    ///
127    /// Returns an `E3xx` error if elaboration fails.
128    fn build_diagram_from_file_ast(&mut self, file_ast: &parser_types::FileAst) -> Result<Diagram> {
129        let (kind_spanned, attributes) = match &file_ast.header {
130            parser_types::FileHeader::Diagram { kind, attributes } => (kind, attributes),
131            parser_types::FileHeader::Library { span } => {
132                return Err(Diagnostic::error("expected diagram, found library")
133                    .with_code(ErrorCode::E306)
134                    .with_label(*span, "expected diagram"));
135            }
136        };
137
138        info!("Processing diagram of kind: {kind_spanned}");
139        trace!("Type definitions: {:?}", file_ast.type_definitions);
140        trace!("Elements count: {}", file_ast.elements.len());
141
142        let saved_type_defs = mem::replace(
143            &mut self.type_definitions,
144            Self::builtin_type_definitions_map(),
145        );
146
147        debug!("Updating type definitions");
148        self.update_type_direct_definitions(&file_ast.type_definitions)?;
149
150        let kind = **kind_spanned;
151
152        debug!("Building block from elements");
153        let block = self.build_block_from_elements(&file_ast.elements, kind)?;
154
155        let scope = match block {
156            Block::None => {
157                debug!("Empty block, using default scope");
158                Scope::default()
159            }
160            Block::Scope(scope) => {
161                debug!(
162                    elements_len = scope.elements().len();
163                    "Using scope from block",
164                );
165                scope
166            }
167            Block::Diagram(_) => {
168                return Err(Diagnostic::error("nested diagram not allowed")
169                    .with_code(ErrorCode::E305)
170                    .with_label(kind_spanned.span(), "nested diagram")
171                    .with_help("diagrams cannot be nested inside other diagrams"));
172            }
173        };
174
175        let (layout_engine, background_color, lifeline_definition) =
176            self.extract_diagram_attributes(kind, attributes)?;
177
178        info!(kind:?; "Diagram elaboration completed successfully");
179
180        // Restore parent type definitions.
181        self.type_definitions = saved_type_defs;
182
183        Ok(Diagram::new(
184            kind,
185            scope,
186            layout_engine,
187            background_color,
188            lifeline_definition,
189        ))
190    }
191
192    /// Builds a semantic diagram from a [`DiagramSource`](parser_types::DiagramSource).
193    ///
194    /// Dispatches on the source variant:
195    /// - [`Inline`](parser_types::DiagramSource::Inline) — the referenced file's AST
196    ///   has been inlined, so elaboration proceeds via
197    ///   [`build_diagram_from_file_ast`](Self::build_diagram_from_file_ast).
198    /// - [`Ref`](parser_types::DiagramSource::Ref) — a defensive guard. The desugar
199    ///   phase must resolve all `Ref` variants before elaboration runs; reaching
200    ///   this branch indicates a compiler bug and produces `E309`.
201    ///
202    /// # Errors
203    ///
204    /// Returns `E309` if a `DiagramSource::Ref` is encountered, or propagates any
205    /// error from [`build_diagram_from_file_ast`](Self::build_diagram_from_file_ast).
206    fn build_diagram_from_diagram_source(
207        &mut self,
208        source: &parser_types::DiagramSource,
209    ) -> Result<Diagram> {
210        match source {
211            parser_types::DiagramSource::Inline(rc) => {
212                let file_ast = rc.borrow();
213                self.build_diagram_from_file_ast(&file_ast)
214            }
215            // Defensive: validate (E204) catches unresolved embed references
216            // before elaboration runs, so this branch should never be reached.
217            parser_types::DiagramSource::Ref(id) => Err(Diagnostic::error(format!(
218                "unresolved embed reference `{id}`",
219            ))
220            .with_code(ErrorCode::E309)
221            .with_label(id.span(), "expected inlined embedded diagram")),
222        }
223    }
224
225    // ============================================================================
226    // Attribute Value Extraction Helpers
227    // ============================================================================
228    // These associated functions provide a way to extract
229    // and validate attribute values with consistent error messages.
230
231    /// Extract a TypeSpec from an attribute value with contextual error.
232    ///
233    /// # Arguments
234    /// * `attr` - The attribute containing the value
235    /// * `key` - Display name for error messages (e.g., "stroke", "text")
236    fn extract_type_spec<'b>(
237        attr: &'b parser_types::Attribute<'b>,
238        key: &str,
239    ) -> Result<&'b parser_types::TypeSpec<'b>> {
240        attr.value.as_type_spec().map_err(|err| {
241            Diagnostic::error(err.to_string())
242                .with_code(ErrorCode::E302)
243                .with_label(attr.span(), format!("invalid {key} attribute value"))
244                .with_help(format!(
245                    "{key} attribute must be a type reference or inline attributes"
246                ))
247        })
248    }
249
250    /// Extract a string from an attribute value with contextual error.
251    ///
252    /// # Arguments
253    /// * `attr` - The attribute containing the value
254    /// * `key` - Display name for error messages (e.g., "style", "layout_engine")
255    fn extract_string<'b>(attr: &'b parser_types::Attribute<'b>, key: &str) -> Result<&'b str> {
256        attr.value.as_str().map_err(|err| {
257            Diagnostic::error(err.to_string())
258                .with_code(ErrorCode::E302)
259                .with_label(attr.span(), format!("invalid {key} value"))
260                .with_help(format!("{key} values must be strings"))
261        })
262    }
263
264    /// Extract and parse a color from an attribute value with contextual error.
265    /// This performs both string extraction and color parsing in one step.
266    ///
267    /// # Arguments
268    /// * `attr` - The attribute containing the value
269    /// * `key` - Display name for error messages (e.g., "fill_color", "background_color")
270    fn extract_color(attr: &parser_types::Attribute<'_>, key: &str) -> Result<Color> {
271        let color_str = attr.value.as_str().map_err(|err| {
272            Diagnostic::error(err.to_string())
273                .with_code(ErrorCode::E302)
274                .with_label(attr.span(), "invalid color value")
275                .with_help("color values must be strings")
276        })?;
277
278        Color::new(color_str).map_err(|err| {
279            Diagnostic::error(format!("invalid {key} `{color_str}`: {err}"))
280                .with_code(ErrorCode::E302)
281                .with_label(attr.span(), "invalid color")
282                .with_help("use a valid CSS color")
283        })
284    }
285
286    /// Extract a positive float from an attribute value with contextual error.
287    ///
288    /// # Arguments
289    /// * `attr` - The attribute containing the value
290    /// * `key` - Display name for error messages (e.g., "width", "padding")
291    fn extract_positive_float(attr: &parser_types::Attribute<'_>, key: &str) -> Result<f32> {
292        attr.value.as_float().map_err(|err| {
293            Diagnostic::error(err.to_string())
294                .with_code(ErrorCode::E302)
295                .with_label(attr.span(), format!("invalid {key} value"))
296                .with_help(format!("{key} must be a positive number"))
297        })
298    }
299
300    /// Extract a usize from an attribute value with contextual error.
301    ///
302    /// # Arguments
303    /// * `attr` - The attribute containing the value
304    /// * `key` - Display name for error messages (e.g., "rounded")
305    /// * `hint` - Additional hint for the error message (e.g., "must be a positive number")
306    fn extract_usize(attr: &parser_types::Attribute<'_>, key: &str, hint: &str) -> Result<usize> {
307        attr.value.as_usize().map_err(|err| {
308            Diagnostic::error(err.to_string())
309                .with_code(ErrorCode::E302)
310                .with_label(attr.span(), format!("invalid {key} value"))
311                .with_help(format!("{key} {hint}"))
312        })
313    }
314
315    // ============================================================================
316    // Type Definition Methods
317    // ============================================================================
318
319    /// Returns the built-in type definitions as an id-keyed map.
320    fn builtin_type_definitions_map() -> HashMap<Id, elaborate_utils::TypeDefinition> {
321        builtin_types::defaults()
322            .into_iter()
323            .map(|def| (def.id(), def))
324            .collect()
325    }
326
327    // TODO: Change error type so it would not accept a span.
328    fn insert_type_definition(
329        &mut self,
330        type_def: elaborate_utils::TypeDefinition,
331        span: Span,
332    ) -> Result<elaborate_utils::TypeDefinition> {
333        let id = type_def.id();
334
335        // Check if the type already exists
336        if self.type_definitions.insert(id, type_def.clone()).is_none() {
337            Ok(type_def)
338        } else {
339            Err(Diagnostic::error(format!("cannot override type `{id}`"))
340                .with_code(ErrorCode::E301)
341                .with_label(span, "type override not supported")
342                .with_help("built-in types cannot be redefined"))
343        }
344    }
345
346    fn update_type_direct_definitions(
347        &mut self,
348        type_definitions: &Vec<parser_types::TypeDefinition>,
349    ) -> Result<()> {
350        for type_def in type_definitions {
351            let base_type_name = type_def
352                .type_spec
353                .type_name
354                .as_ref()
355                .expect("TypeDefinition should always have a type_name in TypeSpec");
356
357            let base = self
358                .type_definitions
359                .get(base_type_name.inner())
360                .ok_or_else(|| {
361                    // Create a rich diagnostic error with source location information
362                    self.create_undefined_type_error(
363                        base_type_name,
364                        &format!("base type `{}` not found", base_type_name.inner()),
365                    )
366                })?;
367
368            // Try to create the type definition
369            let new_type_def = self.build_type_from_base(
370                *type_def.name.inner(),
371                base,
372                &type_def.type_spec.attributes,
373            )?;
374            self.insert_type_definition(new_type_def, type_def.span())?;
375        }
376        Ok(())
377    }
378
379    /// Converts a slice of parser elements into a semantic [`Block`](Block).
380    ///
381    /// Returns `Block::None` for an empty slice, or wraps the elaborated elements
382    /// in a `Block::Scope`. This function only produces `None` or `Scope` variants;
383    /// the `Block::Diagram` variant is constructed by
384    /// [`build_component_element`](Self::build_component_element) when it encounters
385    /// a `ComponentContent::Diagram`.
386    ///
387    /// # Errors
388    ///
389    /// Propagates any error from [`build_scope_from_elements`](Self::build_scope_from_elements).
390    fn build_block_from_elements(
391        &mut self,
392        parser_elements: &[parser_types::Element],
393        diagram_kind: DiagramKind,
394    ) -> Result<Block> {
395        if parser_elements.is_empty() {
396            Ok(Block::None)
397        } else {
398            Ok(Block::Scope(self.build_scope_from_elements(
399                parser_elements,
400                diagram_kind,
401            )?))
402        }
403    }
404
405    /// Elaborates a slice of parser elements into a semantic [`Scope`](Scope).
406    ///
407    /// Dispatches each [`Element`](parser_types::Element) variant to the appropriate
408    /// `build_*_element` method. Sugar variants (`ActivateBlock`, `AltElseBlock`,
409    /// etc.) are expected to have been desugared already and trigger an
410    /// `unreachable!` panic if encountered.
411    ///
412    /// # Errors
413    ///
414    /// Propagates any `E3xx` error from individual element elaboration.
415    fn build_scope_from_elements(
416        &mut self,
417        parser_elements: &[parser_types::Element],
418        diagram_kind: DiagramKind,
419    ) -> Result<Scope> {
420        let mut elements = Vec::new();
421
422        for parser_elm in parser_elements {
423            let element = match parser_elm {
424                parser_types::Element::Component {
425                    name,
426                    display_name,
427                    type_spec,
428                    content,
429                } => self.build_component_element(
430                    name,
431                    display_name,
432                    type_spec,
433                    content,
434                    parser_elm,
435                    diagram_kind,
436                )?,
437                parser_types::Element::Relation {
438                    source,
439                    target,
440                    relation_type,
441                    type_spec,
442                    label,
443                } => {
444                    self.build_relation_element(source, target, relation_type, type_spec, label)?
445                }
446                parser_types::Element::ActivateBlock { .. } => {
447                    unreachable!(
448                        "ActivateBlock should have been desugared into explicit activate/deactivate statements before elaboration"
449                    );
450                }
451                parser_types::Element::Activate {
452                    component,
453                    type_spec,
454                } => self.build_activate_element(component, type_spec, diagram_kind)?,
455                parser_types::Element::Deactivate { component } => {
456                    self.build_deactivate_element(component, diagram_kind)?
457                }
458                parser_types::Element::Fragment(fragment) => {
459                    self.build_fragment_element(fragment, diagram_kind)?
460                }
461                parser_types::Element::AltElseBlock { .. }
462                | parser_types::Element::OptBlock { .. }
463                | parser_types::Element::LoopBlock { .. }
464                | parser_types::Element::ParBlock { .. }
465                | parser_types::Element::BreakBlock { .. }
466                | parser_types::Element::CriticalBlock { .. } => {
467                    unreachable!(
468                        "Fragment sugar syntax should have been desugared into Fragment elements before elaboration"
469                    );
470                }
471                parser_types::Element::Note(note) => self.build_note_element(note, diagram_kind)?,
472            };
473            elements.push(element);
474        }
475        Ok(Scope::new(elements))
476    }
477
478    /// Builds a component element from parser data.
479    ///
480    /// Resolves the component's type definition, validates that the shape supports
481    /// content (if any), and constructs the semantic [`Node`](Node).
482    ///
483    /// Content is determined by matching on [`ComponentContent`](parser_types::ComponentContent):
484    /// - `None` — the component has no nested content.
485    /// - `Scope` — the component contains child elements, elaborated recursively
486    ///   via [`build_block_from_elements`](Self::build_block_from_elements).
487    /// - `Diagram` — the component embeds a diagram, elaborated via
488    ///   [`build_diagram_from_diagram_source`](Self::build_diagram_from_diagram_source).
489    ///
490    /// # Errors
491    ///
492    /// Returns `E307` if the type is not a valid shape, `E308` if the shape does not
493    /// support content but content was provided, or propagates errors from nested
494    /// elaboration.
495    fn build_component_element(
496        &mut self,
497        name: &Spanned<Id>,
498        display_name: &Option<Spanned<String>>,
499        type_spec: &parser_types::TypeSpec,
500        content: &parser_types::ComponentContent,
501        parser_elm: &parser_types::Element,
502        diagram_kind: DiagramKind,
503    ) -> Result<Element> {
504        let type_def = self.build_type_definition(type_spec)?;
505
506        let shape_def = type_def.shape_definition().map_err(|err| {
507            Diagnostic::error(err)
508                .with_code(ErrorCode::E307)
509                .with_label(type_spec.span(), "invalid shape type")
510        })?;
511
512        if !matches!(content, parser_types::ComponentContent::None) && !shape_def.supports_content()
513        {
514            let type_name = type_spec
515                .type_name
516                .as_ref()
517                .map_or(type_def.id(), |name| *name.inner());
518            return Err(Diagnostic::error(format!(
519                "shape type `{type_name}` does not support nested content"
520            ))
521            .with_code(ErrorCode::E308)
522            .with_label(parser_elm.span(), "content not supported")
523            .with_help(format!(
524                "shape `{type_name}` is content-free and cannot contain nested elements or embedded diagrams"
525            )));
526        }
527
528        let block = match content {
529            parser_types::ComponentContent::None => Block::None,
530            parser_types::ComponentContent::Scope(elements) => {
531                self.build_block_from_elements(elements, diagram_kind)?
532            }
533            parser_types::ComponentContent::Diagram(source) => {
534                Block::Diagram(self.build_diagram_from_diagram_source(source)?)
535            }
536        };
537
538        let node = Node::new(
539            *name.inner(),
540            display_name.as_ref().map(|n| n.to_string()),
541            block,
542            Rc::clone(shape_def),
543        );
544
545        Ok(Element::Node(node))
546    }
547
548    /// Builds a relation element from parser data.
549    ///
550    /// Resolves the arrow type definition, parses the arrow direction string
551    /// (`->`, `<-`, `<->`, `-`), and constructs a semantic
552    /// [`Relation`](Relation).
553    ///
554    /// # Errors
555    ///
556    /// Returns `E307` for an invalid arrow type, or `E302` for an unrecognised
557    /// arrow direction string.
558    fn build_relation_element(
559        &mut self,
560        source: &Spanned<Id>,
561        target: &Spanned<Id>,
562        relation_type: &Spanned<&str>,
563        type_spec: &parser_types::TypeSpec,
564        label: &Option<Spanned<String>>,
565    ) -> Result<Element> {
566        // Extract relation type definition from type_spec
567        let relation_type_def = self.build_type_definition(type_spec)?;
568
569        let arrow_def = relation_type_def.arrow_definition().map_err(|err| {
570            Diagnostic::error(err)
571                .with_code(ErrorCode::E307)
572                .with_label(type_spec.span(), "invalid arrow type")
573        })?;
574
575        let arrow_direction = ArrowDirection::from_str(relation_type).map_err(|_| {
576            Diagnostic::error(format!("invalid arrow direction `{relation_type}`"))
577                .with_code(ErrorCode::E302)
578                .with_label(relation_type.span(), "invalid direction")
579                .with_help("arrow direction must be `->`, `<-`, `<->`, or `-`")
580        })?;
581
582        Ok(Element::Relation(Relation::new(
583            *source.inner(),
584            *target.inner(),
585            arrow_direction,
586            label.as_ref().map(|l| l.to_string()),
587            Rc::clone(arrow_def),
588        )))
589    }
590
591    /// Builds an activate element from parser data.
592    ///
593    /// Validates that activation is only used in sequence diagrams, resolves the
594    /// activation-box type definition, and constructs a semantic
595    /// [`Activate`](Activate).
596    ///
597    /// # Errors
598    ///
599    /// Returns `E304` if the diagram is not a sequence diagram, or `E307` if the
600    /// type is not a valid activation box.
601    fn build_activate_element(
602        &mut self,
603        component: &Spanned<Id>,
604        type_spec: &parser_types::TypeSpec,
605        diagram_kind: DiagramKind,
606    ) -> Result<Element> {
607        // Only allow activate in sequence diagrams
608        if diagram_kind != DiagramKind::Sequence {
609            return Err(Diagnostic::error(
610                "activate statements are only supported in sequence diagrams",
611            )
612            .with_code(ErrorCode::E304)
613            .with_label(component.span(), "activate not allowed here")
614            .with_help("activate statements are used for temporal grouping in sequence diagrams"));
615        }
616
617        let activate_type_def = self.build_type_definition(type_spec)?;
618
619        let activation_box_def = activate_type_def
620            .activation_box_definition()
621            .map_err(|err| {
622                Diagnostic::error(err)
623                    .with_code(ErrorCode::E307)
624                    .with_label(type_spec.span(), "invalid activation box type")
625            })?;
626
627        Ok(Element::Activate(Activate::new(
628            *component.inner(),
629            Rc::clone(activation_box_def),
630        )))
631    }
632
633    /// Builds a deactivate element from parser data.
634    ///
635    /// Validates that deactivation is only used in sequence diagrams and
636    /// constructs a semantic [`Deactivate`](Element::Deactivate).
637    ///
638    /// # Errors
639    ///
640    /// Returns `E304` if the diagram is not a sequence diagram.
641    fn build_deactivate_element(
642        &mut self,
643        component: &Spanned<Id>,
644        diagram_kind: DiagramKind,
645    ) -> Result<Element> {
646        // Only allow deactivate in sequence diagrams
647        if diagram_kind != DiagramKind::Sequence {
648            return Err(Diagnostic::error(
649                "deactivate statements are only supported in sequence diagrams",
650            )
651            .with_code(ErrorCode::E304)
652            .with_label(component.span(), "deactivate not allowed here")
653            .with_help(
654                "deactivate statements are used for temporal grouping in sequence diagrams",
655            ));
656        }
657
658        Ok(Element::Deactivate(*component.inner()))
659    }
660
661    /// Builds a fragment element from parser data.
662    ///
663    /// Validates that fragments are only used in sequence diagrams, resolves the
664    /// fragment type definition, and elaborates each section's elements into
665    /// a [`FragmentSection`](FragmentSection).
666    ///
667    /// # Errors
668    ///
669    /// Returns `E304` if the diagram is not a sequence diagram, `E300` if the
670    /// fragment type is invalid, or `E307` if the type is not a fragment definition.
671    fn build_fragment_element(
672        &mut self,
673        fragment: &parser_types::Fragment,
674        diagram_kind: DiagramKind,
675    ) -> Result<Element> {
676        // Only allow fragments in sequence diagrams
677        if diagram_kind != DiagramKind::Sequence {
678            return Err(Diagnostic::error(
679                "fragment blocks are only supported in sequence diagrams",
680            )
681            .with_code(ErrorCode::E304)
682            .with_label(fragment.span(), "fragment not allowed here")
683            .with_help("fragment blocks are used for grouping in sequence diagrams"));
684        }
685
686        // Build the type definition for this fragment
687        let type_def = self
688            .build_type_definition(&fragment.type_spec)
689            .map_err(|_| {
690                Diagnostic::error(format!(
691                    "invalid fragment type for operation `{}`",
692                    fragment.operation.inner()
693                ))
694                .with_code(ErrorCode::E300)
695                .with_label(fragment.operation.span(), "invalid fragment type")
696                .with_help("fragment types must be defined in the type system")
697            })?;
698
699        let fragment_def = type_def.fragment_definition().map_err(|err| {
700            Diagnostic::error(err)
701                .with_code(ErrorCode::E307)
702                .with_label(fragment.type_spec.span(), "invalid fragment type")
703        })?;
704
705        let mut sections = Vec::new();
706        for parser_section in &fragment.sections {
707            let scope = self.build_scope_from_elements(&parser_section.elements, diagram_kind)?;
708            let elements_vec = scope.elements().to_vec();
709
710            sections.push(FragmentSection::new(
711                parser_section.title.as_ref().map(|t| t.inner().to_string()),
712                elements_vec,
713            ));
714        }
715
716        Ok(Element::Fragment(Fragment::new(
717            fragment.operation.inner().to_string(),
718            sections,
719            Rc::clone(fragment_def),
720        )))
721    }
722
723    fn build_type_definition(
724        &mut self,
725        type_spec: &parser_types::TypeSpec,
726    ) -> Result<elaborate_utils::TypeDefinition> {
727        let type_name = type_spec.type_name.as_ref().ok_or_else(|| {
728            Diagnostic::error("base type `type_spec` must have a type name")
729                .with_code(ErrorCode::E306)
730                .with_label(type_spec.span(), "missing type name")
731        })?;
732        // Look up the base type
733        let Some(base) = self.type_definitions.get(type_name.inner()) else {
734            return Err(
735                self.create_undefined_type_error(type_name, &format!("unknown type `{type_name}`"))
736            );
737        };
738
739        let attributes = &type_spec.attributes;
740        // If there are no attributes, just return the base type
741        if attributes.is_empty() {
742            return Ok(base.clone());
743        }
744
745        // Otherwise, create a new anonymous type based on the base type
746        let id = Id::from_anonymous();
747        let new_type = self.build_type_from_base(id, base, attributes)?;
748        self.insert_type_definition(new_type, type_name.span())
749    }
750
751    /// Resolve a text type reference and apply inline attribute overrides.
752    ///
753    /// # Arguments
754    /// * `type_spec` - The type specification with optional type name and attributes
755    /// * `current_text` - The current text definition reference from the host shape
756    fn resolve_text_type_reference(
757        &self,
758        type_spec: &parser_types::TypeSpec,
759        current_text_rc: &Rc<TextDefinition>,
760    ) -> Result<Rc<TextDefinition>> {
761        // Step 1: Determine which Rc to use (current or resolved)
762        let mut text_rc = if let Some(type_name) = &type_spec.type_name {
763            let base_type = self
764                .type_definitions
765                .get(type_name.inner())
766                .ok_or_else(|| {
767                    Diagnostic::error(format!("undefined text type `{}`", type_name.inner()))
768                        .with_code(ErrorCode::E300)
769                        .with_label(type_spec.span(), "undefined type")
770                        .with_help("type must be defined with `type` statement before use")
771                })?;
772
773            let base_text_rc = base_type.text_definition_from_draw().map_err(|err| {
774                Diagnostic::error(format!(
775                    "type `{}` is not a text type: {}",
776                    type_name.inner(),
777                    err
778                ))
779                .with_code(ErrorCode::E307)
780                .with_label(type_spec.span(), "invalid type reference")
781                .with_help("only `Text` types can be used for text attributes")
782            })?;
783
784            Rc::clone(base_text_rc)
785        } else {
786            Rc::clone(current_text_rc)
787        };
788
789        // Step 2: If attributes exist, make mutable and apply them
790        if !type_spec.attributes.is_empty() {
791            let text_def_mut = Rc::make_mut(&mut text_rc);
792            TextAttributeExtractor::extract_text_attributes(text_def_mut, &type_spec.attributes)?;
793        }
794
795        Ok(text_rc)
796    }
797
798    /// Resolve a stroke type reference and apply inline attribute overrides.
799    ///
800    /// # Arguments
801    /// * `type_spec` - The type specification with optional type name and attributes
802    /// * `current_stroke` - The current stroke definition reference from the host shape
803    fn resolve_stroke_type_reference(
804        &self,
805        type_spec: &parser_types::TypeSpec,
806        current_stroke_rc: &Rc<StrokeDefinition>,
807    ) -> Result<Rc<StrokeDefinition>> {
808        // Step 1: Determine which Rc to use (current or resolved)
809        let mut stroke_rc = if let Some(type_name) = &type_spec.type_name {
810            let base_type = self
811                .type_definitions
812                .get(type_name.inner())
813                .ok_or_else(|| {
814                    Diagnostic::error(format!("undefined stroke type `{}`", type_name.inner()))
815                        .with_code(ErrorCode::E300)
816                        .with_label(type_spec.span(), "undefined type")
817                        .with_help("type must be defined with `type` statement before use")
818                })?;
819
820            let base_stroke_rc = base_type.stroke_definition().map_err(|err| {
821                Diagnostic::error(format!(
822                    "type `{}` is not a stroke type: {}",
823                    type_name.inner(),
824                    err
825                ))
826                .with_code(ErrorCode::E307)
827                .with_label(type_spec.span(), "invalid type reference")
828                .with_help("only `Stroke` types can be used for stroke attributes")
829            })?;
830
831            Rc::clone(base_stroke_rc)
832        } else {
833            Rc::clone(current_stroke_rc)
834        };
835
836        // Step 2: If attributes exist, make mutable and apply them
837        if !type_spec.attributes.is_empty() {
838            let stroke_def_mut = Rc::make_mut(&mut stroke_rc);
839            StrokeAttributeExtractor::extract_stroke_attributes(
840                stroke_def_mut,
841                &type_spec.attributes,
842            )?;
843        }
844
845        Ok(stroke_rc)
846    }
847
848    /// Build a new type definition from a base type with additional attributes.
849    /// This method handles type composition and attribute inheritance with integrated
850    /// type reference resolution for text and stroke attributes.
851    fn build_type_from_base(
852        &self,
853        id: Id,
854        base: &elaborate_utils::TypeDefinition,
855        attributes: &[parser_types::Attribute],
856    ) -> Result<elaborate_utils::TypeDefinition> {
857        match base.draw_definition() {
858            elaborate_utils::DrawDefinition::Shape(shape_def) => {
859                let mut new_shape_def = Rc::clone(shape_def);
860                let shape_def_mut = Rc::make_mut(&mut new_shape_def);
861
862                for attr in attributes {
863                    let name = attr.name.inner();
864
865                    match *name {
866                        "fill_color" => {
867                            let color = Self::extract_color(attr, "fill_color")?;
868                            shape_def_mut.set_fill_color(Some(color)).map_err(|err| {
869                                Diagnostic::error(err.to_string())
870                                    .with_code(ErrorCode::E304)
871                                    .with_label(attr.span(), "unsupported attribute")
872                            })?;
873                        }
874                        "stroke" => {
875                            let type_spec = Self::extract_type_spec(attr, "stroke")?;
876                            let stroke_rc = self
877                                .resolve_stroke_type_reference(type_spec, shape_def_mut.stroke())?;
878                            shape_def_mut.set_stroke(stroke_rc);
879                        }
880                        "rounded" => {
881                            let val =
882                                Self::extract_usize(attr, "rounded", "must be a positive number")?;
883                            shape_def_mut.set_rounded(val).map_err(|err| {
884                                Diagnostic::error(err.to_string())
885                                    .with_code(ErrorCode::E304)
886                                    .with_label(attr.span(), "unsupported attribute")
887                            })?;
888                        }
889                        "text" => {
890                            let type_spec = Self::extract_type_spec(attr, "text")?;
891                            let text_rc =
892                                self.resolve_text_type_reference(type_spec, shape_def_mut.text())?;
893                            shape_def_mut.set_text(text_rc);
894                        }
895                        name => {
896                            return Err(Diagnostic::error(format!(
897                                "unknown shape attribute `{name}`"
898                            ))
899                            .with_code(ErrorCode::E303)
900                            .with_label(attr.span(), "unknown attribute")
901                            .with_help(
902                                "valid shape attributes are: `fill_color`, `stroke`=[...], `rounded`, `text`=[...]",
903                            ));
904                        }
905                    }
906                }
907
908                Ok(elaborate_utils::TypeDefinition::new_shape(
909                    id,
910                    new_shape_def,
911                ))
912            }
913            elaborate_utils::DrawDefinition::Arrow(arrow_def) => {
914                let mut new_arrow_def = Rc::clone(arrow_def);
915                let arrow_def_mut = Rc::make_mut(&mut new_arrow_def);
916
917                for attr in attributes {
918                    let name = attr.name.inner();
919
920                    match *name {
921                        "stroke" => {
922                            let type_spec = Self::extract_type_spec(attr, "stroke")?;
923                            let stroke_rc = self
924                                .resolve_stroke_type_reference(type_spec, arrow_def_mut.stroke())?;
925                            arrow_def_mut.set_stroke(stroke_rc);
926                        }
927                        "style" => {
928                            let style_str = Self::extract_string(attr, "style")?;
929                            let val = ArrowStyle::from_str(style_str).map_err(|_| {
930                                Diagnostic::error("invalid arrow style")
931                                    .with_code(ErrorCode::E302)
932                                    .with_label(attr.span(), "invalid style")
933                                    .with_help(
934                                        "arrow style must be `straight`, `curved`, or `orthogonal`",
935                                    )
936                            })?;
937                            arrow_def_mut.set_style(val);
938                        }
939                        "text" => {
940                            let type_spec = Self::extract_type_spec(attr, "text")?;
941                            let text_rc =
942                                self.resolve_text_type_reference(type_spec, arrow_def_mut.text())?;
943                            arrow_def_mut.set_text(text_rc);
944                        }
945                        name => {
946                            return Err(Diagnostic::error(format!(
947                                "unknown arrow attribute `{name}`"
948                            ))
949                            .with_code(ErrorCode::E303)
950                            .with_label(attr.span(), "unknown attribute")
951                            .with_help(
952                                "valid arrow attributes are: `stroke`=[...], `style`, `text`=[...]",
953                            ));
954                        }
955                    }
956                }
957
958                Ok(elaborate_utils::TypeDefinition::new_arrow(
959                    id,
960                    new_arrow_def,
961                ))
962            }
963            elaborate_utils::DrawDefinition::Fragment(fragment_def) => {
964                let mut new_fragment_def = Rc::clone(fragment_def);
965                let fragment_def_mut = Rc::make_mut(&mut new_fragment_def);
966
967                for attr in attributes {
968                    let name = attr.name.inner();
969
970                    match *name {
971                        "border_stroke" => {
972                            let type_spec = Self::extract_type_spec(attr, "border_stroke")?;
973                            let stroke_rc = self.resolve_stroke_type_reference(
974                                type_spec,
975                                fragment_def_mut.border_stroke(),
976                            )?;
977                            fragment_def_mut.set_border_stroke(stroke_rc);
978                        }
979                        "background_color" => {
980                            let color = Self::extract_color(attr, "background_color")?;
981                            fragment_def_mut.set_background_color(Some(color));
982                        }
983                        "separator_stroke" => {
984                            let type_spec = Self::extract_type_spec(attr, "separator_stroke")?;
985                            let stroke_rc = self.resolve_stroke_type_reference(
986                                type_spec,
987                                fragment_def_mut.separator_stroke(),
988                            )?;
989                            fragment_def_mut.set_separator_stroke(stroke_rc);
990                        }
991                        "operation_label_text" => {
992                            let type_spec = Self::extract_type_spec(attr, "operation_label_text")?;
993                            let text_rc = self.resolve_text_type_reference(
994                                type_spec,
995                                fragment_def_mut.operation_label_text(),
996                            )?;
997                            fragment_def_mut.set_operation_label_text(text_rc);
998                        }
999                        "section_title_text" => {
1000                            let type_spec = Self::extract_type_spec(attr, "section_title_text")?;
1001                            let text_rc = self.resolve_text_type_reference(
1002                                type_spec,
1003                                fragment_def_mut.section_title_text(),
1004                            )?;
1005                            fragment_def_mut.set_section_title_text(text_rc);
1006                        }
1007                        name => {
1008                            return Err(Diagnostic::error(format!(
1009                                "unknown fragment attribute `{name}`"
1010                            ))
1011                            .with_code(ErrorCode::E303)
1012                            .with_label(attr.span(), "unknown attribute")
1013                            .with_help("valid fragment attributes are: `border_stroke`=[...], `separator_stroke`=[...], `background_color`, `operation_label_text`=[...], `section_title_text`=[...]"));
1014                        }
1015                    }
1016                }
1017
1018                Ok(elaborate_utils::TypeDefinition::new_fragment(
1019                    id,
1020                    new_fragment_def,
1021                ))
1022            }
1023            elaborate_utils::DrawDefinition::Note(note_def) => {
1024                let mut new_note_def = Rc::clone(note_def);
1025                let note_def_mut = Rc::make_mut(&mut new_note_def);
1026
1027                for attr in attributes {
1028                    let name = attr.name.inner();
1029
1030                    match *name {
1031                        "background_color" => {
1032                            let color = Self::extract_color(attr, "background_color")?;
1033                            note_def_mut.set_background_color(Some(color));
1034                        }
1035                        "stroke" => {
1036                            let type_spec = Self::extract_type_spec(attr, "stroke")?;
1037                            let stroke_rc = self
1038                                .resolve_stroke_type_reference(type_spec, note_def_mut.stroke())?;
1039                            note_def_mut.set_stroke(stroke_rc);
1040                        }
1041                        "text" => {
1042                            let type_spec = Self::extract_type_spec(attr, "text")?;
1043                            let text_rc =
1044                                self.resolve_text_type_reference(type_spec, note_def_mut.text())?;
1045                            note_def_mut.set_text(text_rc);
1046                        }
1047                        "on" | "align" => {
1048                            // Skip positioning attributes - these are handled by build_note_element
1049                            // and are not part of the note's styling definition
1050                        }
1051                        name => {
1052                            return Err(Diagnostic::error(format!(
1053                                "unknown note attribute `{name}`"
1054                            ))
1055                            .with_code(ErrorCode::E303)
1056                            .with_label(attr.span(), "unknown attribute")
1057                            .with_help(
1058                                "valid note attributes are: `background_color`, `stroke`=[...], `text`=[...]",
1059                            ));
1060                        }
1061                    }
1062                }
1063
1064                Ok(elaborate_utils::TypeDefinition::new_note(id, new_note_def))
1065            }
1066            elaborate_utils::DrawDefinition::ActivationBox(activation_box_def) => {
1067                let mut new_activation_box_def = Rc::clone(activation_box_def);
1068                let activation_box_def_mut = Rc::make_mut(&mut new_activation_box_def);
1069
1070                for attr in attributes {
1071                    let name = attr.name.inner();
1072
1073                    match *name {
1074                        "width" => {
1075                            let val = Self::extract_positive_float(attr, "width")?;
1076                            activation_box_def_mut.set_width(val);
1077                        }
1078                        "nesting_offset" => {
1079                            let val = Self::extract_positive_float(attr, "nesting_offset")?;
1080                            activation_box_def_mut.set_nesting_offset(val);
1081                        }
1082                        "fill_color" => {
1083                            let color = Self::extract_color(attr, "fill_color")?;
1084                            activation_box_def_mut.set_fill_color(color);
1085                        }
1086                        "stroke" => {
1087                            let type_spec = Self::extract_type_spec(attr, "stroke")?;
1088                            let stroke_rc = self.resolve_stroke_type_reference(
1089                                type_spec,
1090                                activation_box_def_mut.stroke(),
1091                            )?;
1092                            activation_box_def_mut.set_stroke(stroke_rc);
1093                        }
1094                        name => {
1095                            return Err(Diagnostic::error(format!(
1096                                "unknown activation box attribute `{name}`"
1097                            ))
1098                            .with_code(ErrorCode::E303)
1099                            .with_label(attr.span(), "unknown attribute")
1100                            .with_help("valid activation box attributes are: `width`, `nesting_offset`, `fill_color`, `stroke`=[...]"));
1101                        }
1102                    }
1103                }
1104
1105                Ok(elaborate_utils::TypeDefinition::new_activation_box(
1106                    id,
1107                    new_activation_box_def,
1108                ))
1109            }
1110            elaborate_utils::DrawDefinition::Stroke(stroke_def) => {
1111                let mut new_stroke = (**stroke_def).clone();
1112                StrokeAttributeExtractor::extract_stroke_attributes(&mut new_stroke, attributes)?;
1113                Ok(elaborate_utils::TypeDefinition::new_stroke(id, new_stroke))
1114            }
1115            elaborate_utils::DrawDefinition::Text(text_def) => {
1116                let mut new_text_def = (**text_def).clone();
1117                TextAttributeExtractor::extract_text_attributes(&mut new_text_def, attributes)?;
1118                Ok(elaborate_utils::TypeDefinition::new_text(id, new_text_def))
1119            }
1120        }
1121    }
1122
1123    /// Creates a standardized error for undefined type situations.
1124    fn create_undefined_type_error(&self, span: &Spanned<Id>, message: &str) -> Diagnostic {
1125        Diagnostic::error(message)
1126            .with_code(ErrorCode::E300)
1127            .with_label(span.span(), "undefined type")
1128            .with_help(format!(
1129                "type `{}` must be a built-in type or defined with a `type` statement before it can be used as a base type",
1130                span.inner()
1131            ))
1132    }
1133
1134    /// Extract diagram attributes (layout engine, background color, and lifeline definition).
1135    fn extract_diagram_attributes(
1136        &self,
1137        kind: DiagramKind,
1138        attrs: &Vec<parser_types::Attribute<'_>>,
1139    ) -> Result<(LayoutEngine, Option<Color>, Option<Rc<LifelineDefinition>>)> {
1140        // Set the default layout engine based on the diagram kind and config
1141        let mut layout_engine = match kind {
1142            DiagramKind::Component => self.cfg.component_layout,
1143            DiagramKind::Sequence => self.cfg.sequence_layout,
1144        };
1145
1146        let mut background_color = None;
1147        let mut lifeline_definition = None;
1148
1149        // Single pass through the attributes to extract both values
1150        for attr in attrs {
1151            match *attr.name {
1152                "layout_engine" => {
1153                    layout_engine = Self::determine_layout_engine(attr)?;
1154                }
1155                "background_color" => {
1156                    let color = Self::extract_background_color(attr)?;
1157                    background_color = Some(color);
1158                }
1159                "lifeline" => {
1160                    // Only valid for sequence diagrams
1161                    if kind != DiagramKind::Sequence {
1162                        return Err(Diagnostic::error(
1163                            "`lifeline` attribute is only valid for sequence diagrams",
1164                        )
1165                        .with_code(ErrorCode::E304)
1166                        .with_label(attr.span(), "invalid attribute"));
1167                    }
1168                    let definition = self.extract_lifeline_definition(attr)?;
1169                    lifeline_definition = Some(Rc::new(definition));
1170                }
1171                _ => {
1172                    return Err(Diagnostic::error(format!(
1173                        "unsupported diagram attribute `{}`",
1174                        attr.name
1175                    ))
1176                    .with_code(ErrorCode::E303)
1177                    .with_label(attr.span(), "unsupported attribute"));
1178                }
1179            }
1180        }
1181
1182        Ok((layout_engine, background_color, lifeline_definition))
1183    }
1184
1185    /// Extract background color from an attribute.
1186    fn extract_background_color(color_attr: &parser_types::Attribute<'_>) -> Result<Color> {
1187        Self::extract_color(color_attr, "background_color")
1188    }
1189
1190    /// Extract lifeline definition from an attribute.
1191    fn extract_lifeline_definition(
1192        &self,
1193        lifeline_attr: &parser_types::Attribute<'_>,
1194    ) -> Result<LifelineDefinition> {
1195        let type_spec = Self::extract_type_spec(lifeline_attr, "lifeline")?;
1196
1197        // Start with default lifeline stroke
1198        let default_stroke_rc = Rc::new(StrokeDefinition::dashed(Color::default(), 1.0));
1199
1200        // Look for stroke attribute
1201        let stroke_rc =
1202            if let Some(stroke_attr) = type_spec.attributes.iter().find(|a| *a.name == "stroke") {
1203                let stroke_type_spec = Self::extract_type_spec(stroke_attr, "stroke")?;
1204
1205                self.resolve_stroke_type_reference(stroke_type_spec, &default_stroke_rc)?
1206            } else if !type_spec.attributes.is_empty() {
1207                return Err(Diagnostic::error(format!(
1208                    "unknown lifeline attribute `{}`",
1209                    type_spec.attributes[0].name
1210                ))
1211                .with_code(ErrorCode::E303)
1212                .with_label(type_spec.attributes[0].span(), "unknown attribute")
1213                .with_help("valid lifeline attributes are: `stroke`=[...]"));
1214            } else {
1215                default_stroke_rc
1216            };
1217
1218        Ok(LifelineDefinition::new(stroke_rc))
1219    }
1220
1221    /// Determines the layout engine from an attribute.
1222    fn determine_layout_engine(engine_attr: &parser_types::Attribute<'_>) -> Result<LayoutEngine> {
1223        let engine_str = Self::extract_string(engine_attr, "layout_engine")?;
1224        LayoutEngine::from_str(engine_str).map_err(|_| {
1225            Diagnostic::error(format!("invalid `layout_engine` value: `{engine_str}`"))
1226                .with_code(ErrorCode::E302)
1227                .with_label(engine_attr.value.span(), "unsupported layout engine")
1228                .with_help("supported layout engines are: `basic`, `sugiyama`")
1229        })
1230    }
1231
1232    /// Build a note element from parser types.
1233    ///
1234    /// Converts a parsed note element into an elaborated note with:
1235    /// - Type definition for styling
1236    /// - Element IDs for attachment (from 'on' attribute)
1237    /// - Alignment with diagram-specific defaults (from 'align' attribute)
1238    /// - Text content
1239    ///
1240    /// # Arguments
1241    ///
1242    /// * `note` - Parsed note element from the parser
1243    /// * `diagram_kind` - Diagram type (determines default alignment)
1244    ///
1245    /// # Returns
1246    ///
1247    /// Returns an `Element::Note`
1248    fn build_note_element(
1249        &mut self,
1250        note: &parser_types::Note,
1251        diagram_kind: DiagramKind,
1252    ) -> Result<Element> {
1253        let type_def = self.build_type_definition(&note.type_spec)?;
1254
1255        // Extract 'on' and 'align' attributes
1256        let (on, align) = self.extract_note_attributes(&note.type_spec.attributes, diagram_kind)?;
1257
1258        let content = note.content.inner().to_string();
1259
1260        // Extract NoteDefinition from TypeDefinition
1261        let note_def_ref = type_def.note_definition().map_err(|err| {
1262            Diagnostic::error(err)
1263                .with_code(ErrorCode::E307)
1264                .with_label(note.content.span(), "invalid note type")
1265        })?;
1266        let note_def = Rc::clone(note_def_ref);
1267
1268        Ok(Element::Note(Note::new(on, align, content, note_def)))
1269    }
1270
1271    /// Extract 'on' and 'align' attributes from note attributes.
1272    ///
1273    /// This method extracts:
1274    /// - `on`: List of element identifiers converted to IDs
1275    /// - `align`: Alignment string parsed to NoteAlign enum
1276    ///
1277    /// # Arguments
1278    ///
1279    /// * `attributes` - Note attributes from the parser
1280    /// * `diagram_kind` - Diagram type (determines default alignment if not specified)
1281    ///
1282    /// # Returns
1283    ///
1284    /// Returns `(Vec<Id>, NoteAlign)` tuple with:
1285    /// - Element IDs (empty vec for margin notes)
1286    /// - Alignment
1287    fn extract_note_attributes(
1288        &mut self,
1289        attributes: &[parser_types::Attribute],
1290        diagram_kind: DiagramKind,
1291    ) -> Result<(Vec<Id>, NoteAlign)> {
1292        let mut on: Option<Vec<Id>> = None;
1293        let mut align: Option<NoteAlign> = None;
1294
1295        for attr in attributes {
1296            match *attr.name.inner() {
1297                "on" => {
1298                    let ids = attr.value.as_identifiers().map_err(|_| {
1299                        Diagnostic::error("`on` attribute must be a list of element identifiers")
1300                            .with_code(ErrorCode::E302)
1301                            .with_label(attr.value.span(), "invalid on value")
1302                            .with_help("use syntax: `on=[element1, element2]`")
1303                    })?;
1304
1305                    on = Some(ids.iter().map(|id| *id.inner()).collect());
1306                }
1307                "align" => {
1308                    let align_str = Self::extract_string(attr, "align")?;
1309
1310                    let alignment = align_str.parse::<NoteAlign>().map_err(|_| {
1311                        Diagnostic::error(format!("invalid alignment value: `{}`", align_str))
1312                            .with_code(ErrorCode::E302)
1313                            .with_label(attr.value.span(), "invalid alignment")
1314                            .with_help("valid values: over, left, right, top, bottom")
1315                    })?;
1316
1317                    align = Some(alignment);
1318                }
1319                _ => {} // Ignore other attributes (handled by build_type_definition)
1320            }
1321        }
1322
1323        // Apply defaults if not specified
1324        let on = on.unwrap_or_default();
1325        let align = align.unwrap_or(match diagram_kind {
1326            DiagramKind::Sequence => NoteAlign::Over,
1327            DiagramKind::Component => NoteAlign::Bottom,
1328        });
1329
1330        Ok((on, align))
1331    }
1332}
1333
1334#[cfg(test)]
1335mod tests {
1336    use std::cell::RefCell;
1337
1338    use super::*;
1339
1340    /// Creates a [`Builder`] pre-populated with the built-in type definitions.
1341    fn builder_with_builtins() -> Builder {
1342        let mut builder = Builder::new(ElaborateConfig::default());
1343        builder.type_definitions = Builder::builtin_type_definitions_map();
1344        builder
1345    }
1346
1347    #[test]
1348    #[should_panic(expected = "ActivateBlock should have been desugared")]
1349    fn test_activate_block_panics_in_elaboration() {
1350        // Build a parser_types diagram directly with an ActivateBlock element
1351        let elements = vec![parser_types::Element::ActivateBlock {
1352            component: Spanned::new(Id::new("user"), Span::new(0..4)),
1353            type_spec: parser_types::TypeSpec::default(),
1354            elements: vec![],
1355        }];
1356
1357        let diagram = parser_types::FileAst {
1358            header: parser_types::FileHeader::Diagram {
1359                kind: Spanned::new(DiagramKind::Component, Span::new(0..9)),
1360                attributes: vec![],
1361            },
1362            import_decls: vec![],
1363            type_definitions: vec![],
1364            elements,
1365            imports: vec![],
1366        };
1367
1368        let config = ElaborateConfig::default();
1369        let builder = Builder::new(config);
1370        // This should panic due to unreachable!() on ActivateBlock during elaboration
1371        let _ = builder.build(&diagram);
1372    }
1373
1374    #[test]
1375    fn test_explicit_activation_scoping_behavior() {
1376        // Test that sequence diagrams don't create namespace scopes within activate blocks
1377        let elements = vec![
1378            parser_types::Element::Activate {
1379                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1380                type_spec: parser_types::TypeSpec {
1381                    type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1382                    attributes: vec![],
1383                },
1384            },
1385            parser_types::Element::Relation {
1386                source: Spanned::new(Id::new("user"), Span::new(0..4)),
1387                target: Spanned::new(Id::new("server"), Span::new(0..6)),
1388                relation_type: Spanned::new("->", Span::new(0..2)),
1389                type_spec: parser_types::TypeSpec {
1390                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1391                    attributes: vec![],
1392                },
1393                label: Some(Spanned::new("Request".to_string(), Span::new(0..7))),
1394            },
1395            parser_types::Element::Relation {
1396                source: Spanned::new(Id::new("server"), Span::new(0..6)),
1397                target: Spanned::new(Id::new("database"), Span::new(0..8)),
1398                relation_type: Spanned::new("->", Span::new(0..2)),
1399                type_spec: parser_types::TypeSpec {
1400                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1401                    attributes: vec![],
1402                },
1403                label: Some(Spanned::new("Query".to_string(), Span::new(0..5))),
1404            },
1405            parser_types::Element::Deactivate {
1406                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1407            },
1408        ];
1409
1410        let diagram = parser_types::FileAst {
1411            header: parser_types::FileHeader::Diagram {
1412                kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
1413                attributes: vec![],
1414            },
1415            import_decls: vec![],
1416            type_definitions: vec![],
1417            elements,
1418            imports: vec![],
1419        };
1420
1421        let config = ElaborateConfig::default();
1422        let builder = Builder::new(config);
1423        let result = builder.build(&diagram);
1424
1425        assert!(
1426            result.is_ok(),
1427            "Sequence diagram with activate block should work"
1428        );
1429
1430        let diagram = result.unwrap();
1431        // After desugaring, relations remain unscoped; ensure names were not prefixed
1432        for element in diagram.scope().elements() {
1433            if let Element::Relation(relation) = element {
1434                // Relations should maintain original naming, not be scoped under "user"
1435                let source_str = relation.source().to_string();
1436                let target_str = relation.target().to_string();
1437                assert!(
1438                    !source_str.starts_with("user::user::"),
1439                    "Source should not be double-scoped: {}",
1440                    source_str
1441                );
1442                assert!(
1443                    !target_str.starts_with("user::server::"),
1444                    "Target should not be double-scoped: {}",
1445                    target_str
1446                );
1447            }
1448        }
1449    }
1450
1451    #[test]
1452    fn test_nested_explicit_activations_same_component() {
1453        // Test that nested activate blocks work and same component can be activated multiple times
1454        let elements = vec![
1455            parser_types::Element::Activate {
1456                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1457                type_spec: parser_types::TypeSpec {
1458                    type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1459                    attributes: vec![],
1460                },
1461            },
1462            parser_types::Element::Relation {
1463                source: Spanned::new(Id::new("user"), Span::new(0..4)),
1464                target: Spanned::new(Id::new("server"), Span::new(0..6)),
1465                relation_type: Spanned::new("->", Span::new(0..2)),
1466                type_spec: parser_types::TypeSpec {
1467                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1468                    attributes: vec![],
1469                },
1470                label: Some(Spanned::new(
1471                    "Initial request".to_string(),
1472                    Span::new(0..16),
1473                )),
1474            },
1475            parser_types::Element::Activate {
1476                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1477                type_spec: parser_types::TypeSpec {
1478                    type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1479                    attributes: vec![],
1480                },
1481            },
1482            parser_types::Element::Relation {
1483                source: Spanned::new(Id::new("user"), Span::new(0..4)),
1484                target: Spanned::new(Id::new("database"), Span::new(0..8)),
1485                relation_type: Spanned::new("->", Span::new(0..2)),
1486                type_spec: parser_types::TypeSpec {
1487                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1488                    attributes: vec![],
1489                },
1490                label: Some(Spanned::new("Direct query".to_string(), Span::new(0..12))),
1491            },
1492            parser_types::Element::Deactivate {
1493                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1494            },
1495            parser_types::Element::Activate {
1496                component: Spanned::new(Id::new("server"), Span::new(0..6)),
1497                type_spec: parser_types::TypeSpec {
1498                    type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1499                    attributes: vec![],
1500                },
1501            },
1502            parser_types::Element::Relation {
1503                source: Spanned::new(Id::new("server"), Span::new(0..6)),
1504                target: Spanned::new(Id::new("cache"), Span::new(0..5)),
1505                relation_type: Spanned::new("->", Span::new(0..2)),
1506                type_spec: parser_types::TypeSpec {
1507                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1508                    attributes: vec![],
1509                },
1510                label: Some(Spanned::new("Cache lookup".to_string(), Span::new(0..12))),
1511            },
1512            parser_types::Element::Deactivate {
1513                component: Spanned::new(Id::new("server"), Span::new(0..6)),
1514            },
1515            parser_types::Element::Deactivate {
1516                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1517            },
1518        ];
1519
1520        let diagram = parser_types::FileAst {
1521            header: parser_types::FileHeader::Diagram {
1522                kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
1523                attributes: vec![],
1524            },
1525            import_decls: vec![],
1526            type_definitions: vec![],
1527            elements,
1528            imports: vec![],
1529        };
1530
1531        let config = ElaborateConfig::default();
1532        let builder = Builder::new(config);
1533        let result = builder.build(&diagram);
1534
1535        assert!(
1536            result.is_ok(),
1537            "Nested activate blocks should work: {:?}",
1538            result.err()
1539        );
1540
1541        let diagram = result.unwrap();
1542        let elems = diagram.scope().elements();
1543
1544        let activations: Vec<_> = elems
1545            .iter()
1546            .filter_map(|e| {
1547                if let Element::Activate(activate) = e {
1548                    Some(activate.component().to_string())
1549                } else {
1550                    None
1551                }
1552            })
1553            .collect();
1554        let deactivations: Vec<_> = elems
1555            .iter()
1556            .filter_map(|e| {
1557                if let Element::Deactivate(id) = e {
1558                    Some(id.to_string())
1559                } else {
1560                    None
1561                }
1562            })
1563            .collect();
1564        let relations: Vec<_> = elems
1565            .iter()
1566            .filter_map(|e| {
1567                if let Element::Relation(r) = e {
1568                    Some((r.source().to_string(), r.target().to_string()))
1569                } else {
1570                    None
1571                }
1572            })
1573            .collect();
1574
1575        assert_eq!(
1576            relations.len(),
1577            3,
1578            "Should have 3 relations after desugaring"
1579        );
1580        assert_eq!(
1581            activations.len(),
1582            3,
1583            "Should have 3 activation starts after desugaring"
1584        );
1585        assert_eq!(
1586            deactivations.len(),
1587            3,
1588            "Should have 3 activation ends after desugaring"
1589        );
1590
1591        assert_eq!(
1592            activations[0], "user",
1593            "First activation should be for 'user'"
1594        );
1595        assert_eq!(
1596            deactivations.last().unwrap(),
1597            "user",
1598            "Last deactivation should be for 'user'"
1599        );
1600    }
1601
1602    #[test]
1603    fn test_explicit_activate_in_sequence_diagram() {
1604        let config = ElaborateConfig::default();
1605        let builder = Builder::new(config);
1606
1607        // Create a simple sequence diagram with explicit activate
1608        let elements = vec![
1609            // Define a component
1610            parser_types::Element::Component {
1611                name: Spanned::new(Id::new("user"), Span::new(0..4)),
1612                display_name: None,
1613                type_spec: parser_types::TypeSpec {
1614                    type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(5..14))),
1615                    attributes: vec![],
1616                },
1617                content: parser_types::ComponentContent::None,
1618            },
1619            // Activate the component
1620            parser_types::Element::Activate {
1621                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1622                type_spec: parser_types::TypeSpec {
1623                    type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1624                    attributes: vec![],
1625                },
1626            },
1627            // Deactivate the component
1628            parser_types::Element::Deactivate {
1629                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1630            },
1631        ];
1632
1633        let diagram = parser_types::FileAst {
1634            header: parser_types::FileHeader::Diagram {
1635                kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
1636                attributes: vec![],
1637            },
1638            import_decls: vec![],
1639            type_definitions: vec![],
1640            elements,
1641            imports: vec![],
1642        };
1643
1644        let result = builder.build(&diagram);
1645        assert!(
1646            result.is_ok(),
1647            "Should successfully build sequence diagram with explicit activate/deactivate"
1648        );
1649
1650        let elaborate_diagram = result.unwrap();
1651        let elements = elaborate_diagram.scope().elements();
1652
1653        // Check that we have the expected elements
1654        assert_eq!(
1655            elements.len(),
1656            3,
1657            "Should have 3 elements: component, activate, deactivate"
1658        );
1659
1660        // Verify the activate element
1661        if let Element::Activate(activate) = &elements[1] {
1662            assert_eq!(
1663                activate.component().to_string(),
1664                "user",
1665                "Activate should reference 'user' component"
1666            );
1667        } else {
1668            panic!("Second element should be Activate");
1669        }
1670
1671        // Verify the deactivate element
1672        if let Element::Deactivate(id) = &elements[2] {
1673            assert_eq!(
1674                id.to_string(),
1675                "user",
1676                "Deactivate should reference 'user' component"
1677            );
1678        } else {
1679            panic!("Third element should be Deactivate");
1680        }
1681    }
1682
1683    #[test]
1684    fn test_explicit_activate_not_allowed_in_component_diagram() {
1685        let config = ElaborateConfig::default();
1686        let builder = Builder::new(config);
1687
1688        // Create a component diagram with explicit activate (should fail)
1689        let elements = vec![
1690            // Define a component
1691            parser_types::Element::Component {
1692                name: Spanned::new(Id::new("user"), Span::new(0..4)),
1693                display_name: None,
1694                type_spec: parser_types::TypeSpec {
1695                    type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(5..14))),
1696                    attributes: vec![],
1697                },
1698                content: parser_types::ComponentContent::None,
1699            },
1700            // Try to activate the component (should fail)
1701            parser_types::Element::Activate {
1702                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1703                type_spec: parser_types::TypeSpec::default(),
1704            },
1705        ];
1706
1707        let diagram = parser_types::FileAst {
1708            header: parser_types::FileHeader::Diagram {
1709                kind: Spanned::new(DiagramKind::Component, Span::new(0..9)),
1710                attributes: vec![],
1711            },
1712            import_decls: vec![],
1713            type_definitions: vec![],
1714            elements,
1715            imports: vec![],
1716        };
1717
1718        let result = builder.build(&diagram);
1719        assert!(
1720            result.is_err(),
1721            "Should fail to build component diagram with explicit activate"
1722        );
1723
1724        if let Err(err) = result {
1725            let error_message = format!("{}", err);
1726            assert!(
1727                error_message
1728                    .contains("activate statements are only supported in sequence diagrams"),
1729                "Error should mention that activate is not allowed in component diagrams"
1730            );
1731        }
1732    }
1733
1734    #[test]
1735    fn test_explicit_activation_timing_and_nesting() {
1736        // Test that activate blocks have proper timing based on contained messages
1737        // and correct nesting levels for nested activate blocks
1738        let elements = vec![
1739            // components
1740            parser_types::Element::Component {
1741                name: Spanned::new(Id::new("user"), Span::new(0..4)),
1742                display_name: None,
1743                type_spec: parser_types::TypeSpec {
1744                    type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1745                    attributes: vec![],
1746                },
1747                content: parser_types::ComponentContent::None,
1748            },
1749            parser_types::Element::Component {
1750                name: Spanned::new(Id::new("server"), Span::new(0..6)),
1751                display_name: None,
1752                type_spec: parser_types::TypeSpec {
1753                    type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1754                    attributes: vec![],
1755                },
1756                content: parser_types::ComponentContent::None,
1757            },
1758            parser_types::Element::Component {
1759                name: Spanned::new(Id::new("database"), Span::new(0..8)),
1760                display_name: None,
1761                type_spec: parser_types::TypeSpec {
1762                    type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1763                    attributes: vec![],
1764                },
1765                content: parser_types::ComponentContent::None,
1766            },
1767            // activations and relations
1768            parser_types::Element::Activate {
1769                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1770                type_spec: parser_types::TypeSpec {
1771                    type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1772                    attributes: vec![],
1773                },
1774            },
1775            parser_types::Element::Relation {
1776                source: Spanned::new(Id::new("user"), Span::new(0..4)),
1777                target: Spanned::new(Id::new("server"), Span::new(0..6)),
1778                relation_type: Spanned::new("->", Span::new(0..2)),
1779                type_spec: parser_types::TypeSpec {
1780                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1781                    attributes: vec![],
1782                },
1783                label: Some(Spanned::new("First request".to_string(), Span::new(0..13))),
1784            },
1785            parser_types::Element::Activate {
1786                component: Spanned::new(Id::new("server"), Span::new(0..6)),
1787                type_spec: parser_types::TypeSpec {
1788                    type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1789                    attributes: vec![],
1790                },
1791            },
1792            parser_types::Element::Relation {
1793                source: Spanned::new(Id::new("server"), Span::new(0..6)),
1794                target: Spanned::new(Id::new("database"), Span::new(0..8)),
1795                relation_type: Spanned::new("->", Span::new(0..2)),
1796                type_spec: parser_types::TypeSpec {
1797                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1798                    attributes: vec![],
1799                },
1800                label: Some(Spanned::new("Nested query".to_string(), Span::new(0..12))),
1801            },
1802            parser_types::Element::Relation {
1803                source: Spanned::new(Id::new("database"), Span::new(0..8)),
1804                target: Spanned::new(Id::new("server"), Span::new(0..6)),
1805                relation_type: Spanned::new("->", Span::new(0..2)),
1806                type_spec: parser_types::TypeSpec {
1807                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1808                    attributes: vec![],
1809                },
1810                label: Some(Spanned::new(
1811                    "Nested response".to_string(),
1812                    Span::new(0..15),
1813                )),
1814            },
1815            parser_types::Element::Deactivate {
1816                component: Spanned::new(Id::new("server"), Span::new(0..6)),
1817            },
1818            parser_types::Element::Relation {
1819                source: Spanned::new(Id::new("server"), Span::new(0..6)),
1820                target: Spanned::new(Id::new("user"), Span::new(0..4)),
1821                relation_type: Spanned::new("->", Span::new(0..2)),
1822                type_spec: parser_types::TypeSpec {
1823                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1824                    attributes: vec![],
1825                },
1826                label: Some(Spanned::new("First response".to_string(), Span::new(0..14))),
1827            },
1828            parser_types::Element::Activate {
1829                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1830                type_spec: parser_types::TypeSpec {
1831                    type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1832                    attributes: vec![],
1833                },
1834            },
1835            parser_types::Element::Relation {
1836                source: Spanned::new(Id::new("user"), Span::new(0..4)),
1837                target: Spanned::new(Id::new("server"), Span::new(0..6)),
1838                relation_type: Spanned::new("->", Span::new(0..2)),
1839                type_spec: parser_types::TypeSpec {
1840                    type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1841                    attributes: vec![],
1842                },
1843                label: Some(Spanned::new("Second request".to_string(), Span::new(0..14))),
1844            },
1845            parser_types::Element::Deactivate {
1846                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1847            },
1848            parser_types::Element::Deactivate {
1849                component: Spanned::new(Id::new("user"), Span::new(0..4)),
1850            },
1851        ];
1852
1853        let diagram = parser_types::FileAst {
1854            header: parser_types::FileHeader::Diagram {
1855                kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
1856                attributes: vec![],
1857            },
1858            import_decls: vec![],
1859            type_definitions: vec![],
1860            elements,
1861            imports: vec![],
1862        };
1863
1864        let config = ElaborateConfig::default();
1865        let builder = Builder::new(config);
1866        let result = builder.build(&diagram);
1867
1868        assert!(
1869            result.is_ok(),
1870            "Complex nested activate blocks should work: {:?}",
1871            result.err()
1872        );
1873
1874        let diagram = result.unwrap();
1875
1876        // After desugaring, ensure we have multiple relations and activation statements
1877        let elems = diagram.scope().elements();
1878        let relations = elems
1879            .iter()
1880            .filter(|e| matches!(e, Element::Relation(_)))
1881            .count();
1882        let activates = elems
1883            .iter()
1884            .filter(|e| matches!(e, Element::Activate(_)))
1885            .count();
1886        let deactivates = elems
1887            .iter()
1888            .filter(|e| matches!(e, Element::Deactivate(_)))
1889            .count();
1890
1891        assert!(
1892            relations >= 5,
1893            "Should have at least 5 relations after desugaring, found {}",
1894            relations
1895        );
1896        assert!(
1897            activates >= 3,
1898            "Should have at least 3 activates after desugaring, found {}",
1899            activates
1900        );
1901        assert!(
1902            deactivates >= 3,
1903            "Should have at least 3 deactivates after desugaring, found {}",
1904            deactivates
1905        );
1906    }
1907
1908    #[test]
1909    fn test_note_with_default_alignment_sequence() {
1910        let mut builder = builder_with_builtins();
1911
1912        let note = parser_types::Note {
1913            type_spec: parser_types::TypeSpec {
1914                type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
1915                attributes: vec![],
1916            },
1917            content: Spanned::new("Test note".to_string(), Span::new(0..9)),
1918        };
1919
1920        let diagram_kind = DiagramKind::Sequence;
1921        let result = builder.build_note_element(&note, diagram_kind);
1922
1923        assert!(result.is_ok());
1924        let element = result.unwrap();
1925        if let Element::Note(note_elem) = element {
1926            assert_eq!(note_elem.on().len(), 0); // Margin note
1927            assert_eq!(note_elem.align(), NoteAlign::Over); // Sequence default
1928            assert_eq!(note_elem.content(), "Test note");
1929        } else {
1930            panic!("Expected Note element");
1931        }
1932    }
1933
1934    #[test]
1935    fn test_note_with_default_alignment_component() {
1936        let mut builder = builder_with_builtins();
1937
1938        let note = parser_types::Note {
1939            type_spec: parser_types::TypeSpec {
1940                type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
1941                attributes: vec![],
1942            },
1943            content: Spanned::new("Test note".to_string(), Span::new(0..9)),
1944        };
1945
1946        let diagram_kind = DiagramKind::Component;
1947        let result = builder.build_note_element(&note, diagram_kind);
1948
1949        assert!(result.is_ok());
1950        let element = result.unwrap();
1951        if let Element::Note(note_elem) = element {
1952            assert_eq!(note_elem.on().len(), 0); // Margin note
1953            assert_eq!(note_elem.align(), NoteAlign::Bottom); // Component default
1954            assert_eq!(note_elem.content(), "Test note");
1955        } else {
1956            panic!("Expected Note element");
1957        }
1958    }
1959
1960    #[test]
1961    fn test_note_with_styling_attributes() {
1962        let mut builder = builder_with_builtins();
1963
1964        let attributes = vec![
1965            parser_types::Attribute {
1966                name: Spanned::new("background_color", Span::new(0..16)),
1967                value: parser_types::AttributeValue::String(Spanned::new(
1968                    "lightyellow".to_string(),
1969                    Span::new(0..11),
1970                )),
1971            },
1972            parser_types::Attribute {
1973                name: Spanned::new("stroke", Span::new(0..6)),
1974                value: parser_types::AttributeValue::TypeSpec(parser_types::TypeSpec {
1975                    type_name: None,
1976                    attributes: vec![
1977                        parser_types::Attribute {
1978                            name: Spanned::new("color", Span::new(0..5)),
1979                            value: parser_types::AttributeValue::String(Spanned::new(
1980                                "blue".to_string(),
1981                                Span::new(0..4),
1982                            )),
1983                        },
1984                        parser_types::Attribute {
1985                            name: Spanned::new("width", Span::new(0..5)),
1986                            value: parser_types::AttributeValue::Float(Spanned::new(
1987                                2.0,
1988                                Span::new(0..3),
1989                            )),
1990                        },
1991                    ],
1992                }),
1993            },
1994            parser_types::Attribute {
1995                name: Spanned::new("text", Span::new(0..4)),
1996                value: parser_types::AttributeValue::TypeSpec(parser_types::TypeSpec {
1997                    type_name: None,
1998                    attributes: vec![parser_types::Attribute {
1999                        name: Spanned::new("font_size", Span::new(0..9)),
2000                        value: parser_types::AttributeValue::Float(Spanned::new(
2001                            14.0,
2002                            Span::new(0..2),
2003                        )),
2004                    }],
2005                }),
2006            },
2007        ];
2008
2009        let note = parser_types::Note {
2010            type_spec: parser_types::TypeSpec {
2011                type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
2012                attributes,
2013            },
2014            content: Spanned::new("Styled note".to_string(), Span::new(0..11)),
2015        };
2016
2017        let diagram_kind = DiagramKind::Sequence;
2018        let result = builder.build_note_element(&note, diagram_kind);
2019
2020        assert!(result.is_ok());
2021        let element = result.unwrap();
2022        if let Element::Note(note_elem) = element {
2023            assert_eq!(note_elem.content(), "Styled note");
2024            assert_eq!(note_elem.align(), NoteAlign::Over); // Default for sequence
2025            assert_eq!(note_elem.on().len(), 0); // Margin note
2026        } else {
2027            panic!("Expected Note element");
2028        }
2029    }
2030
2031    // ============================================================================
2032    // Extraction Helper Tests
2033    // ============================================================================
2034
2035    #[test]
2036    fn test_extract_type_spec_success() {
2037        use crate::parser_types::{Attribute, AttributeValue, TypeSpec};
2038
2039        let type_spec = TypeSpec {
2040            type_name: Some(Spanned::new(Id::new("BoldText"), Span::new(0..8))),
2041            attributes: vec![],
2042        };
2043        let attr = Attribute {
2044            name: Spanned::new("text", Span::new(0..4)),
2045            value: AttributeValue::TypeSpec(type_spec),
2046        };
2047
2048        let result = Builder::extract_type_spec(&attr, "text");
2049        assert!(result.is_ok());
2050    }
2051
2052    #[test]
2053    fn test_extract_type_spec_error() {
2054        use crate::parser_types::{Attribute, AttributeValue};
2055
2056        let attr = Attribute {
2057            name: Spanned::new("text", Span::new(0..4)),
2058            value: AttributeValue::String(Spanned::new(
2059                "not a type spec".to_string(),
2060                Span::new(5..20),
2061            )),
2062        };
2063
2064        let result = Builder::extract_type_spec(&attr, "text");
2065        assert!(result.is_err());
2066        let err = result.unwrap_err();
2067        assert!(err.to_string().contains("expected type spec"));
2068    }
2069
2070    #[test]
2071    fn test_extract_string_success() {
2072        use crate::parser_types::{Attribute, AttributeValue};
2073
2074        let attr = Attribute {
2075            name: Spanned::new("style", Span::new(0..5)),
2076            value: AttributeValue::String(Spanned::new("curved".to_string(), Span::new(6..14))),
2077        };
2078
2079        let result = Builder::extract_string(&attr, "style");
2080        assert!(result.is_ok());
2081        assert_eq!(result.unwrap(), "curved");
2082    }
2083
2084    #[test]
2085    fn test_extract_string_error() {
2086        use crate::parser_types::{Attribute, AttributeValue};
2087
2088        let attr = Attribute {
2089            name: Spanned::new("style", Span::new(0..5)),
2090            value: AttributeValue::Float(Spanned::new(42.0, Span::new(6..8))),
2091        };
2092
2093        let result = Builder::extract_string(&attr, "style");
2094        assert!(result.is_err());
2095        let err = result.unwrap_err();
2096        assert!(err.to_string().contains("expected string value"));
2097    }
2098
2099    #[test]
2100    fn test_extract_color_success() {
2101        use crate::parser_types::{Attribute, AttributeValue};
2102
2103        let attr = Attribute {
2104            name: Spanned::new("fill_color", Span::new(0..10)),
2105            value: AttributeValue::String(Spanned::new("red".to_string(), Span::new(11..16))),
2106        };
2107
2108        let result = Builder::extract_color(&attr, "fill_color");
2109        assert!(result.is_ok());
2110    }
2111
2112    #[test]
2113    fn test_extract_color_invalid_string() {
2114        use crate::parser_types::{Attribute, AttributeValue};
2115
2116        let attr = Attribute {
2117            name: Spanned::new("fill_color", Span::new(0..10)),
2118            value: AttributeValue::Float(Spanned::new(42.0, Span::new(11..13))),
2119        };
2120
2121        let result = Builder::extract_color(&attr, "fill_color");
2122        assert!(result.is_err());
2123        let err = result.unwrap_err();
2124        assert!(err.to_string().contains("expected string value"));
2125    }
2126
2127    #[test]
2128    fn test_extract_color_invalid_color() {
2129        use crate::parser_types::{Attribute, AttributeValue};
2130
2131        let attr = Attribute {
2132            name: Spanned::new("fill_color", Span::new(0..10)),
2133            value: AttributeValue::String(Spanned::new(
2134                "not-a-color-xyz".to_string(),
2135                Span::new(11..28),
2136            )),
2137        };
2138
2139        let result = Builder::extract_color(&attr, "fill_color");
2140        assert!(result.is_err());
2141        let err = result.unwrap_err();
2142        assert!(err.to_string().contains("invalid fill_color"));
2143    }
2144
2145    #[test]
2146    fn test_extract_positive_float_success() {
2147        use crate::parser_types::{Attribute, AttributeValue};
2148
2149        let attr = Attribute {
2150            name: Spanned::new("width", Span::new(0..5)),
2151            value: AttributeValue::Float(Spanned::new(42.5, Span::new(6..10))),
2152        };
2153
2154        let result = Builder::extract_positive_float(&attr, "width");
2155        assert!(result.is_ok());
2156        assert_eq!(result.unwrap(), 42.5);
2157    }
2158
2159    #[test]
2160    fn test_extract_positive_float_error() {
2161        use crate::parser_types::{Attribute, AttributeValue};
2162
2163        let attr = Attribute {
2164            name: Spanned::new("width", Span::new(0..5)),
2165            value: AttributeValue::String(Spanned::new(
2166                "not a number".to_string(),
2167                Span::new(6..20),
2168            )),
2169        };
2170
2171        let result = Builder::extract_positive_float(&attr, "width");
2172        assert!(result.is_err());
2173        let err = result.unwrap_err();
2174        assert!(err.to_string().contains("expected"));
2175    }
2176
2177    #[test]
2178    fn test_extract_usize_success() {
2179        use crate::parser_types::{Attribute, AttributeValue};
2180
2181        let attr = Attribute {
2182            name: Spanned::new("rounded", Span::new(0..7)),
2183            value: AttributeValue::Float(Spanned::new(10.0, Span::new(8..10))),
2184        };
2185
2186        let result = Builder::extract_usize(&attr, "rounded", "must be a positive number");
2187        assert!(result.is_ok());
2188        assert_eq!(result.unwrap(), 10);
2189    }
2190
2191    #[test]
2192    fn test_extract_usize_error() {
2193        use crate::parser_types::{Attribute, AttributeValue};
2194
2195        let attr = Attribute {
2196            name: Spanned::new("rounded", Span::new(0..7)),
2197            value: AttributeValue::String(Spanned::new(
2198                "not a number".to_string(),
2199                Span::new(8..22),
2200            )),
2201        };
2202
2203        let result = Builder::extract_usize(&attr, "rounded", "must be a positive number");
2204        assert!(result.is_err());
2205        let err = result.unwrap_err();
2206        assert!(err.to_string().contains("expected"));
2207    }
2208
2209    #[test]
2210    fn test_fragment_with_both_text_attributes() {
2211        use crate::parser_types::{Attribute, AttributeValue, TypeSpec};
2212
2213        let mut builder = builder_with_builtins();
2214
2215        // Create a fragment type with both operation_label_text and section_title_text attributes
2216        let type_spec = TypeSpec {
2217            type_name: Some(Spanned::new(Id::new("Fragment"), Span::new(0..8))),
2218            attributes: vec![
2219                Attribute {
2220                    name: Spanned::new("operation_label_text", Span::new(0..4)),
2221                    value: AttributeValue::TypeSpec(TypeSpec {
2222                        type_name: None,
2223                        attributes: vec![Attribute {
2224                            name: Spanned::new("font_size", Span::new(0..9)),
2225                            value: AttributeValue::Float(Spanned::new(14.0, Span::new(0..2))),
2226                        }],
2227                    }),
2228                },
2229                Attribute {
2230                    name: Spanned::new("section_title_text", Span::new(0..18)),
2231                    value: AttributeValue::TypeSpec(TypeSpec {
2232                        type_name: None,
2233                        attributes: vec![Attribute {
2234                            name: Spanned::new("font_size", Span::new(0..9)),
2235                            value: AttributeValue::Float(Spanned::new(12.0, Span::new(0..2))),
2236                        }],
2237                    }),
2238                },
2239            ],
2240        };
2241
2242        let result = builder.build_type_definition(&type_spec);
2243        assert!(
2244            result.is_ok(),
2245            "Failed to build type definition with both operation_label_text and section_title_text: {:?}",
2246            result.err()
2247        );
2248
2249        let type_def = result.unwrap();
2250        // Verify it's a Fragment type definition
2251        match type_def.draw_definition() {
2252            elaborate_utils::DrawDefinition::Fragment(_) => {
2253                // Success - fragment type was created with both operation_label_text and section_title_text attributes
2254            }
2255            _ => panic!("Expected Fragment draw definition"),
2256        }
2257    }
2258
2259    #[test]
2260    fn test_embedded_diagram_type_definitions_are_isolated() {
2261        // Verifies that each embedded diagram has its own isolated type definition
2262        // scope. The child diagram defines a custom type (`Database`) that is unknown
2263        // to the parent, and the parent defines a custom type (`Service`) that is
2264        // unknown to the child. Both diagrams should elaborate successfully without
2265        // their type definitions interfering with each other.
2266
2267        // --- Build the embedded diagram AST ---
2268        // diagram sequence;
2269        // type Database = Oval[fill_color="#e0f0e0"];
2270        // database: Database;
2271        let child_ast = parser_types::FileAst {
2272            header: parser_types::FileHeader::Diagram {
2273                kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
2274                attributes: vec![],
2275            },
2276            import_decls: vec![],
2277            type_definitions: vec![parser_types::TypeDefinition {
2278                name: Spanned::new(Id::new("Database"), Span::new(0..8)),
2279                type_spec: parser_types::TypeSpec {
2280                    type_name: Some(Spanned::new(Id::new("Oval"), Span::new(0..4))),
2281                    attributes: vec![parser_types::Attribute {
2282                        name: Spanned::new("fill_color", Span::new(0..10)),
2283                        value: parser_types::AttributeValue::String(Spanned::new(
2284                            "#e0f0e0".to_string(),
2285                            Span::new(0..7),
2286                        )),
2287                    }],
2288                },
2289            }],
2290            elements: vec![parser_types::Element::Component {
2291                name: Spanned::new(Id::new("database"), Span::new(0..8)),
2292                display_name: None,
2293                type_spec: parser_types::TypeSpec {
2294                    type_name: Some(Spanned::new(Id::new("Database"), Span::new(0..8))),
2295                    attributes: vec![],
2296                },
2297                content: parser_types::ComponentContent::None,
2298            }],
2299            imports: vec![],
2300        };
2301
2302        // --- Build the parent diagram AST ---
2303        // diagram component;
2304        // type Service = Rectangle[fill_color="#e6f3ff"];
2305        // gateway as "API Gateway": Service;
2306        // auth_overview as "Auth Overview": Service embed auth_flow;
2307        // gateway -> auth_overview: "Auth detail";
2308        let parent_ast = parser_types::FileAst {
2309            header: parser_types::FileHeader::Diagram {
2310                kind: Spanned::new(DiagramKind::Component, Span::new(0..9)),
2311                attributes: vec![],
2312            },
2313            import_decls: vec![],
2314            type_definitions: vec![parser_types::TypeDefinition {
2315                name: Spanned::new(Id::new("Service"), Span::new(0..7)),
2316                type_spec: parser_types::TypeSpec {
2317                    type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
2318                    attributes: vec![parser_types::Attribute {
2319                        name: Spanned::new("fill_color", Span::new(0..10)),
2320                        value: parser_types::AttributeValue::String(Spanned::new(
2321                            "#e6f3ff".to_string(),
2322                            Span::new(0..7),
2323                        )),
2324                    }],
2325                },
2326            }],
2327            elements: vec![
2328                parser_types::Element::Component {
2329                    name: Spanned::new(Id::new("gateway"), Span::new(0..7)),
2330                    display_name: Some(Spanned::new("API Gateway".to_string(), Span::new(0..11))),
2331                    type_spec: parser_types::TypeSpec {
2332                        type_name: Some(Spanned::new(Id::new("Service"), Span::new(0..7))),
2333                        attributes: vec![],
2334                    },
2335                    content: parser_types::ComponentContent::None,
2336                },
2337                parser_types::Element::Component {
2338                    name: Spanned::new(Id::new("auth_overview"), Span::new(0..13)),
2339                    display_name: Some(Spanned::new("Auth Overview".to_string(), Span::new(0..13))),
2340                    type_spec: parser_types::TypeSpec {
2341                        type_name: Some(Spanned::new(Id::new("Service"), Span::new(0..7))),
2342                        attributes: vec![],
2343                    },
2344                    content: parser_types::ComponentContent::Diagram(
2345                        parser_types::DiagramSource::Inline(Rc::new(RefCell::new(child_ast))),
2346                    ),
2347                },
2348                parser_types::Element::Relation {
2349                    source: Spanned::new(Id::new("gateway"), Span::new(0..7)),
2350                    target: Spanned::new(Id::new("auth_overview"), Span::new(0..13)),
2351                    relation_type: Spanned::new("->", Span::new(0..2)),
2352                    type_spec: parser_types::TypeSpec {
2353                        type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
2354                        attributes: vec![],
2355                    },
2356                    label: Some(Spanned::new("Auth detail".to_string(), Span::new(0..11))),
2357                },
2358            ],
2359            imports: vec![],
2360        };
2361
2362        let config = ElaborateConfig::default();
2363        let builder = Builder::new(config);
2364        let result = builder.build(&parent_ast);
2365
2366        assert!(
2367            result.is_ok(),
2368            "Embedded diagram with its own type definitions should build successfully, got: {:?}",
2369            result.err()
2370        );
2371
2372        let diagram = result.unwrap();
2373        assert_eq!(diagram.kind(), DiagramKind::Component);
2374
2375        // Verify the scope has all three elements: gateway, auth_overview, and a relation
2376        let elements = diagram.scope().elements();
2377        assert_eq!(
2378            elements.len(),
2379            3,
2380            "Expected gateway, auth_overview, and a relation"
2381        );
2382
2383        // Verify the embedded node contains a diagram block
2384        if let Element::Node(node) = &elements[1] {
2385            assert!(
2386                matches!(node.block(), Block::Diagram(_)),
2387                "auth_overview should contain an embedded diagram"
2388            );
2389        } else {
2390            panic!("Expected second element to be a Node (auth_overview)");
2391        }
2392    }
2393}