use super::*;
use crate::document;
use dioxus_html as dioxus_elements;

#[non_exhaustive]
#[derive(Clone, Props, PartialEq)]
pub struct StyleProps {
    /// Styles are deduplicated by their href attribute
    pub href: Option<String>,
    pub media: Option<String>,
    pub nonce: Option<String>,
    pub title: Option<String>,
    /// The contents of the style tag. If present, the children must be a single text node.
    pub children: Element,
    #[props(extends = style, extends = GlobalAttributes)]
    pub additional_attributes: Vec<Attribute>,
}

impl StyleProps {
    /// Get all the attributes for the style tag
    pub fn attributes(&self) -> Vec<(&'static str, String)> {
        let mut attributes = Vec::new();
        extend_attributes(&mut attributes, &self.additional_attributes);
        if let Some(href) = &self.href {
            attributes.push(("href", href.clone()));
        }
        if let Some(media) = &self.media {
            attributes.push(("media", media.clone()));
        }
        if let Some(nonce) = &self.nonce {
            attributes.push(("nonce", nonce.clone()));
        }
        if let Some(title) = &self.title {
            attributes.push(("title", title.clone()));
        }
        attributes
    }

    pub fn style_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {
        extract_single_text_node(&self.children)
    }
}

/// Render a [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/style) or [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/link) tag into the head of the page.
///
/// If present, the children of the style component must be a single static or formatted string. If there are more children or the children contain components, conditionals, loops, or fragments, the style will not be added.
///
/// # Example
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// fn RedBackground() -> Element {
///     rsx! {
///         // You can use the style component to render a style tag into the head of the page
///         // This style tag will set the background color of the page to red
///         document::Style {
///             r#"
///                 body {{
///                     background-color: red;
///                 }}
///             "#
///         }
///         // You could also use a style with a href to load a stylesheet asset
///         document::Style {
///             href: asset!("/assets/style.css")
///         }
///     }
/// }
/// ```
///
/// <div class="warning">
///
/// Any updates to the props after the first render will not be reflected in the head.
///
/// </div>
#[component]
pub fn Style(props: StyleProps) -> Element {
    use_update_warning(&props, "Style {}");

    use_hook(|| {
        let document = document();
        let mut insert_style = document.create_head_component();
        if let Some(href) = &props.href {
            if !should_insert_style(href) {
                insert_style = false;
            }
        }
        if !insert_style {
            return;
        }
        let mut attributes = props.attributes();
        match (&props.href, props.style_contents()) {
            // The style has inline contents, render it as a style tag
            (_, Ok(_)) => document.create_style(props),
            // The style has a src, render it as a link tag
            (Some(_), _) => {
                attributes.push(("type", "text/css".into()));
                attributes.push(("rel", "stylesheet".into()));
                document.create_link(LinkProps {
                    media: props.media,
                    title: props.title,
                    r#type: Some("text/css".to_string()),
                    additional_attributes: props.additional_attributes,
                    href: props.href,
                    rel: Some("stylesheet".to_string()),
                    disabled: None,
                    r#as: None,
                    sizes: None,
                    crossorigin: None,
                    referrerpolicy: None,
                    fetchpriority: None,
                    hreflang: None,
                    integrity: None,
                    blocking: None,
                    onload: None,
                });
            }
            // The style has neither contents nor src, log an error
            (None, Err(err)) => err.log("Style"),
        };
    });

    VNode::empty()
}

#[derive(Default, Clone)]
struct StyleContext(DeduplicationContext);

fn should_insert_style(href: &str) -> bool {
    get_or_insert_root_context::<StyleContext>()
        .0
        .should_insert(href)
}
