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