use std::default::Default;
use std::str::FromStr;

use html5ever::tokenizer::{BufferQueue, TokenizerResult};
use html5ever::tokenizer::{StartTag, TagToken};
use html5ever::tokenizer::{Token, TokenSink, TokenSinkResult, Tokenizer, TokenizerOpts};
use html5ever::{local_name, tendril::*};
use tracing::{instrument, trace};

use crate::db::Website;

#[derive(Clone)]
struct LinkParser<'a> {
    site: &'a Website,
}

impl TokenSink for LinkParser<'_> {
    type Handle = Vec<Website>;

    fn process_token(&self, token: Token, _line_number: u64) -> TokenSinkResult<Self::Handle> {
        match token {
            TagToken(tag) => {
                if tag.kind == StartTag {
                    match tag.name {
                        local_name!("a")
                        | local_name!("audio")
                        | local_name!("area")
                        | local_name!("img")
                        | local_name!("link")
                        | local_name!("object")
                        | local_name!("source")
                        | local_name!("base")
                        | local_name!("video") => {
                            let mut links = Vec::new();
                            for attr in &tag.attrs {
                                let attr_name = attr.name.local.to_string();
                                if attr_name == "src" || attr_name == "href" || attr_name == "data"
                                {
                                    // Get clone of the current site object
                                    let mut web = self.site.clone();

                                    // Set url
                                    let mut url = web.site;
                                    url.set_fragment(None); // removes #xyz
                                    let joined = url.join(&attr.value).unwrap();
                                    web.site = joined;

                                    web.crawled = false;
                                    
                                    links.push(web);
                                }
                            }
                            
                            return TokenSinkResult::Script(links);
                        }
                        local_name!("button") | local_name!("meta") | local_name!("iframe") => {
                            // dbg!(attrs);
                        }
                        _ => {}
                    }
                }
            }
            _ => {}
        }
        TokenSinkResult::Continue
    }
}


#[instrument(skip_all)]
pub async fn parse(site: &Website, data: String) -> Vec<Website> {

    let sink = LinkParser { site };
    let chunk = Tendril::from_str(&data).unwrap();
    let mut input = BufferQueue::default();
    input.push_back(chunk.try_reinterpret::<fmt::UTF8>().unwrap());

    let token = Tokenizer::new(sink.clone(), TokenizerOpts::default());
    
    // let mut links_to = Vec::new();
    let mut res = Vec::new();
    while !input.is_empty() {
        if let TokenizerResult::Script(mut s) = token.feed(&mut input) {
            res.append(&mut s);
        }
    }
    trace!("Found {} links.", res.len());

    assert!(input.is_empty());
    token.end();
    res
}
