use crate::ast::ArgSpec;

use super::{BlockSpec, Forge, InlineSpec};

#[derive(Debug, Clone, Default)]
/// Builder for registering mdforge block and inline extensions.
pub struct ForgeBuilder {
    blocks: Vec<BlockSpec>,
    inlines: Vec<InlineSpec>,
}

impl ForgeBuilder {
    /// Start defining a block extension.
    pub fn block(self, name: impl Into<String>) -> BlockBuilder {
        BlockBuilder {
            parent: self,
            name: name.into(),
            args: Vec::new(),
            body_markdown: false,
        }
    }

    /// Start defining an inline extension.
    pub fn inline(self, name: impl Into<String>) -> InlineBuilder {
        InlineBuilder {
            parent: self,
            name: name.into(),
            args: Vec::new(),
        }
    }

    /// Finish the schema and return a [`Forge`].
    pub fn build(self) -> Forge {
        Forge {
            blocks: self.blocks,
            inlines: self.inlines,
        }
    }
}

#[derive(Debug, Clone)]
/// Builder for a single block extension.
pub struct BlockBuilder {
    parent: ForgeBuilder,
    name: String,
    args: Vec<(String, ArgSpec)>,
    body_markdown: bool,
}

impl BlockBuilder {
    /// Add an argument to the block definition.
    pub fn arg(mut self, name: impl Into<String>, spec: ArgSpec) -> Self {
        self.args.push((name.into(), spec));
        self
    }

    /// Mark this block body as Markdown.
    pub fn body_markdown(mut self) -> Self {
        self.body_markdown = true;
        self
    }

    /// Register the block and return to the parent [`ForgeBuilder`].
    pub fn register(self) -> ForgeBuilder {
        let mut parent = self.parent;
        parent.blocks.push(BlockSpec {
            name: self.name,
            args: self.args,
            body_markdown: self.body_markdown,
        });
        parent
    }
}

#[derive(Debug, Clone)]
/// Builder for a single inline extension.
pub struct InlineBuilder {
    parent: ForgeBuilder,
    name: String,
    args: Vec<(String, ArgSpec)>,
}

impl InlineBuilder {
    /// Add an argument to the inline definition.
    pub fn arg(mut self, name: impl Into<String>, spec: ArgSpec) -> Self {
        self.args.push((name.into(), spec));
        self
    }

    /// Register the inline and return to the parent [`ForgeBuilder`].
    pub fn register(self) -> ForgeBuilder {
        let mut parent = self.parent;
        parent.inlines.push(InlineSpec {
            name: self.name,
            args: self.args,
        });
        parent
    }
}
