use anyhow::{anyhow, Context};
use serde::{Deserialize, Serialize};
use std::io::Write;
use tera::Context as TCtx;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PrecompiledFormat {
    Html,
    Markdown,
    LaTeX,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PrecompiledTemplate {
    Paragraph,
    Emphasis,
    Strong,
    HardBreak,
    SoftBreak,
}

impl PrecompiledTemplate {
    pub fn render(
        &self,
        format: &PrecompiledFormat,
        ctx: &TCtx,
        mut buf: impl Write,
    ) -> anyhow::Result<()> {
        match self {
            PrecompiledTemplate::Paragraph => match format {
                PrecompiledFormat::Html => write!(
                    &mut buf,
                    "<p>{}</p>",
                    ctx.get("value")
                        .ok_or(anyhow!("missing value"))?
                        .as_str()
                        .unwrap()
                )
                .context("rendering"),
                PrecompiledFormat::Markdown => writeln!(
                    &mut buf,
                    "{}",
                    ctx.get("value")
                        .ok_or(anyhow!("missing value"))?
                        .as_str()
                        .unwrap()
                )
                .context("rendering"),
                _ => Err(anyhow!(format!("unsupported format {:?}", format))),
            },
            PrecompiledTemplate::Emphasis => match format {
                PrecompiledFormat::Html => write!(
                    &mut buf,
                    "<em>{}</em>",
                    ctx.get("value")
                        .ok_or(anyhow!("missing value"))?
                        .as_str()
                        .unwrap()
                )
                .context("rendering"),
                PrecompiledFormat::Markdown => write!(
                    &mut buf,
                    "_{}_",
                    ctx.get("value")
                        .ok_or(anyhow!("missing value"))?
                        .as_str()
                        .unwrap()
                )
                .context("rendering"),
                _ => Err(anyhow!(format!("unsupported format {:?}", format))),
            },
            PrecompiledTemplate::Strong => match format {
                PrecompiledFormat::Html => write!(
                    &mut buf,
                    "<strong>{}</strong>",
                    ctx.get("value")
                        .ok_or(anyhow!("missing value"))?
                        .as_str()
                        .unwrap()
                )
                .context("rendering"),
                PrecompiledFormat::Markdown => write!(
                    &mut buf,
                    "**{}**",
                    ctx.get("value")
                        .ok_or(anyhow!("missing value"))?
                        .as_str()
                        .unwrap()
                )
                .context("rendering"),
                _ => Err(anyhow!(format!("unsupported format {:?}", format))),
            },
            PrecompiledTemplate::HardBreak => match format {
                PrecompiledFormat::Html => write!(&mut buf, "<br>",).context("rendering"),
                PrecompiledFormat::Markdown => write!(&mut buf, "\n\n",).context("rendering"),
                _ => Err(anyhow!(format!("unsupported format {:?}", format))),
            },
            PrecompiledTemplate::SoftBreak => match format {
                PrecompiledFormat::Html => write!(&mut buf, "",).context("rendering"),
                PrecompiledFormat::Markdown => writeln!(&mut buf).context("rendering"),
                _ => Err(anyhow!(format!("unsupported format {:?}", format))),
            },
        }?;
        Ok(())
    }
}
