use std::collections::{HashMap, HashSet};

use crate::{ast::ArgSpec, BlockNode, Diagnostic, Document, InlineExt, VNode};

mod args;
mod builder;
mod eval;
mod html;
mod parse;
mod render;
mod signature;
mod validate;

pub use builder::ForgeBuilder;
pub use html::HtmlRenderer;

/// Renderer used by [`Forge::render_dom`] to map extensions to [`VNode`]s.
pub trait DomRenderer {
    /// Render a block extension with already-rendered child nodes.
    fn render_block(&self, block: &BlockNode, ctx: &EvalContext, children: Vec<VNode>) -> VNode;

    /// Render an inline extension.
    fn render_inline(&self, inline: &InlineExt, ctx: &EvalContext) -> VNode;
}

/// Runtime values used by the evaluation phase.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct EvalContext {
    /// Allowed values for `ArgType::DynamicEnum`, keyed by dynamic enum name.
    pub dynamic_values: HashMap<String, HashSet<String>>,
}

/// Registered block extension specification.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockSpec {
    /// Block name.
    pub name: String,
    /// Argument specifications in registration order.
    pub args: Vec<(String, ArgSpec)>,
    /// Whether the block body is intended to contain Markdown.
    pub body_markdown: bool,
}

/// Registered inline extension specification.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlineSpec {
    /// Inline name.
    pub name: String,
    /// Argument specifications in registration order.
    pub args: Vec<(String, ArgSpec)>,
}

/// Compiled mdforge schema and processing pipeline.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Forge {
    blocks: Vec<BlockSpec>,
    inlines: Vec<InlineSpec>,
}

impl Forge {
    /// Start building a [`Forge`] schema.
    pub fn builder() -> ForgeBuilder {
        ForgeBuilder::default()
    }

    /// Parse source text into a [`Document`].
    pub fn parse(&self, input: &str) -> Result<Document, Vec<Diagnostic>> {
        parse::parse_document(self, input)
    }

    /// Validate parsed blocks, inlines, required arguments, and static types.
    pub fn validate(&self, doc: &Document) -> Result<(), Vec<Diagnostic>> {
        validate::validate_document(self, doc)
    }

    /// Evaluate runtime-dependent constraints such as dynamic enums.
    pub fn eval(
        &self,
        doc: &Document,
        dynamic_ctx: &EvalContext,
    ) -> Result<EvalContext, Vec<Diagnostic>> {
        eval::eval_document(self, doc, dynamic_ctx)
    }

    /// Render a document into a virtual DOM using a custom renderer.
    pub fn render_dom(
        &self,
        doc: &Document,
        ctx: &EvalContext,
        renderer: &dyn DomRenderer,
    ) -> Result<Vec<VNode>, Vec<Diagnostic>> {
        render::render_document(self, doc, ctx, renderer)
    }

    /// Generate a compact syntax signature for prompts or documentation.
    pub fn signature(&self) -> String {
        signature::build_signature(self)
    }

    /// Render a document to HTML using pulldown-cmark for normal Markdown.
    pub fn render_html(
        &self,
        doc: &Document,
        ctx: &EvalContext,
        renderer: &dyn HtmlRenderer,
    ) -> Result<String, Vec<Diagnostic>> {
        html::render_html_document(self, doc, ctx, renderer)
    }
}

#[cfg(test)]
mod tests;
