use pest::{iterators::Pair, Parser};
use crate::{
    code::convert_code_to_html, safety::html::unsafe_string, ContextParser, ContextParserError,
    Rule,
};
fn convert_inner_pair_to_html(pair: Pair<'_, Rule>) -> Option<String> {
    pair.into_inner()
        .next()
        .and_then(|pair| convert_pair_to_html(pair).ok())
        .and_then(|converted| converted)
}
fn convert_list_to_html(pair: Pair<'_, Rule>) -> Option<String> {
    let is_nested = match pair.as_rule() {
        Rule::List => false,
        Rule::NestedList => true,
        _ => unreachable!("Only list rules are handled here"),
    };
    let mut pairs = pair.into_inner();
    let Some(indentation) = pairs
        .next()
        .and_then(|pair| convert_pair_to_html(pair).ok())
        .and_then(|pair| pair)
    else {
        return None;
    };
    let items = pairs
        .filter_map(|pair| convert_pair_to_html(pair).unwrap_or_default())
        .map(|item| format!("<span>{indentation}{item}</span>"))
        .collect::<Vec<String>>()
        .join("
");
    let suffix = if is_nested { "" } else { "
" };
    Some(format!("<span class=\"list\">{}</span>{}", items, suffix))
}
fn convert_pair_to_html(pair: Pair<'_, Rule>) -> Result<Option<String>, ContextParserError> {
    Ok(Some(match pair.as_rule() {
        Rule::IgnoredUnicode | Rule::EOI => return Ok(None),
        Rule::Block
        | Rule::Span
        | Rule::Text
        | Rule::BlockContent
        | Rule::MentionBoundary
        | Rule::SlashLinkBoundary
        | Rule::HashTagBoundary
        | Rule::QuoteBoundary
        | Rule::CodeBoundary
        | Rule::InlineCodeBoundary
        | Rule::ListItemBoundary
        | Rule::WikiLinkOpenBoundary
        | Rule::WikiLinkCloseBoundary
        | Rule::LeadingInlineBoundary
        | Rule::Bridge
        | Rule::BridgeCharacter
        | Rule::TextCharacter
        | Rule::NonTextSpan
        | Rule::Contiguous
        | Rule::Terminator
        | Rule::Whitespace
        | Rule::Indentation
        | Rule::SlugSpecialCharacter
        | Rule::Protocol
        | Rule::Context => unsafe_string(pair.as_str()).into(),
        Rule::Empty => r#"<span class="empty">
</span>"#.into(),
        Rule::Slug => format!(
            "<span class=\"slug\">{}</span>",
            unsafe_string(pair.as_str())
        ),
        Rule::Petname => format!(
            "<span class=\"petname\">{}</span>",
            unsafe_string(pair.as_str())
        ),
        Rule::HyperLink => format!(
            "<span class=\"hyperlink\"><a href=\"{0}\" target=\"_blank\">{0}</a></span>",
            unsafe_string(pair.as_str())
        ),
        Rule::Mention => {
            let Some(petname) = convert_inner_pair_to_html(pair) else {
                return Ok(None);
            };
            format!("<span class=\"mention\">@{}</span>", petname)
        }
        Rule::SlashLink => {
            let mut pairs = pair.into_inner();
            let mut mention = None;
            let mut link = None;
            while let Some(pair) = pairs.next() {
                match pair.as_rule() {
                    Rule::Mention => {
                        mention = convert_pair_to_html(pair)?;
                    }
                    Rule::Slug => link = convert_pair_to_html(pair)?,
                    _ => return Ok(None),
                }
            }
            link.map(|link| {
                format!(
                    "<span class=\"slashlink\">{}/{}</span>",
                    mention.unwrap_or_default(),
                    link
                )
            })
            .unwrap_or_default()
        }
        Rule::HashTag => {
            let Some(slug) = convert_inner_pair_to_html(pair) else {
                return Ok(None);
            };
            format!("<span class=\"hashtag\">#{}</span>", slug)
        }
        Rule::InlineCode => {
            let Some(value) = convert_inner_pair_to_html(pair) else {
                return Ok(None);
            };
            format!("<span class=\"inlinecode\"><span class=\"fence\">`</span>{}<span class=\"fence\">`</span></span>", value)
        }
        Rule::InlineCodeValue => {
            format!(
                "<span class=\"inlinecodevalue\">{}</span>",
                unsafe_string(pair.as_str())
            )
        }
        Rule::WikiLink => {
            let Some(value) = convert_inner_pair_to_html(pair) else {
                return Ok(None);
            };
            format!("<span class=\"wikilink\">[[{}]]</span>", value)
        }
        Rule::WikiLinkValue => {
            format!(
                "<span class=\"wikilinkvalue\">{}</span>",
                unsafe_string(pair.as_str())
            )
        }
        Rule::Paragraph => {
            let content = pair
                .into_inner()
                .map(|pair| {
                    convert_pair_to_html(pair)
                        .unwrap_or_default()
                        .unwrap_or_default()
                })
                .collect::<Vec<String>>()
                .concat();
            format!("<span class=\"paragraph\">{}
</span>", content)
        }
        Rule::Code => {
            let mut pairs = pair.into_inner();
            let mut slug = None;
            let mut kind = None;
            let mut code_value = None;
            while let Some(pair) = pairs.next() {
                match pair.as_rule() {
                    Rule::Slug => {
                        kind = Some(pair.as_str().to_owned());
                        slug = convert_pair_to_html(pair)?;
                    }
                    Rule::CodeValue => {
                        code_value = if let Some(kind) = kind.as_ref() {
                            let code = pair.as_str();
                            Some(
                                convert_code_to_html(kind, code)
                                    .unwrap_or_else(|| unsafe_string(code).to_string()),
                            )
                        } else {
                            Some(unsafe_string(pair.as_str()).to_string())
                        };
                    }
                    _ => return Ok(None),
                }
            }
            format!(
                    "<span class=\"code\"><span class=\"fence\">```{}</span>
{}<span class=\"fence\">```</span>
</span>",
                    slug.unwrap_or_default(),
                    code_value
                        .unwrap_or_default()
                )
        }
        Rule::CodeValue => {
            format!(
                "<span class=\"codevalue\">{}</span>",
                unsafe_string(pair.as_str())
            )
        }
        Rule::List | Rule::NestedList => convert_list_to_html(pair).unwrap_or_default(),
        Rule::ListIndentation => {
            if pair.as_str().len() == 0 {
                String::new()
            } else {
                format!(
                    "<span class=\"listidentation\">{}</span>",
                    pair.as_str()
                        .chars()
                        .map(|_| " ")
                        .collect::<Vec<&str>>()
                        .concat()
                )
            }
        }
        Rule::ListItem => {
            let mut pairs = pair.into_inner();
            let Some(content) = pairs
                .next()
                .and_then(|pair| convert_pair_to_html(pair).ok())
                .and_then(|pair| pair)
            else {
                return Ok(None);
            };
            let sublist = if let Some(pair) = pairs.next() {
                convert_pair_to_html(pair)
                    .ok()
                    .and_then(|pair| pair)
                    .map(|sublist| format!("
{}", sublist))
            } else {
                None
            };
            format!(
                "<span class=\"listitem\"><span class=\"listbullet\">- </span>{content}{}</span>",
                sublist.unwrap_or_default()
            )
        }
        Rule::ListItemContent => {
            let content = pair
                .into_inner()
                .filter_map(|pair| convert_pair_to_html(pair).ok().unwrap_or_default())
                .collect::<Vec<String>>()
                .concat();
            format!("<span class=\"listitemcontent\">{content}</span>")
        }
        Rule::Quote => {
            let lines = pair
                .into_inner()
                .filter_map(|pair| convert_pair_to_html(pair).unwrap_or_default())
                .collect::<Vec<String>>()
                .concat();
            format!("<span class=\"quote\">{lines}</span>")
        }
        Rule::QuoteLine => {
            let content = pair
                .into_inner()
                .filter_map(|pair| convert_pair_to_html(pair).ok().unwrap_or_default())
                .collect::<Vec<String>>()
                .concat();
            format!("<span class=\"quoteline\">> <span class=\"quotelinecontent\">{content}</span></span>
")
        }
    }))
}
pub fn convert_block_to_html(value: &str) -> Result<String, ContextParserError> {
    let mut root = ContextParser::parse(Rule::Block, value)
        .map_err(|error| ContextParserError::ParseError(format!("{error}")))?;
    if let Some(pair) = root.next() {
        Ok(convert_pair_to_html(pair)?.unwrap_or_default())
    } else {
        Ok(String::new())
    }
}
pub fn convert_document_to_html(value: &str) -> Result<Vec<String>, ContextParserError> {
    let root = ContextParser::parse(Rule::Context, value)
        .map_err(|error| ContextParserError::ParseError(format!("{error}")))?;
    let mut html = Vec::<String>::new();
    for context in root {
        for block in context.into_inner() {
            if let Some(html_chunk) = convert_pair_to_html(block)? {
                html.push(html_chunk);
            }
        }
    }
    Ok(html)
}
#[cfg(test)]
mod tests {
    use crate::html::{convert_block_to_html, convert_document_to_html};
    #[test]
    fn it_converts_a_basic_paragraph_to_html() {
        let html = convert_block_to_html("Hello, world!").unwrap();
        assert_eq!(html, "<span class=\"paragraph\">Hello, world!
</span>");
    }
    #[test]
    fn it_converts_multiple_basic_paragraphs_to_html() {
        let html = convert_document_to_html("Hello, \nworld!").unwrap();
        assert_eq!(
            html,
            vec![
                "<span class=\"paragraph\">Hello, 
</span>",
                "<span class=\"paragraph\">world!
</span>"
            ]
        )
    }
    #[test]
    fn it_converts_a_complex_paragraph_to_html() {
        let html = convert_block_to_html(
            "Hello, world! This #paragraph contains /interesting/content. [[Cool Stuff]].",
        )
        .unwrap();
        assert_eq!(
                html,
                "<span class=\"paragraph\">Hello, world! This <span class=\"hashtag\">#<span class=\"slug\">paragraph</span></span> contains <span class=\"slashlink\">/<span class=\"slug\">interesting/content</span></span>. <span class=\"wikilink\">[[<span class=\"wikilinkvalue\">Cool Stuff</span>]]</span>.
</span>"
            );
    }
    #[test]
    fn it_converts_a_basic_list_to_html() {
        let html = convert_block_to_html(
            r#"- foo
- bar
- baz"#,
        )
        .unwrap();
        assert_eq!(
                html,
                "<span class=\"list\"><span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">foo</span></span></span>
<span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">bar</span></span></span>
<span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">baz</span></span></span></span>
"
            );
    }
    #[test]
    fn it_converts_a_basic_nested_list_to_html() {
        let html = convert_block_to_html(
            r#"- foo
  - bar
- baz"#,
        )
        .unwrap();
        assert_eq!(
                html,
                "<span class=\"list\"><span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">foo</span>
<span class=\"list\"><span><span class=\"listidentation\">  </span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">bar</span></span></span></span></span></span>
<span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">baz</span></span></span></span>
"
            );
    }
    #[test]
    fn it_converts_the_project_example_to_html() {
        let html = convert_document_to_html(include_str!("../example.context")).unwrap();
        println!("{:#?}", html);
        assert_eq!(html, vec![
            "<span class=\"paragraph\">This is a paragraph. It can contain <span class=\"slashlink\">/<span class=\"slug\">slashlinks</span></span>. It may contain <span class=\"mention\">@<span class=\"petname\">mentions</span></span> and so <span class=\"slashlink\"><span class=\"mention\">@<span class=\"petname\">may</span></span>/<span class=\"slug\">slash/links</span></span>. It may also contain <span class=\"hashtag\">#<span class=\"slug\">hashtags</span></span>. It may also contain <span class=\"hyperlink\"><a href=\"https://hyper.links\" target=\"_blank\">https://hyper.links</a></span>. It may also contain <span class=\"wikilink\">[[<span class=\"wikilinkvalue\">wiki-style links</span>]]</span>. It may also contain <span class=\"inlinecode\">`<span class=\"inlinecodevalue\">code blocks</span>`</span>.
</span>",
            "<span class=\"empty\">
</span>",
            "<span class=\"list\"><span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">This is a list</span></span></span>
<span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">It can have several items</span>
<span class=\"list\"><span><span class=\"listidentation\">  </span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">The items can have nested lists</span></span></span>
<span><span class=\"listidentation\">  </span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">With multiple items</span>
<span class=\"list\"><span><span class=\"listidentation\">    </span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">They can be nested as deep as you like</span></span></span></span></span></span></span></span></span>
<span><span class=\"listitem\"><span class=\"listbullet\">- </span><span class=\"listitemcontent\">With <span class=\"hashtag\">#<span class=\"slug\">all</span></span> the <span class=\"slashlink\">/<span class=\"slug\">same</span></span> <span class=\"slashlink\"><span class=\"mention\">@<span class=\"petname\">content</span></span>/<span class=\"slug\">types</span></span> as <span class=\"hyperlink\"><a href=\"http://a.paragraph\" target=\"_blank\">http://a.paragraph</a></span></span></span></span></span>
",
            "<span class=\"empty\">
</span>",
            "<span class=\"quote\"><span class=\"quoteline\">> <span class=\"quotelinecontent\">This is a quote block</span></span>
<span class=\"quoteline\">> <span class=\"quotelinecontent\">It may be spread across several lines</span></span>
<span class=\"quoteline\">> <span class=\"quotelinecontent\">and may include <span class=\"hashtag\">#<span class=\"slug\">the</span></span> <span class=\"mention\">@<span class=\"petname\">same</span></span> <span class=\"slashlink\"><span class=\"mention\">@<span class=\"petname\">kinds</span></span>/<span class=\"slug\">of</span></span> <span class=\"hyperlink\"><a href=\"https://content.as/a?paragraph\" target=\"_blank\">https://content.as/a?paragraph</a></span></span></span>
</span>",
            "<span class=\"empty\">
</span>",
            "<span class=\"code\"><span class=\"fence\">```</span>
// This is a code block\n// It can contain arbitrary text\n// spread across many lines\nprint("Hi!");\n<span class=\"fence\">```</span></span>",
            "<span class=\"empty\">
</span>",
            "<span class=\"code\"><span class=\"fence\">```<span class=\"slug\">rust</span></span>
<span class=\"comment\">// A code block may be tagged</span>
<span class=\"keyword\">fn</span> <span class=\"function\">main</span>() {
  <span class=\"macro\">println!</span>(<span class=\"string\">"Hi!"</span>);
}
<span class=\"fence\">```</span></span>",
        ]);
    }
}