use std::iter::empty;

use crate::generator::util::one_more::OneMore;
use crate::generator::util::slugify::slugify_iter;
use pulldown_cmark::{html, CowStr, Event, HeadingLevel, Tag};
use State::*;

pub struct HeadingAnchors<'a, I: Iterator<Item = Event<'a>>> {
    iter: I,
    state: State,
}

pub fn new<'a, I: Iterator<Item = Event<'a>>>(iter: I) -> HeadingAnchors<'a, I> {
    HeadingAnchors {
        iter,
        state: TakeEvent,
    }
}

impl<'a, I: Iterator<Item = Event<'a>>> Iterator for HeadingAnchors<'a, I> {
    type Item = Event<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.state {
            TakeEvent => match self.iter.next() {
                Some(e) => Some(self.handle_upstream_event(e)),
                None => {
                    self.state = Done;
                    None
                }
            },
            Done => None,
        }
    }
}

impl<'a, I: Iterator<Item = Event<'a>>> HeadingAnchors<'a, I> {
    fn handle_upstream_event(&mut self, e: Event<'a>) -> Event<'a> {
        match e {
            Event::Start(Tag::Heading(level, _, _)) => {
                let inside_heading = self.accumulate_heading_contents();
                let slug = slugify_events(&inside_heading);
                let mut inner = String::new();
                html::push_html(&mut inner, inside_heading.into_iter());
                let hash_marks = level_hash_marks(level);
                let heading_html = format!(
                        "\
                        <{} id=\"{}\">\
                        <a href=\"#{}\" class=\"header-anchor\" aria-hidden=\"true\" role=\"presentation\">{}</a> \
                        {}\
                        </{}>",
                        level, slug, slug, hash_marks, inner, level
                    );
                Event::Html(CowStr::Boxed(heading_html.into_boxed_str()))
            }
            _ => e,
        }
    }

    fn accumulate_heading_contents(&mut self) -> Vec<Event<'a>> {
        let mut events = vec![];
        while let Some(e) = self.iter.next() {
            if let Event::End(Tag::Heading(_, _, _)) = e {
                break;
            } else {
                events.push(e);
            }
        }
        events
    }
}

fn slugify_events<'a>(events: &[Event<'a>]) -> String {
    let chars = events
        .iter()
        .flat_map(|e| -> Box<dyn Iterator<Item = char>> {
            match e {
                Event::Text(s) | Event::Code(s) => Box::new(OneMore::new(s.chars(), '-')),
                _ => Box::new(empty()),
            }
        });
    slugify_iter(chars)
}

fn level_hash_marks(level: HeadingLevel) -> &'static str {
    match level {
        HeadingLevel::H1 => "#",
        HeadingLevel::H2 => "##",
        HeadingLevel::H3 => "###",
        HeadingLevel::H4 => "####",
        HeadingLevel::H5 => "#####",
        HeadingLevel::H6 => "######",
    }
}

enum State {
    TakeEvent,
    Done,
}

#[cfg(test)]
mod tests {
    use pulldown_cmark::{html, CowStr, Event, Parser};

    fn render(s: &str) -> String {
        let mut out = String::new();
        let parser = Parser::new(s);
        let heading_anchors = super::new(parser);
        html::push_html(&mut out, heading_anchors);
        out
    }

    #[test]
    fn test_slugify_events() {
        let slugified = super::slugify_events(&[
            Event::Text(CowStr::Borrowed("foo")),
            Event::Code(CowStr::Borrowed("bar")),
            Event::Text(CowStr::Borrowed("baz")),
        ]);
        assert_eq!(slugified, "foo-bar-baz");
    }

    #[test]
    fn test_heading_ids() {
        assert_eq!(
            render("# Test"),
            r##"<h1 id="test"><a href="#test" class="header-anchor" aria-hidden="true" role="presentation">#</a> Test</h1>"##
        );
        assert_eq!(
            render("# `Test`"),
            r##"<h1 id="test"><a href="#test" class="header-anchor" aria-hidden="true" role="presentation">#</a> <code>Test</code></h1>"##
        );
        assert_eq!(
            render("# [Test](https://example.com)"),
            r##"<h1 id="test"><a href="#test" class="header-anchor" aria-hidden="true" role="presentation">#</a> <a href="https://example.com">Test</a></h1>"##
        );
    }
}
