use lazy_static::lazy_static;
use pulldown_cmark::*;
use regex::Regex;
use tracing::debug;

lazy_static! {
    static ref REPLACE: Regex = Regex::new(
        r#"(#([A-Za-z0-9]+))|(@([A-Za-z0-9_-]+))|(http://\S+[[:alnum:]])|(https://\S+[[:alnum:]])"#
    )
    .unwrap();
}

pub struct Rendered {
    pub rendered: String,
}

pub fn render_markdown(owner: &str, repo: &str, s: &str) -> Rendered {
    let mut options = Options::empty();
    options.insert(
        Options::ENABLE_TABLES
            | Options::ENABLE_FOOTNOTES
            | Options::ENABLE_STRIKETHROUGH
            | Options::ENABLE_TASKLISTS
            | Options::ENABLE_SMART_PUNCTUATION,
    );
    let mut html_output = String::new();
    let mut it = P {
        p: Parser::new_ext(s, options),
        next: [None, None, None],
        owner,
        repo,
        current_text: None,
        mentions: Vec::new(),
    };
    html::push_html(&mut html_output, &mut it);
    Rendered {
        rendered: ammonia::clean(&html_output),
    }
}

struct P<'a> {
    p: Parser<'a>,
    next: [Option<Event<'a>>; 3],
    current_text: Option<(CowStr<'a>, usize)>,
    owner: &'a str,
    repo: &'a str,
    mentions: Vec<String>,
}

impl<'a> Iterator for P<'a> {
    type Item = Event<'a>;
    fn next(&mut self) -> Option<Self::Item> {
        loop {
            debug!("{:?} {:?}", self.next, self.current_text);
            if let Some(next) = self.next[0].take() {
                return Some(next);
            }
            if let Some(next) = self.next[1].take() {
                return Some(next);
            }
            if let Some(next) = self.next[2].take() {
                return Some(next);
            }
            if let Some((c, i)) = self.current_text.take() {
                let cap = self.make_next(&c, i);
                if let Some((start, end)) = cap {
                    let result = if start > 0 {
                        let t = c.split_at(i).1.split_at(start).0;
                        Some(Event::Text(t.to_string().into()))
                    } else {
                        None
                    };
                    if i + end < c.len() {
                        self.current_text = Some((c, i + end));
                    }
                    if result.is_some() {
                        return result;
                    }
                } else {
                    let d = c.split_at(i).1;
                    debug!("return {:?}", d);
                    return Some(Event::Text(d.to_string().into()));
                }
            } else {
                match self.p.next() {
                    None => return None,
                    Some(Event::Html(t)) => return Some(Event::Text(t)),
                    Some(Event::Text(t)) => self.current_text = Some((t, 0)),
                    Some(e) => return Some(e),
                }
            }
        }
    }
}

impl<'a> P<'a> {
    fn make_next(&mut self, c: &str, i: usize) -> Option<(usize, usize)> {
        let d = c.split_at(i).1;
        debug!("d = {:?}", d);
        let cap = if let Some(cap) = REPLACE.captures(d) {
            cap
        } else {
            return None;
        };
        debug!("cap = {:?}", cap);
        let mut l = None;
        if let Some(disc) = cap.get(2) {
            if disc.as_str().chars().all(|c| c >= '0' && c <= '9') {
                l = Some(Tag::Link {
                    link_type: LinkType::Inline,
                    dest_url: format!(
                        "/{}/{}/discussions/{}",
                        self.owner,
                        self.repo,
                        disc.as_str()
                    )
                    .into(),
                    title: "".into(),
                    id: "".into(),
                });
            } else {
                l = Some(Tag::Link {
                    link_type: LinkType::Inline,
                    dest_url: format!("/{}/{}/changes/{}", self.owner, self.repo, disc.as_str())
                        .into(),
                    title: "".into(),
                    id: "".into(),
                });
            }
        } else if let Some(link) = cap.get(4) {
            l = Some(Tag::Link {
                link_type: LinkType::Inline,
                dest_url: format!("/{}", link.as_str()).into(),
                title: "".into(),
                id: "".into(),
            });
            self.mentions.push(link.as_str().to_string())
        } else if let Some(http) = cap.get(5) {
            l = Some(Tag::Link {
                link_type: LinkType::Inline,
                dest_url: http.as_str().to_string().into(),
                title: "".into(),
                id: "".into(),
            });
        } else if let Some(http) = cap.get(6) {
            l = Some(Tag::Link {
                link_type: LinkType::Inline,
                dest_url: http.as_str().to_string().into(),
                title: "".into(),
                id: "".into(),
            });
        }
        self.next[0] = l.clone().map(Event::Start);
        self.next[1] = Some(Event::Text(cap.get(0).unwrap().as_str().to_string().into()));
        self.next[2] = Some(Event::End(TagEnd::Link));

        let z = cap.get(0).unwrap();
        Some((z.start(), z.end()))
    }
}