use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PostMetadata {
    pub slug: String,
    pub image_path: String,
    pub title: String,
    pub date: chrono::DateTime<chrono::Local>,
    pub description: String,
    pub project_link: String,
    pub draft: bool,
    #[serde(default)]
    pub reading_time: u64,
    pub tags: Vec<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Post {
    pub metadata: PostMetadata,
    pub content: String,
}

cfg_if::cfg_if! {
    if #[cfg(feature = "ssr")] {
        #[derive(Debug, thiserror::Error)]
        pub enum PostDeserializationError {
            #[error("Invalid front matter")]
            InvalidFrontMatter,
            #[error("Invalid markdown")]
            InvalidMarkdown,
        }

        impl TryFrom<String> for Post {
            type Error = PostDeserializationError;

            fn try_from(content: String) -> Result<Self, Self::Error> {
                use gray_matter::{Matter, engine::YAML};
                let matter = Matter::<YAML>::new();
                let mut post_data = matter
                    .parse_with_struct::<PostMetadata>(&content)
                    .ok_or_else(|| PostDeserializationError::InvalidFrontMatter)?;

                let content = post_data.content;

                post_data.data.reading_time = crate::app::utils::reading_time::calculate_reading_time(&content);

                use pulldown_cmark::{Parser, Options, html};
                let parser = Parser::new_ext(&content, Options::all());
                let mut html_output = String::new();
                html::push_html(&mut html_output, parser);

                if html_output.is_empty() {
                    return Err(PostDeserializationError::InvalidMarkdown);
                }

                Ok(Self {
                    metadata: post_data.data,
                    content: html_output,
                })
            }
        }
    }
}

