use crate::error::Error;

use crate::types::{Line, Part, Section, SimpleChord, Song};

mod iter_part;
mod iter_section;
mod iter_tab;
mod parse_header;

use iter_part::PartIterator;
use iter_section::SectionIterator;
use iter_tab::TabIterator;

#[cfg(feature = "html")]
fn get_nested_field<'a>(json: &'a serde_json::Value, keys: &[&str]) -> Option<&'a str> {
    let mut current_value = json;
    for key in keys {
        current_value = match current_value.get(key) {
            Some(value) => value,
            None => return None,
        };
    }
    current_value.as_str()
}

#[cfg(feature = "download")]
pub fn load_url(url: &str) -> Result<Song, Error> {
    let response = reqwest::blocking::get(url)?;
    if !response.status().is_success() {
        return Err(Error::Other(format!("status ({})", response.status(),)));
    }
    load_html(&response.text()?)
}

#[cfg(feature = "html")]
pub fn load_html(html: &str) -> Result<Song, Error> {
    let html = scraper::Html::parse_document(html);
    let selector = scraper::Selector::parse("div.js-store").unwrap();
    let element = html
        .select(&selector)
        .next()
        .ok_or(Error::Parse("div.js_store not found".into()))?;
    let json = element
        .value()
        .attr("data-content")
        .ok_or(Error::Parse("data-content not found".into()))?;
    let json: serde_json::Value = serde_json::from_str(json)?;
    let content = get_nested_field(
        &json,
        &["store", "page", "data", "tab_view", "wiki_tab", "content"],
    )
    .ok_or(Error::Parse("content not found".into()))?
    .replace("\r\n", "\n");
    let title = get_nested_field(&json, &["store", "page", "data", "tab", "song_name"])
        .ok_or(Error::Parse("title not found".into()))?;
    let artist = get_nested_field(&json, &["store", "page", "data", "tab", "artist_name"])
        .ok_or(Error::Parse("artist not found".into()))?;
    let key = get_nested_field(&json, &["store", "page", "data", "tab", "tonality_name"])
        .ok_or(Error::Parse("key not found".into()))?;
    load_string(&content, title, artist, key)
}

pub fn load_string(content: &str, title: &str, artist: &str, key: &str) -> Result<Song, Error> {
    let mut section_iter = SectionIterator::new(content);

    let mut tempo = None;
    let mut time = None;
    if let Some(header) = section_iter.next() {
        (tempo, time) = parse_header::parse_header(header);
    }

    let sections = section_iter
        .map(|section| {
            let index = section.find('\n').unwrap();
            let title = section[1..index - 1].to_string();
            let lines = TabIterator::new(&section[index + 1..])
                .map(|tab| {
                    let parts = PartIterator::new(tab).collect::<Result<Vec<Part>, Error>>()?;
                    Ok(Line::new(parts))
                })
                .collect::<Result<Vec<Line>, Error>>()?;
            Ok(Section::new(title, lines))
        })
        .collect::<Result<Vec<Section>, Error>>()?;

    Ok(Song {
        title: title.into(),
        subtitle: None,  // TODO: parse subtitle
        copyright: None, // TODO: parse copyright
        key: Some(SimpleChord::guess_key(key)),
        artist: Some(artist.into()),
        language: None, // TODO: parse language
        tempo,
        time,
        sections,
    }
    .normalize()
    .clone())
}
