use regex::Regex;

pub fn clean_html(value: &str) -> String {
    decode_entities(&strip_tags(value))
        .split_whitespace()
        .collect::<Vec<_>>()
        .join(" ")
}

pub fn strip_tags(value: &str) -> String {
    let tag_re = Regex::new(r"(?is)<[^>]+>").expect("valid tag regex");
    tag_re.replace_all(value, " ").into_owned()
}

pub fn first_capture(value: &str, pattern: &str) -> Option<String> {
    Regex::new(pattern)
        .ok()?
        .captures(value)
        .and_then(|capture| capture.get(1))
        .map(|match_| decode_entities(match_.as_str().trim()))
        .filter(|value| !value.is_empty())
}

fn decode_entities(value: &str) -> String {
    let value = value
        .replace("&amp;", "&")
        .replace("&lt;", "<")
        .replace("&gt;", ">")
        .replace("&quot;", "\"")
        .replace("&#39;", "'")
        .replace("&apos;", "'")
        .replace("&nbsp;", " ");

    decode_numeric_entities(&value)
}

fn decode_numeric_entities(value: &str) -> String {
    let entity_re = Regex::new(r"&#(x[0-9a-fA-F]+|\d+);").expect("valid numeric entity regex");
    entity_re
        .replace_all(value, |captures: &regex::Captures<'_>| {
            let raw = captures
                .get(1)
                .map(|value| value.as_str())
                .unwrap_or_default();
            let codepoint = if let Some(hex) = raw.strip_prefix('x') {
                u32::from_str_radix(hex, 16).ok()
            } else {
                raw.parse::<u32>().ok()
            };
            codepoint
                .and_then(char::from_u32)
                .map(|value| value.to_string())
                .unwrap_or_else(|| captures[0].to_string())
        })
        .into_owned()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn cleans_tags_and_common_entities() {
        assert_eq!(
            clean_html("<b>Rust&nbsp;&amp;&nbsp;Search &#23665;&#35199;</b>"),
            "Rust & Search 山西"
        );
    }
}
