use pulldown_cmark::{html, CowStr, Event, Tag};
use url::Url;

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

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

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

    fn next(&mut self) -> Option<Self::Item> {
        match self.iter.next() {
            Some(Event::Start(Tag::Link(_, href, title))) => {
                let link = href_to_pretty_link(&href);
                let mut s = format!(r#"<a href="{}" data-link="{}"#, href, link);
                if !title.is_empty() {
                    s.push_str(" title=\"");
                    s.push_str(title.as_ref());
                }
                s.push_str("\">");
                html::push_html(&mut s, LinkContents(&mut self.iter));
                s.push_str("</a>");

                Some(Event::Html(CowStr::Boxed(s.into_boxed_str())))
            }
            e => e,
        }
    }
}

struct LinkContents<'a, 'b, I: Iterator<Item = Event<'a>>>(&'b mut I);

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

    fn next(&mut self) -> Option<Self::Item> {
        match self.0.next() {
            Some(Event::End(Tag::Link(_, _, _))) => None,
            e => e,
        }
    }
}

fn href_to_pretty_link<'a>(href: &CowStr<'a>) -> CowStr<'a> {
    match Url::parse(href) {
        Ok(url) => {
            let mut s = String::new();
            let host = url.host_str();
            if let Some(h) = host {
                if h.starts_with("www.") {
                    s.push_str(&h[4..]);
                } else {
                    s.push_str(h);
                }
            }
            let path = url.path();
            if path != "/" {
                s.push_str(path);
            }

            if host.map(|s| s.ends_with("youtube.com")).unwrap_or(false) && path == "/watch" {
                s.push('?');
                s.push_str(url.query().unwrap());
            }

            if s.len() > 40 {
                s = s.chars().take(40).collect();
                s.push('…');
            }

            CowStr::Boxed(s.into_boxed_str())
        }
        Err(_) => {
            // if there was an error parsing the url, we just pass it through unchanged
            href.clone()
        }
    }
}

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

    fn pretty_link<'a>(s: &'a str) -> CowStr<'a> {
        super::href_to_pretty_link(&CowStr::Borrowed(s))
    }

    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_href_to_pretty_link() {
        assert_eq!(
            pretty_link("https://asahilinux.org/2022/03/asahi-linux-alpha-release/").as_ref(),
            "asahilinux.org/2022/03/asahi-linux-alpha…"
        );
        assert_eq!(
            pretty_link("https://www.youtube.com/watch?v=MFzDaBzBlL0").as_ref(),
            "youtube.com/watch?v=MFzDaBzBlL0"
        );
        assert_eq!(
            pretty_link("https://www.youtube.com/watch?v=MFzDaBzBlL0&t=10").as_ref(),
            "youtube.com/watch?v=MFzDaBzBlL0&t=10"
        );
    }

    #[test]
    fn test_link_decorations() {
        assert_eq!(
            render("[foo](bar)").trim(),
            r#"<p><a href="bar" data-link="bar">foo</a></p>"#
        );
    }
}
