use serde::Serialize;

use super::macros::render;
use super::render_context::RenderContext;
use crate::context::macros::push_file;
use crate::context::RenderDocumentElement;
use crate::context::RenderRealFootnoteDefinition;
use crate::error::CustomError;
use crate::intermediate::get_web_path;
use crate::intermediate::BlogPost;

use super::GlobalSettings;
use super::PageHeader;

#[derive(Debug)]
pub(crate) struct RenderBlogStreamInput<'a, 'b> {
    original: &'a [&'b BlogPost],
    older_link: Option<String>,
    newer_link: Option<String>,
}

impl<'a, 'b> RenderBlogStreamInput<'a, 'b> {
    pub(crate) fn new(
        original: &'a [&'b BlogPost],
        older_link: Option<String>,
        newer_link: Option<String>,
    ) -> RenderBlogStreamInput<'a, 'b> {
        RenderBlogStreamInput {
            original,
            older_link,
            newer_link,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "blog_stream")]
pub(crate) struct RenderBlogStream {
    global_settings: GlobalSettings,
    page_header: Option<PageHeader>,
    children: Vec<RenderBlogStreamEntry>,
    stream_pagination: Option<RenderBlogStreamPagination>,
}

render!(
    RenderBlogStream,
    RenderBlogStreamInput,
    original,
    render_context,
    {
        let css_files = vec![
            get_web_path(
                render_context.config,
                render_context.output_root_directory,
                render_context.output_file,
                "stylesheet/reset.css",
            )?,
            get_web_path(
                render_context.config,
                render_context.output_root_directory,
                render_context.output_file,
                "stylesheet/main.css",
            )?,
        ];
        let js_files = vec![get_web_path(
            render_context.config,
            render_context.output_root_directory,
            render_context.output_file,
            "blog_post.js",
        )?];
        let global_settings = GlobalSettings::new(
            render_context.config.get_site_title().map(str::to_string),
            css_files,
            js_files,
        );
        let page_header = PageHeader::new(
            render_context.config.get_site_title().map(str::to_string),
            Some(get_web_path(
                render_context.config,
                render_context.output_root_directory,
                render_context.output_file,
                "",
            )?),
        );

        let children = original
            .original
            .iter()
            .enumerate()
            .map(|(i, blog_post)| {
                RenderBlogStreamEntry::new(
                    render_context.clone(),
                    &RenderBlogStreamEntryInput::new(blog_post, i),
                )
            })
            .collect::<Result<Vec<_>, _>>()?;

        let stream_pagination = if original.older_link.is_some() || original.newer_link.is_some() {
            Some(RenderBlogStreamPagination::new(
                original.older_link.clone(),
                original.newer_link.clone(),
            )?)
        } else {
            None
        };

        Ok(RenderBlogStream {
            global_settings,
            page_header: Some(page_header),
            children,
            stream_pagination,
        })
    }
);

#[derive(Debug)]
pub(crate) struct RenderBlogStreamEntryInput<'a> {
    original: &'a BlogPost,
    offset: usize,
}

impl<'a> RenderBlogStreamEntryInput<'a> {
    fn new(original: &'a BlogPost, offset: usize) -> RenderBlogStreamEntryInput<'a> {
        RenderBlogStreamEntryInput { original, offset }
    }
}

#[derive(Debug, Serialize)]
pub(crate) struct RenderBlogStreamEntry {
    /// The title that will be shown visibly on the page.
    title: Option<String>,

    self_link: Option<String>,

    children: Vec<RenderDocumentElement>,

    footnotes: Vec<RenderRealFootnoteDefinition>,
}

render!(
    RenderBlogStreamEntry,
    RenderBlogStreamEntryInput,
    original,
    render_context,
    {
        let offset_string = original.offset.to_string();
        let render_context = {
            let mut render_context = render_context.clone();
            render_context.id_addition = Some(offset_string.as_str());
            render_context
        };
        let link_to_blog_post = get_web_path(
            render_context.config,
            render_context.output_root_directory,
            render_context.output_file,
            render_context
                .config
                .get_relative_path_to_post(&original.original.id),
        )?;

        // TODO: Should I guess an index page instead of erroring out?
        let index_page = original
            .original
            .get_index_page()
            .ok_or_else(|| format!("Blog post {} needs an index page.", original.original.id))?;

        push_file!(render_context, &index_page.src, {
            let title = index_page.title.clone();

            let children = index_page
                .children
                .iter()
                .map(|child| RenderDocumentElement::new(render_context.clone(), child))
                .collect::<Result<Vec<_>, _>>()?;

            let footnotes = {
                let mut ret = Vec::new();

                for footnote in index_page.footnotes.iter() {
                    ret.push(RenderRealFootnoteDefinition::new(
                        render_context.clone(),
                        footnote,
                    )?);
                }

                ret
            };

            Ok(RenderBlogStreamEntry {
                title,
                self_link: Some(link_to_blog_post),
                children,
                footnotes,
            })
        })
    }
);

#[derive(Debug, Serialize)]
pub(crate) struct RenderBlogStreamPagination {
    older_link: Option<String>,
    newer_link: Option<String>,
}

impl RenderBlogStreamPagination {
    fn new(
        older_link: Option<String>,
        newer_link: Option<String>,
    ) -> Result<RenderBlogStreamPagination, CustomError> {
        Ok(RenderBlogStreamPagination {
            older_link,
            newer_link,
        })
    }
}