mod r#macro;
mod skip_fn;

use std::ops::{Add, AddAssign};

use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum Component {
    InlineComponent(InlineComponent),
    BlockComponent(BlockComponent),
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(tag = "type")]
pub enum InlineComponent {
    Text(Text),
    Icon(Icon),
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(tag = "type")]
pub enum BlockComponent {
    Fragment(Fragment),
    Heading(Heading),
    Paragraph(Paragraph),
    ListItem(ListItem),
    List(List),
    BlockQuote(BlockQuote),
    Callout(Callout),
    Divider(Divider),
    Toggle(Toggle),
    Bookmark(Bookmark),
    File(File),
    Image(Image),
    CodeBlock(CodeBlock),
    Katex(Katex),
    Mermaid(Mermaid),
    Tab(Tab),
    Tabs(Tabs),
    Table(Table),
    TableRow(TableRow),
    TableCell(TableCell),
    ColumnList(ColumnList),
    Column(Column),
    Unsupported(Unsupported),
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Assets {
    pub icons: Vec<String>,
    pub files: Vec<String>,
    pub images: Vec<String>,
}

impl AddAssign for Assets {
    fn add_assign(&mut self, rhs: Self) {
        self.icons.extend(rhs.icons);
        self.files.extend(rhs.files);
        self.images.extend(rhs.images);
    }
}

impl Add for Assets {
    type Output = Self;

    fn add(mut self, rhs: Self) -> Self::Output {
        self += rhs;
        self
    }
}

impl Component {
    pub fn extract_assets(self) -> Assets {
        let empty_assets = Assets::default();
        self.extract_assets_recursive(empty_assets)
    }

    fn extract_assets_recursive(self, mut assets: Assets) -> Assets {
        match self {
            Component::InlineComponent(inline_component) => match inline_component {
                InlineComponent::Icon(icon) => {
                    assets.icons.push(icon.props.src);
                }
                _ => {}
            },
            Component::BlockComponent(block_component) => match block_component {
                BlockComponent::File(file) => {
                    assets.files.push(file.props.src);
                }
                BlockComponent::Image(image) => {
                    assets.images.push(image.props.src);
                }
                BlockComponent::Fragment(fragment) => {
                    for child in fragment.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::Heading(heading) => {
                    for child in heading.slots.default {
                        assets = Component::InlineComponent(child).extract_assets_recursive(assets);
                    }
                }
                BlockComponent::Paragraph(paragraph) => {
                    for child in paragraph.slots.default {
                        assets = Component::InlineComponent(child).extract_assets_recursive(assets);
                    }
                }
                BlockComponent::ListItem(list_item) => {
                    for child in list_item.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::List(list) => {
                    for child in list.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::BlockQuote(block_quote) => {
                    for child in block_quote.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::Callout(callout) => {
                    for child in callout.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::Toggle(toggle) => {
                    for child in toggle.slots.summary {
                        assets = Component::InlineComponent(child).extract_assets_recursive(assets);
                    }
                    for child in toggle.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::CodeBlock(code_block) => {
                    if let Some(slots) = code_block.slots {
                        for child in slots.default {
                            assets =
                                Component::InlineComponent(child).extract_assets_recursive(assets);
                        }
                    }
                }
                BlockComponent::Tab(tab) => {
                    for child in tab.slots.labels {
                        assets = Component::InlineComponent(child).extract_assets_recursive(assets);
                    }
                    for child in tab.slots.contents {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::Tabs(tabs) => {
                    for child in tabs.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::Table(table) => {
                    if let Some(header) = table.slots.header {
                        for child in header {
                            assets = child.extract_assets_recursive(assets);
                        }
                    }
                    for child in table.slots.body {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::TableRow(table_row) => {
                    for child in table_row.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::TableCell(table_cell) => {
                    for child in table_cell.slots.default {
                        assets = Component::InlineComponent(child).extract_assets_recursive(assets);
                    }
                }
                BlockComponent::ColumnList(column_list) => {
                    for child in column_list.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                BlockComponent::Column(column) => {
                    for child in column.slots.default {
                        assets = child.extract_assets_recursive(assets);
                    }
                }
                _ => {}
            },
        }
        assets
    }
}

#[cfg(test)]
mod tests {

    use super::{Assets, Component};

    #[test]
    fn test_extract_assets() {
        let slice = include_bytes!("./jarkup.json");
        let components = serde_json::from_slice::<Vec<Component>>(slice).unwrap();
        let assets = components
            .into_iter()
            .map(|component| component.extract_assets())
            .collect::<Vec<_>>();

        let mut merged_assets = Assets::default();

        for asset in assets {
            merged_assets += asset;
        }

        println!("{:#?}", merged_assets);
    }
}

// Text # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Text {
    pub id: Option<String>,

    pub props: TextProps,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub slots: Option<TextSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TextProps {
    pub text: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub color: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub background_color: Option<String>,

    #[serde(skip_serializing_if = "crate::skip_fn::option_false")]
    pub bold: Option<bool>,

    #[serde(skip_serializing_if = "crate::skip_fn::option_false")]
    pub italic: Option<bool>,

    #[serde(skip_serializing_if = "crate::skip_fn::option_false")]
    pub underline: Option<bool>,

    #[serde(skip_serializing_if = "crate::skip_fn::option_false")]
    pub strikethrough: Option<bool>,

    #[serde(skip_serializing_if = "crate::skip_fn::option_false")]
    pub katex: Option<bool>,

    #[serde(skip_serializing_if = "crate::skip_fn::option_false")]
    pub code: Option<bool>,

    #[serde(skip_serializing_if = "crate::skip_fn::option_false")]
    pub kbd: Option<bool>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub ruby: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub href: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub favicon: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TextSlots;

crate::to_inline_component!(Text);

// Icon # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Icon {
    pub id: Option<String>,

    /// Always `true`
    pub props: IconProps,
    // Always `None`
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slots: Option<IconSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct IconProps {
    pub src: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub alt: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct IconSlots;

crate::to_inline_component!(Icon);

// Fragment # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Fragment {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<FragmentProps>,

    pub slots: FragmentSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct FragmentProps;

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct FragmentSlots {
    pub default: Vec<Component>,
}

// Heading # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Heading {
    pub id: Option<String>,

    /// Always `false`
    pub props: HeadingProps,

    pub slots: HeadingSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
#[serde(try_from = "u8", into = "u8")]
pub enum HeadingLevel {
    #[default]
    H1,
    H2,
    H3,
    H4,
    H5,
    H6,
}

impl From<HeadingLevel> for u8 {
    fn from(level: HeadingLevel) -> Self {
        match level {
            HeadingLevel::H1 => 1,
            HeadingLevel::H2 => 2,
            HeadingLevel::H3 => 3,
            HeadingLevel::H4 => 4,
            HeadingLevel::H5 => 5,
            HeadingLevel::H6 => 6,
        }
    }
}

impl TryFrom<u8> for HeadingLevel {
    type Error = String;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            1 => Ok(HeadingLevel::H1),
            2 => Ok(HeadingLevel::H2),
            3 => Ok(HeadingLevel::H3),
            4 => Ok(HeadingLevel::H4),
            5 => Ok(HeadingLevel::H5),
            6 => Ok(HeadingLevel::H6),
            _ => Err(format!("Invalid heading level: {}", value)),
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct HeadingProps {
    pub level: HeadingLevel,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct HeadingSlots {
    pub default: Vec<InlineComponent>,
}

crate::to_block_component!(Heading);

// Paragraph # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Paragraph {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<ParagraphProps>,

    pub slots: ParagraphSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ParagraphProps {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub color: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub background_color: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ParagraphSlots {
    pub default: Vec<InlineComponent>,
}

crate::to_block_component!(Paragraph);

// ListItem # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListItem {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<ListItemProps>,

    pub slots: ListItemSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListItemProps;

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListItemSlots {
    pub default: Vec<Component>,
}

crate::to_block_component!(ListItem);

// List # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct List {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<ListProps>,
    pub slots: ListSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListProps {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub list_style: Option<ListStyle>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub enum ListStyle {
    #[default]
    Unordered,
    Ordered,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListSlots {
    pub default: Vec<Component>,
}

crate::to_block_component!(List);

// BlockQuote # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct BlockQuote {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<BlockQuoteProps>,
    pub slots: BlockQuoteSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct BlockQuoteProps {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cite: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct BlockQuoteSlots {
    pub default: Vec<Component>,
}

crate::to_block_component!(BlockQuote);

// Callout # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Callout {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<CalloutProps>,
    pub slots: CalloutSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub enum CalloutType {
    #[default]
    Note,
    Tip,
    Important,
    Warning,
    Caution,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct CalloutProps {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub r#type: Option<CalloutType>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct CalloutSlots {
    pub default: Vec<Component>,
}

crate::to_block_component!(Callout);

// Divider # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Divider {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<DividerProps>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slots: Option<DividerSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct DividerProps;

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct DividerSlots;

crate::to_block_component!(Divider);

// Toggle # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Toggle {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<ToggleProps>,
    pub slots: ToggleSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ToggleProps;

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ToggleSlots {
    pub default: Vec<Component>,
    pub summary: Vec<InlineComponent>,
}

crate::to_block_component!(Toggle);

// Bookmark # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Bookmark {
    pub id: Option<String>,

    pub props: BookmarkProps,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slots: Option<BookmarkSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct BookmarkProps {
    pub url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct BookmarkSlots;

crate::to_block_component!(Bookmark);

// File # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct File {
    pub id: Option<String>,

    pub props: FileProps,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slots: Option<FileSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct FileProps {
    pub src: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct FileSlots;

crate::to_block_component!(File);

// Image # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Image {
    pub id: Option<String>,

    pub props: ImageProps,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slots: Option<ImageSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ImageProps {
    pub src: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub alt: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub width: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub height: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub srcset: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sizes: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ImageSlots;

crate::to_block_component!(Image);

// CodeBlock # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct CodeBlock {
    pub id: Option<String>,

    pub props: CodeBlockProps,
    pub slots: Option<CodeBlockSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct CodeBlockProps {
    pub code: String,
    pub language: String,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct CodeBlockSlots {
    pub default: Vec<InlineComponent>,
}

crate::to_block_component!(CodeBlock);

// Katex # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Katex {
    pub id: Option<String>,

    pub props: KatexProps,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slots: Option<KatexSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct KatexProps {
    pub expression: String,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct KatexSlots;

crate::to_block_component!(Katex);

// Mermaid # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Mermaid {
    pub id: Option<String>,

    pub props: MermaidProps,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slots: Option<MermaidSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct MermaidProps {
    pub code: String,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct MermaidSlots;

crate::to_block_component!(Mermaid);

// Tab # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Tab {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<TabProps>,
    pub slots: TabSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TabProps {
    pub title: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TabSlots {
    pub labels: Vec<InlineComponent>,
    pub contents: Vec<Component>,
}

crate::to_block_component!(Tab);

// Tabs # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Tabs {
    pub id: Option<String>,
    pub props: Option<TabsProps>,
    pub slots: TabsSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TabsProps {
    pub title: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TabsSlots {
    pub default: Vec<Component>,
}

crate::to_block_component!(Tabs);

// Table # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Table {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<TableProps>,
    pub slots: TableSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TableProps {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub has_column_header: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub has_row_header: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub caption: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TableSlots {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub header: Option<Vec<Component>>,
    pub body: Vec<Component>,
}

crate::to_block_component!(Table);

// TableRow # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TableRow {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<TableRowProps>,
    pub slots: TableRowSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TableRowProps;

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TableRowSlots {
    pub default: Vec<Component>,
}

crate::to_block_component!(TableRow);

// TableCell # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TableCell {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<TableCellProps>,
    pub slots: TableCellSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TableCellProps {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_header: Option<bool>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TableCellSlots {
    pub default: Vec<InlineComponent>,
}

crate::to_block_component!(TableCell);

// ColumnList # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ColumnList {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<ColumnListProps>,
    pub slots: ColumnListSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ColumnListProps {}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ColumnListSlots {
    pub default: Vec<Component>,
}

crate::to_block_component!(ColumnList);

// Column # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Column {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<ColumnProps>,
    pub slots: ColumnSlots,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ColumnProps {
    pub width_ratio: Option<f32>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ColumnSlots {
    pub default: Vec<Component>,
}

crate::to_block_component!(Column);

// Unsupported # -------------------------------------------------- #
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Unsupported {
    pub id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub props: Option<UnsupportedProps>,
    pub slots: Option<UnsupportedSlots>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct UnsupportedProps {
    pub details: String,
}

#[derive(Debug, Deserialize, Serialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct UnsupportedSlots {}

crate::to_block_component!(Unsupported);
