Skip to main content

cdx_core/content/
block.rs

1//! Content block types.
2
3use std::borrow::Cow;
4
5use serde::de;
6use serde::ser::SerializeMap;
7use serde::{Deserialize, Serialize};
8
9use super::Text;
10use crate::extensions::ExtensionBlock;
11
12/// Root content structure for a Codex document.
13///
14/// The content file contains a version identifier and an array of blocks
15/// that make up the document content.
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Content {
18    /// Content model version (e.g., "0.1").
19    pub version: String,
20
21    /// Array of content blocks.
22    pub blocks: Vec<Block>,
23}
24
25impl Content {
26    /// Create new content with the default version.
27    #[must_use]
28    pub fn new(blocks: Vec<Block>) -> Self {
29        Self {
30            version: crate::SPEC_VERSION.to_string(),
31            blocks,
32        }
33    }
34
35    /// Create empty content.
36    #[must_use]
37    pub fn empty() -> Self {
38        Self::new(Vec::new())
39    }
40
41    /// Check if the content has any blocks.
42    #[must_use]
43    pub fn is_empty(&self) -> bool {
44        self.blocks.is_empty()
45    }
46
47    /// Get the number of blocks.
48    #[must_use]
49    pub fn len(&self) -> usize {
50        self.blocks.len()
51    }
52}
53
54impl Default for Content {
55    fn default() -> Self {
56        Self::empty()
57    }
58}
59
60/// Common attributes that can appear on any block.
61#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct BlockAttributes {
64    /// Text direction.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub dir: Option<String>,
67
68    /// BCP 47 language tag.
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub lang: Option<String>,
71
72    /// Writing mode for text direction.
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub writing_mode: Option<WritingMode>,
75}
76
77impl BlockAttributes {
78    /// Check if attributes are empty (all None).
79    #[must_use]
80    pub fn is_empty(&self) -> bool {
81        self.dir.is_none() && self.lang.is_none() && self.writing_mode.is_none()
82    }
83}
84
85/// A content block in the document tree.
86///
87/// Blocks are the structural elements of a document, containing
88/// either other blocks (containers) or text content (leaves).
89///
90/// # Serialization
91///
92/// Blocks serialize as JSON objects with a `"type"` field. Core block types
93/// use camelCase names (e.g., `"paragraph"`, `"codeBlock"`). Extension blocks
94/// use colon-delimited types (e.g., `"forms:textInput"`, `"academic:theorem"`).
95#[derive(Debug, Clone, PartialEq)]
96pub enum Block {
97    /// Standard paragraph block.
98    Paragraph {
99        /// Optional unique identifier.
100        id: Option<String>,
101
102        /// Text content.
103        children: Vec<Text>,
104
105        /// Block attributes.
106        attributes: BlockAttributes,
107    },
108
109    /// Section heading (levels 1-6).
110    Heading {
111        /// Optional unique identifier.
112        id: Option<String>,
113
114        /// Heading level (1-6).
115        level: u8,
116
117        /// Text content.
118        children: Vec<Text>,
119
120        /// Block attributes.
121        attributes: BlockAttributes,
122    },
123
124    /// Ordered or unordered list.
125    List {
126        /// Optional unique identifier.
127        id: Option<String>,
128
129        /// Whether the list is ordered (numbered).
130        ordered: bool,
131
132        /// Starting number for ordered lists.
133        start: Option<u32>,
134
135        /// List items (must be `ListItem` blocks).
136        children: Vec<Block>,
137
138        /// Block attributes.
139        attributes: BlockAttributes,
140    },
141
142    /// Item within a list.
143    ListItem {
144        /// Optional unique identifier.
145        id: Option<String>,
146
147        /// Checkbox state (None = not a checkbox).
148        checked: Option<bool>,
149
150        /// Block content.
151        children: Vec<Block>,
152
153        /// Block attributes.
154        attributes: BlockAttributes,
155    },
156
157    /// Quoted content block.
158    Blockquote {
159        /// Optional unique identifier.
160        id: Option<String>,
161
162        /// Block content.
163        children: Vec<Block>,
164
165        /// Block attributes.
166        attributes: BlockAttributes,
167    },
168
169    /// Source code or preformatted text.
170    CodeBlock {
171        /// Optional unique identifier.
172        id: Option<String>,
173
174        /// Programming language identifier.
175        language: Option<String>,
176
177        /// Syntax highlighting theme.
178        highlighting: Option<String>,
179
180        /// Pre-tokenized syntax highlighting.
181        tokens: Option<Vec<CodeToken>>,
182
183        /// Code content (single text node, no marks).
184        children: Vec<Text>,
185
186        /// Block attributes.
187        attributes: BlockAttributes,
188    },
189
190    /// Thematic break between sections.
191    HorizontalRule {
192        /// Optional unique identifier.
193        id: Option<String>,
194    },
195
196    /// Embedded or referenced image.
197    Image(ImageBlock),
198
199    /// Tabular data.
200    Table {
201        /// Optional unique identifier.
202        id: Option<String>,
203
204        /// Table rows.
205        children: Vec<Block>,
206
207        /// Block attributes.
208        attributes: BlockAttributes,
209    },
210
211    /// Row within a table.
212    TableRow {
213        /// Optional unique identifier.
214        id: Option<String>,
215
216        /// Whether this is a header row.
217        header: bool,
218
219        /// Table cells.
220        children: Vec<Block>,
221
222        /// Block attributes.
223        attributes: BlockAttributes,
224    },
225
226    /// Cell within a table row.
227    TableCell(TableCellBlock),
228
229    /// Mathematical content.
230    Math(MathBlock),
231
232    /// Line break within a block.
233    Break {
234        /// Optional unique identifier.
235        id: Option<String>,
236    },
237
238    /// Definition list.
239    DefinitionList(DefinitionListBlock),
240
241    /// Definition item (term + description pair).
242    DefinitionItem {
243        /// Optional unique identifier.
244        id: Option<String>,
245
246        /// Children (typically `DefinitionTerm` and `DefinitionDescription`).
247        children: Vec<Block>,
248
249        /// Block attributes.
250        attributes: BlockAttributes,
251    },
252
253    /// Definition term.
254    DefinitionTerm {
255        /// Optional unique identifier.
256        id: Option<String>,
257
258        /// Term text content.
259        children: Vec<Text>,
260
261        /// Block attributes.
262        attributes: BlockAttributes,
263    },
264
265    /// Definition description.
266    DefinitionDescription {
267        /// Optional unique identifier.
268        id: Option<String>,
269
270        /// Description content (blocks).
271        children: Vec<Block>,
272
273        /// Block attributes.
274        attributes: BlockAttributes,
275    },
276
277    /// Scientific/technical measurement.
278    Measurement(MeasurementBlock),
279
280    /// Block-level signature.
281    Signature(SignatureBlock),
282
283    /// SVG image.
284    Svg(SvgBlock),
285
286    /// Barcode (QR, Data Matrix, etc.).
287    Barcode(BarcodeBlock),
288
289    /// Figure container.
290    Figure(FigureBlock),
291
292    /// Figure caption.
293    FigCaption(FigCaptionBlock),
294
295    /// Admonition block (note, warning, tip, etc.).
296    Admonition(AdmonitionBlock),
297
298    /// Extension block for custom/unknown block types.
299    ///
300    /// Extension blocks use namespaced types like "forms:textInput" or
301    /// "semantic:citation". When parsing, unknown types are preserved
302    /// as extension blocks with their raw attributes intact.
303    Extension(ExtensionBlock),
304}
305
306/// Image block content.
307#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
308pub struct ImageBlock {
309    /// Optional unique identifier.
310    #[serde(default, skip_serializing_if = "Option::is_none")]
311    pub id: Option<String>,
312
313    /// Image source (path or URL).
314    pub src: String,
315
316    /// Alternative text for accessibility.
317    pub alt: String,
318
319    /// Image title/caption.
320    #[serde(default, skip_serializing_if = "Option::is_none")]
321    pub title: Option<String>,
322
323    /// Intrinsic width in pixels.
324    #[serde(default, skip_serializing_if = "Option::is_none")]
325    pub width: Option<u32>,
326
327    /// Intrinsic height in pixels.
328    #[serde(default, skip_serializing_if = "Option::is_none")]
329    pub height: Option<u32>,
330}
331
332/// Table cell block content.
333#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334pub struct TableCellBlock {
335    /// Optional unique identifier.
336    #[serde(default, skip_serializing_if = "Option::is_none")]
337    pub id: Option<String>,
338
339    /// Number of columns to span.
340    #[serde(default = "default_span", skip_serializing_if = "is_default_span")]
341    pub colspan: u32,
342
343    /// Number of rows to span.
344    #[serde(default = "default_span", skip_serializing_if = "is_default_span")]
345    pub rowspan: u32,
346
347    /// Text alignment.
348    #[serde(default, skip_serializing_if = "Option::is_none")]
349    pub align: Option<CellAlign>,
350
351    /// Cell content.
352    pub children: Vec<Text>,
353
354    /// Block attributes.
355    #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
356    pub attributes: BlockAttributes,
357}
358
359fn default_span() -> u32 {
360    1
361}
362
363#[allow(clippy::trivially_copy_pass_by_ref)] // Required by serde skip_serializing_if
364fn is_default_span(span: &u32) -> bool {
365    *span == 1
366}
367
368/// Cell text alignment.
369#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
370#[serde(rename_all = "lowercase")]
371pub enum CellAlign {
372    /// Left alignment.
373    Left,
374    /// Center alignment.
375    Center,
376    /// Right alignment.
377    Right,
378}
379
380/// Mathematical content block.
381#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
382pub struct MathBlock {
383    /// Optional unique identifier.
384    #[serde(default, skip_serializing_if = "Option::is_none")]
385    pub id: Option<String>,
386
387    /// Display mode (true) vs inline (false).
388    pub display: bool,
389
390    /// Math format.
391    pub format: MathFormat,
392
393    /// Math content in the specified format.
394    pub value: String,
395}
396
397/// Mathematical content format.
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
399#[serde(rename_all = "lowercase")]
400pub enum MathFormat {
401    /// LaTeX format.
402    Latex,
403    /// `MathML` format.
404    Mathml,
405}
406
407/// Writing mode for text direction.
408///
409/// Controls the direction in which text flows within a block.
410/// This is particularly important for CJK (Chinese, Japanese, Korean)
411/// languages which can be written vertically.
412#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
413#[serde(rename_all = "kebab-case")]
414pub enum WritingMode {
415    /// Horizontal text, top-to-bottom block flow (default).
416    /// Used for Latin, Cyrillic, Arabic, Hebrew scripts.
417    #[default]
418    HorizontalTb,
419
420    /// Vertical text, right-to-left block flow.
421    /// Traditional Chinese, Japanese, Korean.
422    VerticalRl,
423
424    /// Vertical text, left-to-right block flow.
425    /// Used for Mongolian script.
426    VerticalLr,
427
428    /// Sideways text, right-to-left (90° clockwise rotation).
429    SidewaysRl,
430
431    /// Sideways text, left-to-right (90° counter-clockwise rotation).
432    SidewaysLr,
433}
434
435/// Measurement block for scientific/technical values.
436#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
437#[serde(rename_all = "camelCase")]
438pub struct MeasurementBlock {
439    /// Optional unique identifier.
440    #[serde(default, skip_serializing_if = "Option::is_none")]
441    pub id: Option<String>,
442
443    /// The numeric value.
444    pub value: f64,
445
446    /// Uncertainty value (optional).
447    #[serde(default, skip_serializing_if = "Option::is_none")]
448    pub uncertainty: Option<f64>,
449
450    /// How to display uncertainty.
451    #[serde(default, skip_serializing_if = "Option::is_none")]
452    pub uncertainty_notation: Option<UncertaintyNotation>,
453
454    /// Scientific notation exponent (optional).
455    #[serde(default, skip_serializing_if = "Option::is_none")]
456    pub exponent: Option<i32>,
457
458    /// Display format (required).
459    pub display: String,
460
461    /// Unit of measurement (optional).
462    #[serde(default, skip_serializing_if = "Option::is_none")]
463    pub unit: Option<String>,
464}
465
466impl MeasurementBlock {
467    /// Create a new measurement block.
468    #[must_use]
469    pub fn new(value: f64, display: impl Into<String>) -> Self {
470        Self {
471            id: None,
472            value,
473            uncertainty: None,
474            uncertainty_notation: None,
475            exponent: None,
476            display: display.into(),
477            unit: None,
478        }
479    }
480
481    /// Set the unit.
482    #[must_use]
483    pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
484        self.unit = Some(unit.into());
485        self
486    }
487
488    /// Set the uncertainty.
489    #[must_use]
490    pub fn with_uncertainty(mut self, uncertainty: f64, notation: UncertaintyNotation) -> Self {
491        self.uncertainty = Some(uncertainty);
492        self.uncertainty_notation = Some(notation);
493        self
494    }
495
496    /// Set the exponent.
497    #[must_use]
498    pub fn with_exponent(mut self, exponent: i32) -> Self {
499        self.exponent = Some(exponent);
500        self
501    }
502}
503
504/// Uncertainty notation format.
505#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
506#[serde(rename_all = "lowercase")]
507pub enum UncertaintyNotation {
508    /// Parenthetical notation, e.g., 1.234(5).
509    Parenthetical,
510    /// Plus-minus notation, e.g., 1.234 ± 0.005.
511    Plusminus,
512    /// Range notation, e.g., 1.229–1.239.
513    Range,
514    /// Percentage notation, e.g., 1.234 ± 0.4%.
515    Percent,
516}
517
518/// Block signature for attestation.
519#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
520#[serde(rename_all = "camelCase")]
521pub struct SignatureBlock {
522    /// Optional unique identifier.
523    #[serde(default, skip_serializing_if = "Option::is_none")]
524    pub id: Option<String>,
525
526    /// Type of signature.
527    pub signature_type: BlockSignatureType,
528
529    /// Signer information.
530    #[serde(default, skip_serializing_if = "Option::is_none")]
531    pub signer: Option<SignerDetails>,
532
533    /// Timestamp of signature.
534    #[serde(default, skip_serializing_if = "Option::is_none")]
535    pub timestamp: Option<String>,
536
537    /// Purpose of signature.
538    #[serde(default, skip_serializing_if = "Option::is_none")]
539    pub purpose: Option<SignaturePurpose>,
540
541    /// Reference to digital signature if applicable.
542    #[serde(default, skip_serializing_if = "Option::is_none")]
543    pub digital_signature_ref: Option<String>,
544}
545
546impl SignatureBlock {
547    /// Create a new signature block.
548    #[must_use]
549    pub fn new(signature_type: BlockSignatureType) -> Self {
550        Self {
551            id: None,
552            signature_type,
553            signer: None,
554            timestamp: None,
555            purpose: None,
556            digital_signature_ref: None,
557        }
558    }
559
560    /// Set the signer details.
561    #[must_use]
562    pub fn with_signer(mut self, signer: SignerDetails) -> Self {
563        self.signer = Some(signer);
564        self
565    }
566
567    /// Set the purpose.
568    #[must_use]
569    pub fn with_purpose(mut self, purpose: SignaturePurpose) -> Self {
570        self.purpose = Some(purpose);
571        self
572    }
573}
574
575/// Type of block-level signature.
576#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
577#[serde(rename_all = "lowercase")]
578pub enum BlockSignatureType {
579    /// Handwritten signature.
580    Handwritten,
581    /// Digital cryptographic signature.
582    Digital,
583    /// Electronic signature (e.g., typed name).
584    Electronic,
585    /// Stamp or seal.
586    Stamp,
587}
588
589/// Purpose of a signature.
590#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
591#[serde(rename_all = "lowercase")]
592pub enum SignaturePurpose {
593    /// Certification of accuracy.
594    Certification,
595    /// Approval of content.
596    Approval,
597    /// Witnessing.
598    Witness,
599    /// Acknowledgment of receipt/understanding.
600    Acknowledgment,
601    /// Authorship attribution.
602    Authorship,
603}
604
605/// Signer details for signatures.
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
607pub struct SignerDetails {
608    /// Signer's name.
609    pub name: String,
610
611    /// Signer's title.
612    #[serde(default, skip_serializing_if = "Option::is_none")]
613    pub title: Option<String>,
614
615    /// Signer's organization.
616    #[serde(default, skip_serializing_if = "Option::is_none")]
617    pub organization: Option<String>,
618
619    /// Signer's email.
620    #[serde(default, skip_serializing_if = "Option::is_none")]
621    pub email: Option<String>,
622
623    /// Signer's unique identifier.
624    #[serde(default, skip_serializing_if = "Option::is_none")]
625    pub id: Option<String>,
626}
627
628impl SignerDetails {
629    /// Create new signer details.
630    #[must_use]
631    pub fn new(name: impl Into<String>) -> Self {
632        Self {
633            name: name.into(),
634            title: None,
635            organization: None,
636            email: None,
637            id: None,
638        }
639    }
640
641    /// Set the title.
642    #[must_use]
643    pub fn with_title(mut self, title: impl Into<String>) -> Self {
644        self.title = Some(title.into());
645        self
646    }
647
648    /// Set the organization.
649    #[must_use]
650    pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
651        self.organization = Some(organization.into());
652        self
653    }
654}
655
656/// SVG image block.
657#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
658pub struct SvgBlock {
659    /// Optional unique identifier.
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub id: Option<String>,
662
663    /// Source reference (mutually exclusive with content).
664    #[serde(default, skip_serializing_if = "Option::is_none")]
665    pub src: Option<String>,
666
667    /// Inline SVG content (mutually exclusive with src).
668    #[serde(default, skip_serializing_if = "Option::is_none")]
669    pub content: Option<String>,
670
671    /// Width.
672    #[serde(default, skip_serializing_if = "Option::is_none")]
673    pub width: Option<u32>,
674
675    /// Height.
676    #[serde(default, skip_serializing_if = "Option::is_none")]
677    pub height: Option<u32>,
678
679    /// Alternative text for accessibility.
680    #[serde(default, skip_serializing_if = "Option::is_none")]
681    pub alt: Option<String>,
682}
683
684impl SvgBlock {
685    /// Create an SVG block from a source reference.
686    #[must_use]
687    pub fn from_src(src: impl Into<String>) -> Self {
688        Self {
689            id: None,
690            src: Some(src.into()),
691            content: None,
692            width: None,
693            height: None,
694            alt: None,
695        }
696    }
697
698    /// Create an SVG block from inline content.
699    #[must_use]
700    pub fn from_content(content: impl Into<String>) -> Self {
701        Self {
702            id: None,
703            src: None,
704            content: Some(content.into()),
705            width: None,
706            height: None,
707            alt: None,
708        }
709    }
710
711    /// Set the alt text.
712    #[must_use]
713    pub fn with_alt(mut self, alt: impl Into<String>) -> Self {
714        self.alt = Some(alt.into());
715        self
716    }
717
718    /// Set dimensions.
719    #[must_use]
720    pub fn with_dimensions(mut self, width: u32, height: u32) -> Self {
721        self.width = Some(width);
722        self.height = Some(height);
723        self
724    }
725}
726
727/// Barcode block.
728#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
729#[serde(rename_all = "camelCase")]
730pub struct BarcodeBlock {
731    /// Optional unique identifier.
732    #[serde(default, skip_serializing_if = "Option::is_none")]
733    pub id: Option<String>,
734
735    /// Barcode format.
736    pub format: BarcodeFormat,
737
738    /// Encoded data.
739    pub data: String,
740
741    /// Error correction level (for QR codes).
742    #[serde(default, skip_serializing_if = "Option::is_none")]
743    pub error_correction: Option<ErrorCorrectionLevel>,
744
745    /// Size specification.
746    #[serde(default, skip_serializing_if = "Option::is_none")]
747    pub size: Option<BarcodeSize>,
748
749    /// Quiet zone around barcode.
750    #[serde(default, skip_serializing_if = "Option::is_none")]
751    pub quiet_zone: Option<String>,
752
753    /// Alternative text (required for accessibility).
754    pub alt: String,
755}
756
757impl BarcodeBlock {
758    /// Create a new barcode block.
759    #[must_use]
760    pub fn new(format: BarcodeFormat, data: impl Into<String>, alt: impl Into<String>) -> Self {
761        Self {
762            id: None,
763            format,
764            data: data.into(),
765            error_correction: None,
766            size: None,
767            quiet_zone: None,
768            alt: alt.into(),
769        }
770    }
771
772    /// Set error correction level.
773    #[must_use]
774    pub fn with_error_correction(mut self, level: ErrorCorrectionLevel) -> Self {
775        self.error_correction = Some(level);
776        self
777    }
778
779    /// Set the size.
780    #[must_use]
781    pub fn with_size(mut self, width: String, height: String) -> Self {
782        self.size = Some(BarcodeSize { width, height });
783        self
784    }
785}
786
787/// Barcode format.
788#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
789#[serde(rename_all = "lowercase")]
790pub enum BarcodeFormat {
791    /// QR Code.
792    Qr,
793    /// Data Matrix.
794    DataMatrix,
795    /// Code 128.
796    Code128,
797    /// Code 39.
798    Code39,
799    /// EAN-13.
800    Ean13,
801    /// EAN-8.
802    Ean8,
803    /// UPC-A.
804    UpcA,
805    /// PDF417.
806    Pdf417,
807}
808
809/// Error correction level for barcodes.
810#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
811pub enum ErrorCorrectionLevel {
812    /// Low (~7% recovery).
813    L,
814    /// Medium (~15% recovery).
815    M,
816    /// Quartile (~25% recovery).
817    Q,
818    /// High (~30% recovery).
819    H,
820}
821
822/// Barcode size specification.
823#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
824pub struct BarcodeSize {
825    /// Width with units.
826    pub width: String,
827    /// Height with units.
828    pub height: String,
829}
830
831/// Figure block (container for images/diagrams with captions).
832#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
833pub struct FigureBlock {
834    /// Optional unique identifier.
835    #[serde(default, skip_serializing_if = "Option::is_none")]
836    pub id: Option<String>,
837
838    /// Figure numbering mode.
839    #[serde(default, skip_serializing_if = "Option::is_none")]
840    pub numbering: Option<FigureNumbering>,
841
842    /// Subfigures within this figure.
843    #[serde(default, skip_serializing_if = "Option::is_none")]
844    pub subfigures: Option<Vec<Subfigure>>,
845
846    /// Figure contents (usually image, svg, or other visual block).
847    pub children: Vec<Block>,
848
849    /// Block attributes.
850    #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
851    pub attributes: BlockAttributes,
852}
853
854impl FigureBlock {
855    /// Create a new figure block.
856    #[must_use]
857    pub fn new(children: Vec<Block>) -> Self {
858        Self {
859            id: None,
860            numbering: None,
861            subfigures: None,
862            children,
863            attributes: BlockAttributes::default(),
864        }
865    }
866}
867
868/// Figure numbering mode.
869#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
870#[serde(rename_all = "camelCase")]
871pub enum FigureNumbering {
872    /// Automatic numbering.
873    Auto,
874    /// No numbering.
875    #[serde(rename = "none")]
876    Unnumbered,
877    /// Explicit number.
878    #[serde(untagged)]
879    Number(u32),
880}
881
882/// A subfigure within a figure.
883#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
884pub struct Subfigure {
885    /// Optional unique identifier.
886    #[serde(default, skip_serializing_if = "Option::is_none")]
887    pub id: Option<String>,
888
889    /// Optional label (e.g., "(a)", "(b)").
890    #[serde(default, skip_serializing_if = "Option::is_none")]
891    pub label: Option<String>,
892
893    /// Subfigure content blocks.
894    pub children: Vec<Block>,
895}
896
897/// A pre-tokenized syntax highlighting token.
898#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
899#[serde(rename_all = "camelCase")]
900pub struct CodeToken {
901    /// Token type (e.g., "keyword", "string", "comment").
902    pub token_type: String,
903
904    /// Token value text.
905    pub value: String,
906
907    /// Optional scope name for the token.
908    #[serde(default, skip_serializing_if = "Option::is_none")]
909    pub scope: Option<String>,
910}
911
912/// Figure caption block.
913#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
914pub struct FigCaptionBlock {
915    /// Optional unique identifier.
916    #[serde(default, skip_serializing_if = "Option::is_none")]
917    pub id: Option<String>,
918
919    /// Caption text content.
920    pub children: Vec<Text>,
921
922    /// Block attributes.
923    #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
924    pub attributes: BlockAttributes,
925}
926
927impl FigCaptionBlock {
928    /// Create a new figure caption.
929    #[must_use]
930    pub fn new(children: Vec<Text>) -> Self {
931        Self {
932            id: None,
933            children,
934            attributes: BlockAttributes::default(),
935        }
936    }
937}
938
939/// Admonition block for callout boxes (note, warning, tip, etc.).
940#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
941#[serde(rename_all = "camelCase")]
942pub struct AdmonitionBlock {
943    /// Optional unique identifier.
944    #[serde(default, skip_serializing_if = "Option::is_none")]
945    pub id: Option<String>,
946
947    /// Admonition variant (note, tip, warning, etc.).
948    pub variant: AdmonitionVariant,
949
950    /// Optional title (if not provided, variant name is used).
951    #[serde(default, skip_serializing_if = "Option::is_none")]
952    pub title: Option<String>,
953
954    /// Content blocks within the admonition.
955    pub children: Vec<Block>,
956
957    /// Block attributes.
958    #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
959    pub attributes: BlockAttributes,
960}
961
962impl AdmonitionBlock {
963    /// Create a new admonition block.
964    #[must_use]
965    pub fn new(variant: AdmonitionVariant, children: Vec<Block>) -> Self {
966        Self {
967            id: None,
968            variant,
969            title: None,
970            children,
971            attributes: BlockAttributes::default(),
972        }
973    }
974
975    /// Set the admonition title.
976    #[must_use]
977    pub fn with_title(mut self, title: impl Into<String>) -> Self {
978        self.title = Some(title.into());
979        self
980    }
981
982    /// Set the block ID.
983    #[must_use]
984    pub fn with_id(mut self, id: impl Into<String>) -> Self {
985        self.id = Some(id.into());
986        self
987    }
988}
989
990/// Admonition variant/type.
991#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
992#[serde(rename_all = "lowercase")]
993pub enum AdmonitionVariant {
994    /// General note or information.
995    Note,
996    /// Helpful tip.
997    Tip,
998    /// Informational notice.
999    Info,
1000    /// Warning about potential issues.
1001    Warning,
1002    /// Caution about important considerations.
1003    Caution,
1004    /// Danger alert for critical issues.
1005    Danger,
1006    /// Important notice requiring attention.
1007    Important,
1008    /// Example content.
1009    Example,
1010}
1011
1012/// Definition list block.
1013#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1014pub struct DefinitionListBlock {
1015    /// Optional unique identifier.
1016    #[serde(default, skip_serializing_if = "Option::is_none")]
1017    pub id: Option<String>,
1018
1019    /// Definition items (must be `DefinitionItem` blocks).
1020    pub children: Vec<Block>,
1021
1022    /// Block attributes.
1023    #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
1024    pub attributes: BlockAttributes,
1025}
1026
1027impl DefinitionListBlock {
1028    /// Create a new definition list.
1029    #[must_use]
1030    pub fn new(children: Vec<Block>) -> Self {
1031        Self {
1032            id: None,
1033            children,
1034            attributes: BlockAttributes::default(),
1035        }
1036    }
1037}
1038
1039/// Helper: serialize a Block variant by converting to Value, injecting "type", then serializing.
1040fn serialize_block_as_map<S: serde::Serializer>(
1041    type_str: &str,
1042    value: &serde_json::Value,
1043    serializer: S,
1044) -> Result<S::Ok, S::Error> {
1045    use serde::ser::Error;
1046    let obj = value
1047        .as_object()
1048        .ok_or_else(|| S::Error::custom("expected object"))?;
1049    let mut map = serializer.serialize_map(Some(1 + obj.len()))?;
1050    map.serialize_entry("type", type_str)?;
1051    for (k, v) in obj {
1052        map.serialize_entry(k, v)?;
1053    }
1054    map.end()
1055}
1056
1057/// Helper struct for serializing inline struct variants (Paragraph, Heading, etc.).
1058/// These don't have their own named struct, so we serialize field-by-field.
1059#[derive(Serialize)]
1060struct InlineParagraph<'a> {
1061    #[serde(skip_serializing_if = "Option::is_none")]
1062    id: &'a Option<String>,
1063    children: &'a Vec<Text>,
1064    #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1065    attributes: &'a BlockAttributes,
1066}
1067
1068#[derive(Serialize)]
1069struct InlineHeading<'a> {
1070    #[serde(skip_serializing_if = "Option::is_none")]
1071    id: &'a Option<String>,
1072    level: u8,
1073    children: &'a Vec<Text>,
1074    #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1075    attributes: &'a BlockAttributes,
1076}
1077
1078#[derive(Serialize)]
1079struct InlineList<'a> {
1080    #[serde(skip_serializing_if = "Option::is_none")]
1081    id: &'a Option<String>,
1082    ordered: bool,
1083    #[serde(skip_serializing_if = "Option::is_none")]
1084    start: &'a Option<u32>,
1085    children: &'a Vec<Block>,
1086    #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1087    attributes: &'a BlockAttributes,
1088}
1089
1090#[derive(Serialize)]
1091#[serde(rename_all = "camelCase")]
1092struct InlineListItem<'a> {
1093    #[serde(skip_serializing_if = "Option::is_none")]
1094    id: &'a Option<String>,
1095    #[serde(skip_serializing_if = "Option::is_none")]
1096    checked: &'a Option<bool>,
1097    children: &'a Vec<Block>,
1098    #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1099    attributes: &'a BlockAttributes,
1100}
1101
1102#[derive(Serialize)]
1103struct InlineContainer<'a> {
1104    #[serde(skip_serializing_if = "Option::is_none")]
1105    id: &'a Option<String>,
1106    children: &'a Vec<Block>,
1107    #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1108    attributes: &'a BlockAttributes,
1109}
1110
1111#[derive(Serialize)]
1112#[serde(rename_all = "camelCase")]
1113struct InlineCodeBlock<'a> {
1114    #[serde(skip_serializing_if = "Option::is_none")]
1115    id: &'a Option<String>,
1116    #[serde(skip_serializing_if = "Option::is_none")]
1117    language: &'a Option<String>,
1118    #[serde(skip_serializing_if = "Option::is_none")]
1119    highlighting: &'a Option<String>,
1120    #[serde(skip_serializing_if = "Option::is_none")]
1121    tokens: &'a Option<Vec<CodeToken>>,
1122    children: &'a Vec<Text>,
1123    #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1124    attributes: &'a BlockAttributes,
1125}
1126
1127#[derive(Serialize)]
1128struct InlineTableRow<'a> {
1129    #[serde(skip_serializing_if = "Option::is_none")]
1130    id: &'a Option<String>,
1131    #[serde(skip_serializing_if = "std::ops::Not::not")]
1132    header: bool,
1133    children: &'a Vec<Block>,
1134    #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1135    attributes: &'a BlockAttributes,
1136}
1137
1138#[derive(Serialize)]
1139struct InlineTextContainer<'a> {
1140    #[serde(skip_serializing_if = "Option::is_none")]
1141    id: &'a Option<String>,
1142    children: &'a Vec<Text>,
1143    #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1144    attributes: &'a BlockAttributes,
1145}
1146
1147#[derive(Serialize)]
1148struct InlineIdOnly<'a> {
1149    #[serde(skip_serializing_if = "Option::is_none")]
1150    id: &'a Option<String>,
1151}
1152
1153impl Serialize for Block {
1154    #[allow(clippy::too_many_lines)] // mechanical match over 20+ block variants — splitting would obscure the dispatch
1155    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1156        use serde::ser::Error;
1157
1158        match self {
1159            Self::Paragraph {
1160                id,
1161                children,
1162                attributes,
1163            } => {
1164                let inner = InlineParagraph {
1165                    id,
1166                    children,
1167                    attributes,
1168                };
1169                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1170                serialize_block_as_map("paragraph", &val, serializer)
1171            }
1172            Self::Heading {
1173                id,
1174                level,
1175                children,
1176                attributes,
1177            } => {
1178                let inner = InlineHeading {
1179                    id,
1180                    level: *level,
1181                    children,
1182                    attributes,
1183                };
1184                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1185                serialize_block_as_map("heading", &val, serializer)
1186            }
1187            Self::List {
1188                id,
1189                ordered,
1190                start,
1191                children,
1192                attributes,
1193            } => {
1194                let inner = InlineList {
1195                    id,
1196                    ordered: *ordered,
1197                    start,
1198                    children,
1199                    attributes,
1200                };
1201                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1202                serialize_block_as_map("list", &val, serializer)
1203            }
1204            Self::ListItem {
1205                id,
1206                checked,
1207                children,
1208                attributes,
1209            } => {
1210                let inner = InlineListItem {
1211                    id,
1212                    checked,
1213                    children,
1214                    attributes,
1215                };
1216                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1217                serialize_block_as_map("listItem", &val, serializer)
1218            }
1219            Self::Blockquote {
1220                id,
1221                children,
1222                attributes,
1223            } => {
1224                let inner = InlineContainer {
1225                    id,
1226                    children,
1227                    attributes,
1228                };
1229                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1230                serialize_block_as_map("blockquote", &val, serializer)
1231            }
1232            Self::CodeBlock {
1233                id,
1234                language,
1235                highlighting,
1236                tokens,
1237                children,
1238                attributes,
1239            } => {
1240                let inner = InlineCodeBlock {
1241                    id,
1242                    language,
1243                    highlighting,
1244                    tokens,
1245                    children,
1246                    attributes,
1247                };
1248                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1249                serialize_block_as_map("codeBlock", &val, serializer)
1250            }
1251            Self::HorizontalRule { id } => {
1252                let inner = InlineIdOnly { id };
1253                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1254                serialize_block_as_map("horizontalRule", &val, serializer)
1255            }
1256            Self::Image(img) => {
1257                let val = serde_json::to_value(img).map_err(S::Error::custom)?;
1258                serialize_block_as_map("image", &val, serializer)
1259            }
1260            Self::Table {
1261                id,
1262                children,
1263                attributes,
1264            } => {
1265                let inner = InlineContainer {
1266                    id,
1267                    children,
1268                    attributes,
1269                };
1270                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1271                serialize_block_as_map("table", &val, serializer)
1272            }
1273            Self::TableRow {
1274                id,
1275                header,
1276                children,
1277                attributes,
1278            } => {
1279                let inner = InlineTableRow {
1280                    id,
1281                    header: *header,
1282                    children,
1283                    attributes,
1284                };
1285                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1286                serialize_block_as_map("tableRow", &val, serializer)
1287            }
1288            Self::TableCell(cell) => {
1289                let val = serde_json::to_value(cell).map_err(S::Error::custom)?;
1290                serialize_block_as_map("tableCell", &val, serializer)
1291            }
1292            Self::Math(math) => {
1293                let val = serde_json::to_value(math).map_err(S::Error::custom)?;
1294                serialize_block_as_map("math", &val, serializer)
1295            }
1296            Self::Break { id } => {
1297                let inner = InlineIdOnly { id };
1298                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1299                serialize_block_as_map("break", &val, serializer)
1300            }
1301            Self::DefinitionList(dl) => {
1302                let val = serde_json::to_value(dl).map_err(S::Error::custom)?;
1303                serialize_block_as_map("definitionList", &val, serializer)
1304            }
1305            Self::DefinitionItem {
1306                id,
1307                children,
1308                attributes,
1309            } => {
1310                let inner = InlineContainer {
1311                    id,
1312                    children,
1313                    attributes,
1314                };
1315                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1316                serialize_block_as_map("definitionItem", &val, serializer)
1317            }
1318            Self::DefinitionTerm {
1319                id,
1320                children,
1321                attributes,
1322            } => {
1323                let inner = InlineTextContainer {
1324                    id,
1325                    children,
1326                    attributes,
1327                };
1328                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1329                serialize_block_as_map("definitionTerm", &val, serializer)
1330            }
1331            Self::DefinitionDescription {
1332                id,
1333                children,
1334                attributes,
1335            } => {
1336                let inner = InlineContainer {
1337                    id,
1338                    children,
1339                    attributes,
1340                };
1341                let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1342                serialize_block_as_map("definitionDescription", &val, serializer)
1343            }
1344            Self::Measurement(m) => {
1345                let val = serde_json::to_value(m).map_err(S::Error::custom)?;
1346                serialize_block_as_map("measurement", &val, serializer)
1347            }
1348            Self::Signature(sig) => {
1349                let val = serde_json::to_value(sig).map_err(S::Error::custom)?;
1350                serialize_block_as_map("signature", &val, serializer)
1351            }
1352            Self::Svg(svg) => {
1353                let val = serde_json::to_value(svg).map_err(S::Error::custom)?;
1354                serialize_block_as_map("svg", &val, serializer)
1355            }
1356            Self::Barcode(bc) => {
1357                let val = serde_json::to_value(bc).map_err(S::Error::custom)?;
1358                serialize_block_as_map("barcode", &val, serializer)
1359            }
1360            Self::Figure(fig) => {
1361                let val = serde_json::to_value(fig).map_err(S::Error::custom)?;
1362                serialize_block_as_map("figure", &val, serializer)
1363            }
1364            Self::FigCaption(fc) => {
1365                let val = serde_json::to_value(fc).map_err(S::Error::custom)?;
1366                serialize_block_as_map("figcaption", &val, serializer)
1367            }
1368            Self::Admonition(adm) => {
1369                let val = serde_json::to_value(adm).map_err(S::Error::custom)?;
1370                serialize_block_as_map("admonition", &val, serializer)
1371            }
1372            Self::Extension(ext) => {
1373                // Extension blocks: type is "namespace:blockType", flatten id/children/fallback/attributes
1374                let type_str = ext.full_type();
1375                let attr_count = ext.attributes.as_object().map_or(0, serde_json::Map::len);
1376                let mut count = 1; // type
1377                if ext.id.is_some() {
1378                    count += 1;
1379                }
1380                if !ext.children.is_empty() {
1381                    count += 1;
1382                }
1383                if ext.fallback.is_some() {
1384                    count += 1;
1385                }
1386                count += attr_count;
1387
1388                let mut map = serializer.serialize_map(Some(count))?;
1389                map.serialize_entry("type", &type_str)?;
1390                if let Some(id) = &ext.id {
1391                    map.serialize_entry("id", id)?;
1392                }
1393                if !ext.children.is_empty() {
1394                    map.serialize_entry("children", &ext.children)?;
1395                }
1396                if let Some(fallback) = &ext.fallback {
1397                    map.serialize_entry("fallback", fallback)?;
1398                }
1399                if let Some(obj) = ext.attributes.as_object() {
1400                    for (k, v) in obj {
1401                        map.serialize_entry(k, v)?;
1402                    }
1403                }
1404                map.end()
1405            }
1406        }
1407    }
1408}
1409
1410impl<'de> Deserialize<'de> for Block {
1411    #[allow(clippy::too_many_lines)] // mechanical match over 20+ block type strings — splitting would obscure the dispatch
1412    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1413        // Deserialize into a generic Value first, then dispatch based on "type"
1414        let mut value = serde_json::Value::deserialize(deserializer)?;
1415
1416        let obj = value
1417            .as_object_mut()
1418            .ok_or_else(|| de::Error::custom("block must be an object"))?;
1419
1420        let type_str = obj
1421            .get("type")
1422            .and_then(serde_json::Value::as_str)
1423            .ok_or_else(|| de::Error::missing_field("type"))?
1424            .to_string();
1425
1426        // Remove "type" before deserializing into inner structs (they don't expect it)
1427        obj.remove("type");
1428
1429        match type_str.as_str() {
1430            "paragraph" => {
1431                #[derive(Deserialize)]
1432                struct Inner {
1433                    #[serde(default)]
1434                    id: Option<String>,
1435                    #[serde(default)]
1436                    children: Vec<Text>,
1437                    #[serde(default)]
1438                    attributes: BlockAttributes,
1439                }
1440                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1441                Ok(Block::Paragraph {
1442                    id: inner.id,
1443                    children: inner.children,
1444                    attributes: inner.attributes,
1445                })
1446            }
1447            "heading" => {
1448                #[derive(Deserialize)]
1449                struct Inner {
1450                    #[serde(default)]
1451                    id: Option<String>,
1452                    level: u8,
1453                    #[serde(default)]
1454                    children: Vec<Text>,
1455                    #[serde(default)]
1456                    attributes: BlockAttributes,
1457                }
1458                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1459                Ok(Block::Heading {
1460                    id: inner.id,
1461                    level: inner.level,
1462                    children: inner.children,
1463                    attributes: inner.attributes,
1464                })
1465            }
1466            "list" => {
1467                #[derive(Deserialize)]
1468                struct Inner {
1469                    #[serde(default)]
1470                    id: Option<String>,
1471                    #[serde(default)]
1472                    ordered: bool,
1473                    #[serde(default)]
1474                    start: Option<u32>,
1475                    #[serde(default)]
1476                    children: Vec<Block>,
1477                    #[serde(default)]
1478                    attributes: BlockAttributes,
1479                }
1480                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1481                Ok(Block::List {
1482                    id: inner.id,
1483                    ordered: inner.ordered,
1484                    start: inner.start,
1485                    children: inner.children,
1486                    attributes: inner.attributes,
1487                })
1488            }
1489            "listItem" => {
1490                #[derive(Deserialize)]
1491                struct Inner {
1492                    #[serde(default)]
1493                    id: Option<String>,
1494                    #[serde(default)]
1495                    checked: Option<bool>,
1496                    #[serde(default)]
1497                    children: Vec<Block>,
1498                    #[serde(default)]
1499                    attributes: BlockAttributes,
1500                }
1501                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1502                Ok(Block::ListItem {
1503                    id: inner.id,
1504                    checked: inner.checked,
1505                    children: inner.children,
1506                    attributes: inner.attributes,
1507                })
1508            }
1509            "blockquote" => {
1510                #[derive(Deserialize)]
1511                struct Inner {
1512                    #[serde(default)]
1513                    id: Option<String>,
1514                    #[serde(default)]
1515                    children: Vec<Block>,
1516                    #[serde(default)]
1517                    attributes: BlockAttributes,
1518                }
1519                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1520                Ok(Block::Blockquote {
1521                    id: inner.id,
1522                    children: inner.children,
1523                    attributes: inner.attributes,
1524                })
1525            }
1526            "codeBlock" => {
1527                #[derive(Deserialize)]
1528                struct Inner {
1529                    #[serde(default)]
1530                    id: Option<String>,
1531                    #[serde(default)]
1532                    language: Option<String>,
1533                    #[serde(default)]
1534                    highlighting: Option<String>,
1535                    #[serde(default)]
1536                    tokens: Option<Vec<CodeToken>>,
1537                    #[serde(default)]
1538                    children: Vec<Text>,
1539                    #[serde(default)]
1540                    attributes: BlockAttributes,
1541                }
1542                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1543                Ok(Block::CodeBlock {
1544                    id: inner.id,
1545                    language: inner.language,
1546                    highlighting: inner.highlighting,
1547                    tokens: inner.tokens,
1548                    children: inner.children,
1549                    attributes: inner.attributes,
1550                })
1551            }
1552            "horizontalRule" => {
1553                #[derive(Deserialize)]
1554                struct Inner {
1555                    #[serde(default)]
1556                    id: Option<String>,
1557                }
1558                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1559                Ok(Block::HorizontalRule { id: inner.id })
1560            }
1561            "image" => {
1562                let img: ImageBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1563                Ok(Block::Image(img))
1564            }
1565            "table" => {
1566                #[derive(Deserialize)]
1567                struct Inner {
1568                    #[serde(default)]
1569                    id: Option<String>,
1570                    #[serde(default)]
1571                    children: Vec<Block>,
1572                    #[serde(default)]
1573                    attributes: BlockAttributes,
1574                }
1575                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1576                Ok(Block::Table {
1577                    id: inner.id,
1578                    children: inner.children,
1579                    attributes: inner.attributes,
1580                })
1581            }
1582            "tableRow" => {
1583                #[derive(Deserialize)]
1584                struct Inner {
1585                    #[serde(default)]
1586                    id: Option<String>,
1587                    #[serde(default)]
1588                    header: bool,
1589                    #[serde(default)]
1590                    children: Vec<Block>,
1591                    #[serde(default)]
1592                    attributes: BlockAttributes,
1593                }
1594                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1595                Ok(Block::TableRow {
1596                    id: inner.id,
1597                    header: inner.header,
1598                    children: inner.children,
1599                    attributes: inner.attributes,
1600                })
1601            }
1602            "tableCell" => {
1603                let cell: TableCellBlock =
1604                    serde_json::from_value(value).map_err(de::Error::custom)?;
1605                Ok(Block::TableCell(cell))
1606            }
1607            "math" => {
1608                let math: MathBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1609                Ok(Block::Math(math))
1610            }
1611            "break" => {
1612                #[derive(Deserialize)]
1613                struct Inner {
1614                    #[serde(default)]
1615                    id: Option<String>,
1616                }
1617                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1618                Ok(Block::Break { id: inner.id })
1619            }
1620            "definitionList" => {
1621                let dl: DefinitionListBlock =
1622                    serde_json::from_value(value).map_err(de::Error::custom)?;
1623                Ok(Block::DefinitionList(dl))
1624            }
1625            "definitionItem" => {
1626                #[derive(Deserialize)]
1627                struct Inner {
1628                    #[serde(default)]
1629                    id: Option<String>,
1630                    #[serde(default)]
1631                    children: Vec<Block>,
1632                    #[serde(default)]
1633                    attributes: BlockAttributes,
1634                }
1635                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1636                Ok(Block::DefinitionItem {
1637                    id: inner.id,
1638                    children: inner.children,
1639                    attributes: inner.attributes,
1640                })
1641            }
1642            "definitionTerm" => {
1643                #[derive(Deserialize)]
1644                struct Inner {
1645                    #[serde(default)]
1646                    id: Option<String>,
1647                    #[serde(default)]
1648                    children: Vec<Text>,
1649                    #[serde(default)]
1650                    attributes: BlockAttributes,
1651                }
1652                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1653                Ok(Block::DefinitionTerm {
1654                    id: inner.id,
1655                    children: inner.children,
1656                    attributes: inner.attributes,
1657                })
1658            }
1659            "definitionDescription" => {
1660                #[derive(Deserialize)]
1661                struct Inner {
1662                    #[serde(default)]
1663                    id: Option<String>,
1664                    #[serde(default)]
1665                    children: Vec<Block>,
1666                    #[serde(default)]
1667                    attributes: BlockAttributes,
1668                }
1669                let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1670                Ok(Block::DefinitionDescription {
1671                    id: inner.id,
1672                    children: inner.children,
1673                    attributes: inner.attributes,
1674                })
1675            }
1676            "measurement" => {
1677                let m: MeasurementBlock =
1678                    serde_json::from_value(value).map_err(de::Error::custom)?;
1679                Ok(Block::Measurement(m))
1680            }
1681            "signature" => {
1682                let sig: SignatureBlock =
1683                    serde_json::from_value(value).map_err(de::Error::custom)?;
1684                Ok(Block::Signature(sig))
1685            }
1686            "svg" => {
1687                let svg: SvgBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1688                Ok(Block::Svg(svg))
1689            }
1690            "barcode" => {
1691                let bc: BarcodeBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1692                Ok(Block::Barcode(bc))
1693            }
1694            "figure" => {
1695                let fig: FigureBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1696                Ok(Block::Figure(fig))
1697            }
1698            "figcaption" | "figCaption" => {
1699                // Accept both "figcaption" (spec) and "figCaption" (old format)
1700                let fc: FigCaptionBlock =
1701                    serde_json::from_value(value).map_err(de::Error::custom)?;
1702                Ok(Block::FigCaption(fc))
1703            }
1704            "admonition" => {
1705                let adm: AdmonitionBlock =
1706                    serde_json::from_value(value).map_err(de::Error::custom)?;
1707                Ok(Block::Admonition(adm))
1708            }
1709
1710            // Old format backward compat: {"type": "extension", "namespace": "...", "blockType": "..."}
1711            "extension" => {
1712                let ext: ExtensionBlock =
1713                    serde_json::from_value(value).map_err(de::Error::custom)?;
1714                Ok(Block::Extension(ext))
1715            }
1716
1717            // Colon-delimited extension type (new format)
1718            other if other.contains(':') => {
1719                let (namespace, block_type) = other.split_once(':').unwrap();
1720                let obj = value
1721                    .as_object()
1722                    .ok_or_else(|| de::Error::custom("expected object"))?;
1723
1724                let id = obj
1725                    .get("id")
1726                    .and_then(serde_json::Value::as_str)
1727                    .map(ToString::to_string);
1728                let children: Vec<Block> = obj
1729                    .get("children")
1730                    .map(|v| serde_json::from_value(v.clone()))
1731                    .transpose()
1732                    .map_err(de::Error::custom)?
1733                    .unwrap_or_default();
1734                let fallback: Option<Box<Block>> = obj
1735                    .get("fallback")
1736                    .map(|v| serde_json::from_value(v.clone()))
1737                    .transpose()
1738                    .map_err(de::Error::custom)?;
1739
1740                // Collect remaining keys as attributes
1741                let reserved = ["id", "children", "fallback"];
1742                let mut attrs = serde_json::Map::new();
1743                for (k, v) in obj {
1744                    if !reserved.contains(&k.as_str()) {
1745                        attrs.insert(k.clone(), v.clone());
1746                    }
1747                }
1748                let attributes = if attrs.is_empty() {
1749                    serde_json::Value::Null
1750                } else {
1751                    serde_json::Value::Object(attrs)
1752                };
1753
1754                Ok(Block::Extension(ExtensionBlock {
1755                    namespace: namespace.to_string(),
1756                    block_type: block_type.to_string(),
1757                    id,
1758                    attributes,
1759                    children,
1760                    fallback,
1761                }))
1762            }
1763
1764            unknown => Err(de::Error::custom(format!("unknown block type: {unknown}"))),
1765        }
1766    }
1767}
1768
1769// Convenience constructors
1770impl Block {
1771    /// Create a paragraph block.
1772    #[must_use]
1773    pub fn paragraph(children: Vec<Text>) -> Self {
1774        Self::Paragraph {
1775            id: None,
1776            children,
1777            attributes: BlockAttributes::default(),
1778        }
1779    }
1780
1781    /// Create a heading block.
1782    #[must_use]
1783    pub fn heading(level: u8, children: Vec<Text>) -> Self {
1784        Self::Heading {
1785            id: None,
1786            level: level.clamp(1, 6),
1787            children,
1788            attributes: BlockAttributes::default(),
1789        }
1790    }
1791
1792    /// Create an unordered list.
1793    #[must_use]
1794    pub fn unordered_list(items: Vec<Block>) -> Self {
1795        Self::List {
1796            id: None,
1797            ordered: false,
1798            start: None,
1799            children: items,
1800            attributes: BlockAttributes::default(),
1801        }
1802    }
1803
1804    /// Create an ordered list.
1805    #[must_use]
1806    pub fn ordered_list(items: Vec<Block>) -> Self {
1807        Self::List {
1808            id: None,
1809            ordered: true,
1810            start: None,
1811            children: items,
1812            attributes: BlockAttributes::default(),
1813        }
1814    }
1815
1816    /// Create a list item.
1817    #[must_use]
1818    pub fn list_item(children: Vec<Block>) -> Self {
1819        Self::ListItem {
1820            id: None,
1821            checked: None,
1822            children,
1823            attributes: BlockAttributes::default(),
1824        }
1825    }
1826
1827    /// Create a checkbox list item.
1828    #[must_use]
1829    pub fn checkbox(checked: bool, children: Vec<Block>) -> Self {
1830        Self::ListItem {
1831            id: None,
1832            checked: Some(checked),
1833            children,
1834            attributes: BlockAttributes::default(),
1835        }
1836    }
1837
1838    /// Create a blockquote.
1839    #[must_use]
1840    pub fn blockquote(children: Vec<Block>) -> Self {
1841        Self::Blockquote {
1842            id: None,
1843            children,
1844            attributes: BlockAttributes::default(),
1845        }
1846    }
1847
1848    /// Create a code block.
1849    #[must_use]
1850    pub fn code_block(code: impl Into<String>, language: Option<String>) -> Self {
1851        Self::CodeBlock {
1852            id: None,
1853            language,
1854            highlighting: None,
1855            tokens: None,
1856            children: vec![Text::plain(code)],
1857            attributes: BlockAttributes::default(),
1858        }
1859    }
1860
1861    /// Create a horizontal rule.
1862    #[must_use]
1863    pub fn horizontal_rule() -> Self {
1864        Self::HorizontalRule { id: None }
1865    }
1866
1867    /// Create an image block.
1868    #[must_use]
1869    pub fn image(src: impl Into<String>, alt: impl Into<String>) -> Self {
1870        Self::Image(ImageBlock {
1871            id: None,
1872            src: src.into(),
1873            alt: alt.into(),
1874            title: None,
1875            width: None,
1876            height: None,
1877        })
1878    }
1879
1880    /// Create a table.
1881    #[must_use]
1882    pub fn table(rows: Vec<Block>) -> Self {
1883        Self::Table {
1884            id: None,
1885            children: rows,
1886            attributes: BlockAttributes::default(),
1887        }
1888    }
1889
1890    /// Create a table row.
1891    #[must_use]
1892    pub fn table_row(cells: Vec<Block>, header: bool) -> Self {
1893        Self::TableRow {
1894            id: None,
1895            header,
1896            children: cells,
1897            attributes: BlockAttributes::default(),
1898        }
1899    }
1900
1901    /// Create a table cell.
1902    #[must_use]
1903    pub fn table_cell(children: Vec<Text>) -> Self {
1904        Self::TableCell(TableCellBlock {
1905            id: None,
1906            colspan: 1,
1907            rowspan: 1,
1908            align: None,
1909            children,
1910            attributes: BlockAttributes::default(),
1911        })
1912    }
1913
1914    /// Create a math block.
1915    #[must_use]
1916    pub fn math(value: impl Into<String>, format: MathFormat, display: bool) -> Self {
1917        Self::Math(MathBlock {
1918            id: None,
1919            display,
1920            format,
1921            value: value.into(),
1922        })
1923    }
1924
1925    /// Create a line break.
1926    #[must_use]
1927    pub fn line_break() -> Self {
1928        Self::Break { id: None }
1929    }
1930
1931    /// Create a definition list.
1932    #[must_use]
1933    pub fn definition_list(items: Vec<Block>) -> Self {
1934        Self::DefinitionList(DefinitionListBlock::new(items))
1935    }
1936
1937    /// Create a definition item.
1938    #[must_use]
1939    pub fn definition_item(children: Vec<Block>) -> Self {
1940        Self::DefinitionItem {
1941            id: None,
1942            children,
1943            attributes: BlockAttributes::default(),
1944        }
1945    }
1946
1947    /// Create a definition term.
1948    #[must_use]
1949    pub fn definition_term(children: Vec<Text>) -> Self {
1950        Self::DefinitionTerm {
1951            id: None,
1952            children,
1953            attributes: BlockAttributes::default(),
1954        }
1955    }
1956
1957    /// Create a definition description.
1958    #[must_use]
1959    pub fn definition_description(children: Vec<Block>) -> Self {
1960        Self::DefinitionDescription {
1961            id: None,
1962            children,
1963            attributes: BlockAttributes::default(),
1964        }
1965    }
1966
1967    /// Create a measurement block.
1968    #[must_use]
1969    pub fn measurement(value: f64, display: impl Into<String>) -> Self {
1970        Self::Measurement(MeasurementBlock::new(value, display))
1971    }
1972
1973    /// Create a signature block.
1974    #[must_use]
1975    pub fn signature(signature_type: BlockSignatureType) -> Self {
1976        Self::Signature(SignatureBlock::new(signature_type))
1977    }
1978
1979    /// Create an SVG block from a source reference.
1980    #[must_use]
1981    pub fn svg_from_src(src: impl Into<String>) -> Self {
1982        Self::Svg(SvgBlock::from_src(src))
1983    }
1984
1985    /// Create an SVG block from inline content.
1986    #[must_use]
1987    pub fn svg_from_content(content: impl Into<String>) -> Self {
1988        Self::Svg(SvgBlock::from_content(content))
1989    }
1990
1991    /// Create a barcode block.
1992    #[must_use]
1993    pub fn barcode(format: BarcodeFormat, data: impl Into<String>, alt: impl Into<String>) -> Self {
1994        Self::Barcode(BarcodeBlock::new(format, data, alt))
1995    }
1996
1997    /// Create a figure block.
1998    #[must_use]
1999    pub fn figure(children: Vec<Block>) -> Self {
2000        Self::Figure(FigureBlock::new(children))
2001    }
2002
2003    /// Create a figure caption.
2004    #[must_use]
2005    pub fn figcaption(children: Vec<Text>) -> Self {
2006        Self::FigCaption(FigCaptionBlock::new(children))
2007    }
2008
2009    /// Create an admonition block.
2010    #[must_use]
2011    pub fn admonition(variant: AdmonitionVariant, children: Vec<Block>) -> Self {
2012        Self::Admonition(AdmonitionBlock::new(variant, children))
2013    }
2014
2015    /// Get the block type as a string.
2016    ///
2017    /// For core blocks, returns the camelCase type name.
2018    /// For extension blocks, returns the colon-delimited type (e.g., `"forms:textInput"`).
2019    #[must_use]
2020    pub fn block_type(&self) -> Cow<'_, str> {
2021        match self {
2022            Self::Paragraph { .. } => Cow::Borrowed("paragraph"),
2023            Self::Heading { .. } => Cow::Borrowed("heading"),
2024            Self::List { .. } => Cow::Borrowed("list"),
2025            Self::ListItem { .. } => Cow::Borrowed("listItem"),
2026            Self::Blockquote { .. } => Cow::Borrowed("blockquote"),
2027            Self::CodeBlock { .. } => Cow::Borrowed("codeBlock"),
2028            Self::HorizontalRule { .. } => Cow::Borrowed("horizontalRule"),
2029            Self::Image(_) => Cow::Borrowed("image"),
2030            Self::Table { .. } => Cow::Borrowed("table"),
2031            Self::TableRow { .. } => Cow::Borrowed("tableRow"),
2032            Self::TableCell(_) => Cow::Borrowed("tableCell"),
2033            Self::Math(_) => Cow::Borrowed("math"),
2034            Self::Break { .. } => Cow::Borrowed("break"),
2035            Self::DefinitionList(_) => Cow::Borrowed("definitionList"),
2036            Self::DefinitionItem { .. } => Cow::Borrowed("definitionItem"),
2037            Self::DefinitionTerm { .. } => Cow::Borrowed("definitionTerm"),
2038            Self::DefinitionDescription { .. } => Cow::Borrowed("definitionDescription"),
2039            Self::Measurement(_) => Cow::Borrowed("measurement"),
2040            Self::Signature(_) => Cow::Borrowed("signature"),
2041            Self::Svg(_) => Cow::Borrowed("svg"),
2042            Self::Barcode(_) => Cow::Borrowed("barcode"),
2043            Self::Figure(_) => Cow::Borrowed("figure"),
2044            Self::FigCaption(_) => Cow::Borrowed("figcaption"),
2045            Self::Admonition(_) => Cow::Borrowed("admonition"),
2046            Self::Extension(ext) => Cow::Owned(ext.full_type()),
2047        }
2048    }
2049
2050    /// Get the block's ID if it has one.
2051    #[must_use]
2052    pub fn id(&self) -> Option<&str> {
2053        match self {
2054            Self::Paragraph { id, .. }
2055            | Self::Heading { id, .. }
2056            | Self::List { id, .. }
2057            | Self::ListItem { id, .. }
2058            | Self::Blockquote { id, .. }
2059            | Self::CodeBlock { id, .. }
2060            | Self::HorizontalRule { id }
2061            | Self::Table { id, .. }
2062            | Self::TableRow { id, .. }
2063            | Self::Break { id }
2064            | Self::DefinitionItem { id, .. }
2065            | Self::DefinitionTerm { id, .. }
2066            | Self::DefinitionDescription { id, .. } => id.as_deref(),
2067            Self::Image(img) => img.id.as_deref(),
2068            Self::TableCell(cell) => cell.id.as_deref(),
2069            Self::Math(math) => math.id.as_deref(),
2070            Self::DefinitionList(dl) => dl.id.as_deref(),
2071            Self::Measurement(m) => m.id.as_deref(),
2072            Self::Signature(sig) => sig.id.as_deref(),
2073            Self::Svg(svg) => svg.id.as_deref(),
2074            Self::Barcode(bc) => bc.id.as_deref(),
2075            Self::Figure(fig) => fig.id.as_deref(),
2076            Self::FigCaption(fc) => fc.id.as_deref(),
2077            Self::Admonition(adm) => adm.id.as_deref(),
2078            Self::Extension(ext) => ext.id.as_deref(),
2079        }
2080    }
2081
2082    /// Create an extension block.
2083    #[must_use]
2084    pub fn extension(namespace: impl Into<String>, block_type: impl Into<String>) -> Self {
2085        Self::Extension(ExtensionBlock::new(namespace, block_type))
2086    }
2087
2088    /// Check if this is an extension block.
2089    #[must_use]
2090    pub fn is_extension(&self) -> bool {
2091        matches!(self, Self::Extension(_))
2092    }
2093
2094    /// Get the extension block if this is one.
2095    #[must_use]
2096    pub fn as_extension(&self) -> Option<&ExtensionBlock> {
2097        match self {
2098            Self::Extension(ext) => Some(ext),
2099            _ => None,
2100        }
2101    }
2102}
2103
2104#[cfg(test)]
2105mod tests {
2106    use super::*;
2107
2108    #[test]
2109    fn test_content_new() {
2110        let content = Content::new(vec![Block::paragraph(vec![Text::plain("Hello")])]);
2111        assert_eq!(content.version, "0.1");
2112        assert_eq!(content.len(), 1);
2113        assert!(!content.is_empty());
2114    }
2115
2116    #[test]
2117    fn test_content_empty() {
2118        let content = Content::empty();
2119        assert!(content.is_empty());
2120        assert_eq!(content.len(), 0);
2121    }
2122
2123    #[test]
2124    fn test_paragraph() {
2125        let block = Block::paragraph(vec![Text::plain("Hello")]);
2126        assert_eq!(block.block_type(), "paragraph");
2127        assert!(block.id().is_none());
2128    }
2129
2130    #[test]
2131    fn test_heading() {
2132        let block = Block::heading(1, vec![Text::plain("Title")]);
2133        if let Block::Heading { level, .. } = &block {
2134            assert_eq!(*level, 1);
2135        } else {
2136            panic!("Expected Heading");
2137        }
2138
2139        // Test clamping
2140        let block = Block::heading(10, vec![Text::plain("Title")]);
2141        if let Block::Heading { level, .. } = &block {
2142            assert_eq!(*level, 6);
2143        }
2144    }
2145
2146    #[test]
2147    fn test_list() {
2148        let items = vec![
2149            Block::list_item(vec![Block::paragraph(vec![Text::plain("Item 1")])]),
2150            Block::list_item(vec![Block::paragraph(vec![Text::plain("Item 2")])]),
2151        ];
2152        let list = Block::unordered_list(items);
2153        assert_eq!(list.block_type(), "list");
2154    }
2155
2156    #[test]
2157    fn test_code_block() {
2158        let block = Block::code_block("fn main() {}", Some("rust".to_string()));
2159        if let Block::CodeBlock {
2160            language, children, ..
2161        } = &block
2162        {
2163            assert_eq!(language.as_deref(), Some("rust"));
2164            assert_eq!(children[0].value, "fn main() {}");
2165        } else {
2166            panic!("Expected CodeBlock");
2167        }
2168    }
2169
2170    #[test]
2171    fn test_image() {
2172        let block = Block::image("assets/photo.png", "A photo");
2173        if let Block::Image(img) = &block {
2174            assert_eq!(img.src, "assets/photo.png");
2175            assert_eq!(img.alt, "A photo");
2176        } else {
2177            panic!("Expected Image");
2178        }
2179    }
2180
2181    #[test]
2182    fn test_math() {
2183        let block = Block::math("E = mc^2", MathFormat::Latex, true);
2184        if let Block::Math(math) = &block {
2185            assert_eq!(math.value, "E = mc^2");
2186            assert_eq!(math.format, MathFormat::Latex);
2187            assert!(math.display);
2188        } else {
2189            panic!("Expected Math");
2190        }
2191    }
2192
2193    #[test]
2194    fn test_block_serialization() {
2195        let block = Block::paragraph(vec![Text::plain("Test")]);
2196        let json = serde_json::to_string(&block).unwrap();
2197        assert!(json.contains("\"type\":\"paragraph\""));
2198    }
2199
2200    #[test]
2201    fn test_content_serialization() {
2202        let content = Content::new(vec![
2203            Block::heading(1, vec![Text::plain("Title")]),
2204            Block::paragraph(vec![Text::plain("Body")]),
2205        ]);
2206        let json = serde_json::to_string_pretty(&content).unwrap();
2207        assert!(json.contains("\"version\": \"0.1\""));
2208        assert!(json.contains("\"type\": \"heading\""));
2209        assert!(json.contains("\"type\": \"paragraph\""));
2210    }
2211
2212    #[test]
2213    fn test_block_deserialization() {
2214        let json = r#"{
2215            "type": "heading",
2216            "level": 2,
2217            "children": [{"value": "Section"}]
2218        }"#;
2219        let block: Block = serde_json::from_str(json).unwrap();
2220        if let Block::Heading {
2221            level, children, ..
2222        } = block
2223        {
2224            assert_eq!(level, 2);
2225            assert_eq!(children[0].value, "Section");
2226        } else {
2227            panic!("Expected Heading");
2228        }
2229    }
2230
2231    #[test]
2232    fn test_table_serialization() {
2233        let table = Block::table(vec![Block::table_row(
2234            vec![Block::table_cell(vec![Text::plain("Header")])],
2235            true,
2236        )]);
2237        let json = serde_json::to_string(&table).unwrap();
2238        assert!(json.contains("\"type\":\"table\""));
2239        assert!(json.contains("\"type\":\"tableRow\""));
2240        assert!(json.contains("\"header\":true"));
2241    }
2242
2243    #[test]
2244    fn test_extension_block() {
2245        let ext = Block::extension("forms", "textInput");
2246        assert!(ext.is_extension());
2247        assert_eq!(ext.block_type(), "forms:textInput");
2248
2249        if let Block::Extension(inner) = &ext {
2250            assert_eq!(inner.namespace, "forms");
2251            assert_eq!(inner.block_type, "textInput");
2252            assert_eq!(inner.full_type(), "forms:textInput");
2253        } else {
2254            panic!("Expected Extension");
2255        }
2256    }
2257
2258    #[test]
2259    fn test_extension_as_extension() {
2260        let ext = Block::extension("semantic", "citation");
2261        let inner = ext.as_extension().expect("should be extension");
2262        assert_eq!(inner.namespace, "semantic");
2263
2264        let para = Block::paragraph(vec![Text::plain("Not extension")]);
2265        assert!(para.as_extension().is_none());
2266    }
2267
2268    #[test]
2269    fn test_extension_with_fallback() {
2270        let fallback = Block::paragraph(vec![Text::plain("[Form field]")]);
2271        let ext = ExtensionBlock::new("forms", "textInput")
2272            .with_id("name-field")
2273            .with_fallback(fallback);
2274
2275        assert_eq!(ext.id, Some("name-field".to_string()));
2276        assert!(ext.fallback_content().is_some());
2277    }
2278
2279    // Tests for new block types
2280
2281    #[test]
2282    fn test_definition_list() {
2283        let dl = Block::definition_list(vec![Block::definition_item(vec![
2284            Block::definition_term(vec![Text::plain("Term")]),
2285            Block::definition_description(vec![Block::paragraph(vec![Text::plain("Description")])]),
2286        ])]);
2287        assert_eq!(dl.block_type(), "definitionList");
2288    }
2289
2290    #[test]
2291    fn test_measurement() {
2292        let m = Block::measurement(9.81, "9.81 m/s²");
2293        assert_eq!(m.block_type(), "measurement");
2294        if let Block::Measurement(meas) = &m {
2295            assert!((meas.value - 9.81).abs() < 0.001);
2296            assert_eq!(meas.display, "9.81 m/s²");
2297        } else {
2298            panic!("Expected Measurement");
2299        }
2300    }
2301
2302    #[test]
2303    fn test_measurement_with_uncertainty() {
2304        let m = MeasurementBlock::new(1.234, "1.234(5) m")
2305            .with_unit("m")
2306            .with_uncertainty(0.005, UncertaintyNotation::Parenthetical);
2307        assert_eq!(m.unit, Some("m".to_string()));
2308        assert!(m.uncertainty.is_some());
2309        assert_eq!(
2310            m.uncertainty_notation,
2311            Some(UncertaintyNotation::Parenthetical)
2312        );
2313    }
2314
2315    #[test]
2316    fn test_signature() {
2317        let sig = Block::signature(BlockSignatureType::Digital);
2318        assert_eq!(sig.block_type(), "signature");
2319        if let Block::Signature(s) = &sig {
2320            assert_eq!(s.signature_type, BlockSignatureType::Digital);
2321        } else {
2322            panic!("Expected Signature");
2323        }
2324    }
2325
2326    #[test]
2327    fn test_signature_with_signer() {
2328        let signer = SignerDetails::new("John Doe")
2329            .with_title("CEO")
2330            .with_organization("Acme Corp");
2331        let sig = SignatureBlock::new(BlockSignatureType::Handwritten)
2332            .with_signer(signer)
2333            .with_purpose(SignaturePurpose::Approval);
2334        assert!(sig.signer.is_some());
2335        assert_eq!(sig.purpose, Some(SignaturePurpose::Approval));
2336    }
2337
2338    #[test]
2339    fn test_svg_from_src() {
2340        let svg = Block::svg_from_src("diagram.svg");
2341        assert_eq!(svg.block_type(), "svg");
2342        if let Block::Svg(s) = &svg {
2343            assert_eq!(s.src, Some("diagram.svg".to_string()));
2344            assert!(s.content.is_none());
2345        } else {
2346            panic!("Expected Svg");
2347        }
2348    }
2349
2350    #[test]
2351    fn test_svg_from_content() {
2352        let svg = Block::svg_from_content("<svg>...</svg>");
2353        if let Block::Svg(s) = &svg {
2354            assert!(s.src.is_none());
2355            assert_eq!(s.content, Some("<svg>...</svg>".to_string()));
2356        } else {
2357            panic!("Expected Svg");
2358        }
2359    }
2360
2361    #[test]
2362    fn test_barcode() {
2363        let bc = Block::barcode(
2364            BarcodeFormat::Qr,
2365            "https://example.com",
2366            "Link to example.com",
2367        );
2368        assert_eq!(bc.block_type(), "barcode");
2369        if let Block::Barcode(b) = &bc {
2370            assert_eq!(b.format, BarcodeFormat::Qr);
2371            assert_eq!(b.data, "https://example.com");
2372            assert_eq!(b.alt, "Link to example.com");
2373        } else {
2374            panic!("Expected Barcode");
2375        }
2376    }
2377
2378    #[test]
2379    fn test_barcode_with_options() {
2380        let bc = BarcodeBlock::new(BarcodeFormat::Qr, "data", "Meaningful alt text")
2381            .with_error_correction(ErrorCorrectionLevel::H)
2382            .with_size("2in".to_string(), "2in".to_string());
2383        assert_eq!(bc.error_correction, Some(ErrorCorrectionLevel::H));
2384        assert!(bc.size.is_some());
2385    }
2386
2387    #[test]
2388    fn test_figure() {
2389        let fig = Block::figure(vec![
2390            Block::image("photo.png", "A photo"),
2391            Block::figcaption(vec![Text::plain("Figure 1: A photo")]),
2392        ]);
2393        assert_eq!(fig.block_type(), "figure");
2394    }
2395
2396    #[test]
2397    fn test_figcaption() {
2398        let fc = Block::figcaption(vec![Text::plain("Caption text")]);
2399        assert_eq!(fc.block_type(), "figcaption");
2400    }
2401
2402    #[test]
2403    fn test_writing_mode_serialization() {
2404        let attrs = BlockAttributes {
2405            dir: None,
2406            lang: None,
2407            writing_mode: Some(WritingMode::VerticalRl),
2408        };
2409        let json = serde_json::to_string(&attrs).unwrap();
2410        assert!(json.contains("\"writingMode\":\"vertical-rl\""));
2411    }
2412
2413    #[test]
2414    fn test_writing_mode_deserialization() {
2415        let json = r#"{"writingMode":"vertical-lr"}"#;
2416        let attrs: BlockAttributes = serde_json::from_str(json).unwrap();
2417        assert_eq!(attrs.writing_mode, Some(WritingMode::VerticalLr));
2418    }
2419
2420    #[test]
2421    fn test_measurement_serialization() {
2422        let m = MeasurementBlock::new(299_792_458.0, "299,792,458 m/s")
2423            .with_unit("m/s")
2424            .with_exponent(8);
2425        let json = serde_json::to_string(&m).unwrap();
2426        assert!(json.contains("\"value\":299792458")); // JSON doesn't use underscores
2427        assert!(json.contains("\"unit\":\"m/s\""));
2428        assert!(json.contains("\"exponent\":8"));
2429    }
2430
2431    #[test]
2432    fn test_barcode_format_serialization() {
2433        let bc = BarcodeBlock::new(BarcodeFormat::DataMatrix, "ABC123", "Product code ABC123");
2434        let json = serde_json::to_string(&bc).unwrap();
2435        // DataMatrix becomes datamatrix with lowercase rename_all
2436        assert!(json.contains("\"format\":\"datamatrix\""));
2437    }
2438
2439    #[test]
2440    fn test_signature_type_serialization() {
2441        let sig = SignatureBlock::new(BlockSignatureType::Electronic);
2442        let json = serde_json::to_string(&sig).unwrap();
2443        assert!(json.contains("\"signatureType\":\"electronic\""));
2444    }
2445
2446    #[test]
2447    fn test_new_block_types_deserialization() {
2448        // Definition list
2449        let json = r#"{"type":"definitionList","children":[]}"#;
2450        let block: Block = serde_json::from_str(json).unwrap();
2451        assert_eq!(block.block_type(), "definitionList");
2452
2453        // Measurement
2454        let json = r#"{"type":"measurement","value":3.14159,"display":"π"}"#;
2455        let block: Block = serde_json::from_str(json).unwrap();
2456        assert_eq!(block.block_type(), "measurement");
2457
2458        // SVG
2459        let json = r#"{"type":"svg","src":"diagram.svg"}"#;
2460        let block: Block = serde_json::from_str(json).unwrap();
2461        assert_eq!(block.block_type(), "svg");
2462
2463        // Barcode
2464        let json = r#"{"type":"barcode","format":"qr","data":"test","alt":"Test QR code"}"#;
2465        let block: Block = serde_json::from_str(json).unwrap();
2466        assert_eq!(block.block_type(), "barcode");
2467
2468        // Figure
2469        let json = r#"{"type":"figure","children":[]}"#;
2470        let block: Block = serde_json::from_str(json).unwrap();
2471        assert_eq!(block.block_type(), "figure");
2472    }
2473
2474    // Admonition tests
2475
2476    #[test]
2477    fn test_admonition() {
2478        let adm = Block::admonition(
2479            AdmonitionVariant::Warning,
2480            vec![Block::paragraph(vec![Text::plain("Be careful!")])],
2481        );
2482        assert_eq!(adm.block_type(), "admonition");
2483        if let Block::Admonition(a) = &adm {
2484            assert_eq!(a.variant, AdmonitionVariant::Warning);
2485            assert_eq!(a.children.len(), 1);
2486        } else {
2487            panic!("Expected Admonition");
2488        }
2489    }
2490
2491    #[test]
2492    fn test_admonition_with_title() {
2493        let adm = AdmonitionBlock::new(
2494            AdmonitionVariant::Note,
2495            vec![Block::paragraph(vec![Text::plain("Important info")])],
2496        )
2497        .with_title("Please Note")
2498        .with_id("note-1");
2499
2500        assert_eq!(adm.title, Some("Please Note".to_string()));
2501        assert_eq!(adm.id, Some("note-1".to_string()));
2502    }
2503
2504    #[test]
2505    fn test_admonition_serialization() {
2506        let adm = Block::admonition(
2507            AdmonitionVariant::Tip,
2508            vec![Block::paragraph(vec![Text::plain("Pro tip!")])],
2509        );
2510        let json = serde_json::to_string(&adm).unwrap();
2511        assert!(json.contains("\"type\":\"admonition\""));
2512        assert!(json.contains("\"variant\":\"tip\""));
2513    }
2514
2515    #[test]
2516    fn test_admonition_deserialization() {
2517        let json = r#"{
2518            "type": "admonition",
2519            "variant": "danger",
2520            "title": "Warning!",
2521            "children": [
2522                {"type": "paragraph", "children": [{"value": "Do not proceed!"}]}
2523            ]
2524        }"#;
2525        let block: Block = serde_json::from_str(json).unwrap();
2526        assert_eq!(block.block_type(), "admonition");
2527        if let Block::Admonition(adm) = block {
2528            assert_eq!(adm.variant, AdmonitionVariant::Danger);
2529            assert_eq!(adm.title, Some("Warning!".to_string()));
2530            assert_eq!(adm.children.len(), 1);
2531        } else {
2532            panic!("Expected Admonition");
2533        }
2534    }
2535
2536    #[test]
2537    fn test_admonition_variant_display() {
2538        assert_eq!(AdmonitionVariant::Note.to_string(), "Note");
2539        assert_eq!(AdmonitionVariant::Warning.to_string(), "Warning");
2540        assert_eq!(AdmonitionVariant::Important.to_string(), "Important");
2541    }
2542
2543    #[test]
2544    fn test_all_admonition_variants() {
2545        let variants = [
2546            (AdmonitionVariant::Note, "note"),
2547            (AdmonitionVariant::Tip, "tip"),
2548            (AdmonitionVariant::Info, "info"),
2549            (AdmonitionVariant::Warning, "warning"),
2550            (AdmonitionVariant::Caution, "caution"),
2551            (AdmonitionVariant::Danger, "danger"),
2552            (AdmonitionVariant::Important, "important"),
2553            (AdmonitionVariant::Example, "example"),
2554        ];
2555
2556        for (variant, expected_name) in variants {
2557            let adm = AdmonitionBlock::new(variant, vec![]);
2558            let json = serde_json::to_string(&adm).unwrap();
2559            assert!(
2560                json.contains(&format!("\"variant\":\"{expected_name}\"")),
2561                "Variant {variant:?} should serialize as {expected_name}",
2562            );
2563        }
2564    }
2565}
2566
2567#[cfg(test)]
2568mod proptests {
2569    use super::*;
2570    use proptest::prelude::*;
2571
2572    /// Generate arbitrary text content for testing.
2573    fn arb_text_content() -> impl Strategy<Value = String> {
2574        "[a-zA-Z0-9 .,!?]{0,100}".prop_map(|s| s)
2575    }
2576
2577    /// Generate arbitrary optional string.
2578    fn arb_optional_string() -> impl Strategy<Value = Option<String>> {
2579        prop_oneof![Just(None), "[a-zA-Z0-9_-]{1,20}".prop_map(Some),]
2580    }
2581
2582    /// Generate arbitrary heading level.
2583    fn arb_heading_level() -> impl Strategy<Value = u8> {
2584        1u8..=6u8
2585    }
2586
2587    /// Generate arbitrary paragraph block.
2588    fn arb_paragraph() -> impl Strategy<Value = Block> {
2589        (arb_optional_string(), arb_text_content()).prop_map(|(id, text)| {
2590            let mut block = Block::paragraph(vec![Text::plain(text)]);
2591            if let Block::Paragraph {
2592                id: ref mut block_id,
2593                ..
2594            } = block
2595            {
2596                *block_id = id;
2597            }
2598            block
2599        })
2600    }
2601
2602    /// Generate arbitrary heading block.
2603    fn arb_heading() -> impl Strategy<Value = Block> {
2604        (
2605            arb_optional_string(),
2606            arb_heading_level(),
2607            arb_text_content(),
2608        )
2609            .prop_map(|(id, level, text)| {
2610                let mut block = Block::heading(level, vec![Text::plain(text)]);
2611                if let Block::Heading {
2612                    id: ref mut block_id,
2613                    ..
2614                } = block
2615                {
2616                    *block_id = id;
2617                }
2618                block
2619            })
2620    }
2621
2622    /// Generate arbitrary code block.
2623    fn arb_code_block() -> impl Strategy<Value = Block> {
2624        (
2625            arb_optional_string(),
2626            arb_text_content(),
2627            arb_optional_string(),
2628        )
2629            .prop_map(|(id, code, language)| {
2630                let mut block = Block::code_block(code, language);
2631                if let Block::CodeBlock {
2632                    id: ref mut block_id,
2633                    ..
2634                } = block
2635                {
2636                    *block_id = id;
2637                }
2638                block
2639            })
2640    }
2641
2642    /// Generate arbitrary math block.
2643    fn arb_math_block() -> impl Strategy<Value = Block> {
2644        (
2645            arb_optional_string(),
2646            arb_text_content(),
2647            prop_oneof![Just(MathFormat::Latex), Just(MathFormat::Mathml)],
2648            any::<bool>(),
2649        )
2650            .prop_map(|(id, value, format, display)| {
2651                let mut block = Block::math(value, format, display);
2652                if let Block::Math(ref mut math) = block {
2653                    math.id = id;
2654                }
2655                block
2656            })
2657    }
2658
2659    proptest! {
2660        /// Paragraph JSON roundtrip - serialize and deserialize should preserve data.
2661        #[test]
2662        fn paragraph_json_roundtrip(para in arb_paragraph()) {
2663            let json = serde_json::to_string(&para).unwrap();
2664            let parsed: Block = serde_json::from_str(&json).unwrap();
2665            prop_assert_eq!(para, parsed);
2666        }
2667
2668        /// Heading JSON roundtrip - serialize and deserialize should preserve data.
2669        #[test]
2670        fn heading_json_roundtrip(heading in arb_heading()) {
2671            let json = serde_json::to_string(&heading).unwrap();
2672            let parsed: Block = serde_json::from_str(&json).unwrap();
2673            prop_assert_eq!(heading, parsed);
2674        }
2675
2676        /// Code block JSON roundtrip - serialize and deserialize should preserve data.
2677        #[test]
2678        fn code_block_json_roundtrip(code in arb_code_block()) {
2679            let json = serde_json::to_string(&code).unwrap();
2680            let parsed: Block = serde_json::from_str(&json).unwrap();
2681            prop_assert_eq!(code, parsed);
2682        }
2683
2684        /// Math block JSON roundtrip - serialize and deserialize should preserve data.
2685        #[test]
2686        fn math_block_json_roundtrip(math in arb_math_block()) {
2687            let json = serde_json::to_string(&math).unwrap();
2688            let parsed: Block = serde_json::from_str(&json).unwrap();
2689            prop_assert_eq!(math, parsed);
2690        }
2691
2692        /// Heading level is always clamped to valid range 1-6.
2693        #[test]
2694        fn heading_level_clamped(level in any::<u8>()) {
2695            let block = Block::heading(level, vec![Text::plain("Test")]);
2696            if let Block::Heading { level: actual, .. } = block {
2697                prop_assert!((1..=6).contains(&actual));
2698            } else {
2699                prop_assert!(false, "Expected Heading block");
2700            }
2701        }
2702
2703        /// Content version is always set correctly.
2704        #[test]
2705        fn content_version_is_spec_version(blocks in prop::collection::vec(arb_paragraph(), 0..5)) {
2706            let blocks_len = blocks.len();
2707            let content = Content::new(blocks);
2708            prop_assert_eq!(&content.version, crate::SPEC_VERSION);
2709            prop_assert_eq!(content.len(), blocks_len);
2710        }
2711
2712        /// Block type returns correct string for paragraphs.
2713        #[test]
2714        fn paragraph_block_type(para in arb_paragraph()) {
2715            prop_assert_eq!(para.block_type(), "paragraph");
2716        }
2717
2718        /// Block type returns correct string for headings.
2719        #[test]
2720        fn heading_block_type(heading in arb_heading()) {
2721            prop_assert_eq!(heading.block_type(), "heading");
2722        }
2723    }
2724
2725    #[test]
2726    fn code_block_highlighting_and_tokens_roundtrip() {
2727        let json = serde_json::json!({
2728            "type": "codeBlock",
2729            "value": "let x = 1;",
2730            "language": "rust",
2731            "children": [],
2732            "highlighting": "monokai",
2733            "tokens": [
2734                { "tokenType": "keyword", "value": "let" },
2735                { "tokenType": "identifier", "value": "x", "scope": "variable" }
2736            ]
2737        });
2738        let block: Block = serde_json::from_value(json).unwrap();
2739        if let Block::CodeBlock {
2740            highlighting,
2741            tokens,
2742            ..
2743        } = &block
2744        {
2745            assert_eq!(highlighting.as_deref(), Some("monokai"));
2746            let toks = tokens.as_ref().unwrap();
2747            assert_eq!(toks.len(), 2);
2748            assert_eq!(toks[0].token_type, "keyword");
2749            assert_eq!(toks[1].scope.as_deref(), Some("variable"));
2750        } else {
2751            panic!("Expected CodeBlock");
2752        }
2753        // Roundtrip
2754        let serialized = serde_json::to_value(&block).unwrap();
2755        let parsed: Block = serde_json::from_value(serialized).unwrap();
2756        assert_eq!(block, parsed);
2757    }
2758
2759    #[test]
2760    fn code_block_without_new_fields_defaults_to_none() {
2761        let json = serde_json::json!({
2762            "type": "codeBlock",
2763            "value": "print('hello')",
2764            "language": "python",
2765            "children": []
2766        });
2767        let block: Block = serde_json::from_value(json).unwrap();
2768        if let Block::CodeBlock {
2769            highlighting,
2770            tokens,
2771            ..
2772        } = &block
2773        {
2774            assert!(highlighting.is_none());
2775            assert!(tokens.is_none());
2776        } else {
2777            panic!("Expected CodeBlock");
2778        }
2779    }
2780
2781    #[test]
2782    fn figure_numbering_serialization() {
2783        // Auto
2784        let json = serde_json::to_value(FigureNumbering::Auto).unwrap();
2785        assert_eq!(json, serde_json::json!("auto"));
2786
2787        // Unnumbered
2788        let json = serde_json::to_value(FigureNumbering::Unnumbered).unwrap();
2789        assert_eq!(json, serde_json::json!("none"));
2790
2791        // Number
2792        let json = serde_json::to_value(FigureNumbering::Number(3)).unwrap();
2793        assert_eq!(json, serde_json::json!(3));
2794
2795        // Roundtrip
2796        let auto: FigureNumbering = serde_json::from_str("\"auto\"").unwrap();
2797        assert_eq!(auto, FigureNumbering::Auto);
2798        let unnumbered: FigureNumbering = serde_json::from_str("\"none\"").unwrap();
2799        assert_eq!(unnumbered, FigureNumbering::Unnumbered);
2800        let num: FigureNumbering = serde_json::from_str("3").unwrap();
2801        assert_eq!(num, FigureNumbering::Number(3));
2802    }
2803
2804    #[test]
2805    fn figure_with_numbering_and_subfigures_roundtrip() {
2806        let json = serde_json::json!({
2807            "type": "figure",
2808            "children": [
2809                { "type": "image", "src": "fig1.png", "alt": "Figure 1" }
2810            ],
2811            "numbering": "auto",
2812            "subfigures": [
2813                {
2814                    "id": "sub-a",
2815                    "label": "(a)",
2816                    "children": [
2817                        { "type": "paragraph", "children": [{ "type": "text", "value": "Sub A" }] }
2818                    ]
2819                }
2820            ]
2821        });
2822        let block: Block = serde_json::from_value(json).unwrap();
2823        if let Block::Figure(fig) = &block {
2824            assert_eq!(fig.numbering, Some(FigureNumbering::Auto));
2825            let subs = fig.subfigures.as_ref().unwrap();
2826            assert_eq!(subs.len(), 1);
2827            assert_eq!(subs[0].id.as_deref(), Some("sub-a"));
2828            assert_eq!(subs[0].label.as_deref(), Some("(a)"));
2829        } else {
2830            panic!("Expected Figure block");
2831        }
2832        let serialized = serde_json::to_value(&block).unwrap();
2833        let parsed: Block = serde_json::from_value(serialized).unwrap();
2834        assert_eq!(block, parsed);
2835    }
2836
2837    #[test]
2838    fn figure_without_new_fields_defaults_to_none() {
2839        let json = serde_json::json!({
2840            "type": "figure",
2841            "children": []
2842        });
2843        let block: Block = serde_json::from_value(json).unwrap();
2844        if let Block::Figure(fig) = &block {
2845            assert!(fig.numbering.is_none());
2846            assert!(fig.subfigures.is_none());
2847        } else {
2848            panic!("Expected Figure block");
2849        }
2850    }
2851}