use liwe::graph::{Graph, GraphContext};
use liwe::model::config::MarkdownOptions;
use liwe::model::graph::{blocks_to_markdown_sparce, GraphBlock};
use liwe::model::node::NodePointer;
use liwe::model::projector::Projector;
use liwe::model::tree::TreeIter;
use liwe::model::Key;
use serde::Serialize;

use super::output::{DocumentOutput, RetrieveOutput};

#[derive(Serialize)]
struct DocumentFrontmatter {
    document: DocumentMeta,
}

#[derive(Serialize)]
struct DocumentMeta {
    key: String,
    title: String,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    parents: Vec<LinkMeta>,
    #[serde(rename = "back-links", skip_serializing_if = "Vec::is_empty")]
    back_links: Vec<LinkMeta>,
}

#[derive(Serialize)]
struct LinkMeta {
    key: String,
    title: String,
}

pub struct RetrieveRenderer<'a> {
    output: &'a RetrieveOutput,
    options: &'a MarkdownOptions,
    graph: &'a Graph,
}

impl<'a> RetrieveRenderer<'a> {
    pub fn new(output: &'a RetrieveOutput, options: &'a MarkdownOptions, graph: &'a Graph) -> Self {
        Self {
            output,
            options,
            graph,
        }
    }

    pub fn render(&self) -> String {
        self.output
            .documents
            .iter()
            .map(|doc| self.render_document(doc))
            .collect::<Vec<String>>()
            .join("")
    }

    fn render_document(&self, doc: &DocumentOutput) -> String {
        let mut output = String::new();

        let frontmatter = DocumentFrontmatter {
            document: DocumentMeta {
                key: doc.key.clone(),
                title: doc.title.clone(),
                parents: doc
                    .parent_documents
                    .iter()
                    .map(|p| LinkMeta {
                        key: p.key.clone(),
                        title: p.title.clone(),
                    })
                    .collect(),
                back_links: doc
                    .backlinks
                    .iter()
                    .map(|b| LinkMeta {
                        key: b.key.clone(),
                        title: b.title.clone(),
                    })
                    .collect(),
            },
        };

        if let Ok(yaml) = serde_yaml::to_string(&frontmatter) {
            output.push_str("---\n");
            output.push_str(&yaml);
            output.push_str("---\n\n");
        }

        if !doc.content.is_empty() {
            let key = Key::name(&doc.key);
            let content = self.render_content_to_string(&key);
            output.push_str(&content);
        }

        output.push_str("\n\n");

        output
    }

    fn render_content_to_string(&self, key: &Key) -> String {
        let blocks = self.render_content(key);
        blocks_to_markdown_sparce(&blocks, self.options)
    }

    fn render_content(&self, key: &Key) -> Vec<GraphBlock> {
        let tree = self.graph.collect(key);

        let parent_lookup = |ref_key: &Key| -> Vec<(Key, String)> {
            let refs = self.graph.get_block_references_to(ref_key);
            let mut parents = Vec::new();

            for ref_id in refs {
                let node = self.graph.node(ref_id);
                if let Some(doc_node) = node.to_document() {
                    if let Some(doc_key) = doc_node.document_key() {
                        if doc_key == *key {
                            continue;
                        }
                        let title = self
                            .graph
                            .get_key_title(&doc_key)
                            .unwrap_or_else(|| doc_key.to_string());
                        if !parents.iter().any(|(k, _)| k == &doc_key) {
                            parents.push((doc_key, title));
                        }
                    }
                }
            }

            parents
        };

        let annotated = tree.annotate_references(&parent_lookup, &key.parent());

        Projector::project(TreeIter::new(&annotated), &key.parent())
    }
}
