use crate::*;

/// Implementation of `Parse` for `HtmlRoot`, parsing zero or more HTML nodes.
impl Parse for HtmlRoot {
    /// Parses the root of an `html!` macro invocation.
    ///
    /// # Arguments
    ///
    /// - `ParseStream`: The syn parse stream to read from.
    ///
    /// # Returns
    ///
    /// - `syn::Result<Self>`: The parsed `HtmlRoot`, or a syntax error.
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let children: Vec<HtmlNode> = parse_html_children(input)?;
        Ok(HtmlRoot { children })
    }
}

/// Implementation of `ToTokens` for `HtmlRoot`, converting root HTML nodes into virtual node tokens.
///
/// - 0 children → `VirtualNode::Empty`
/// - 1 child → the child's token stream (no Fragment wrapper)
/// - N children → `VirtualNode::Fragment(vec![...])`
impl ToTokens for HtmlRoot {
    /// Converts the root HTML nodes into a single virtual node token stream.
    ///
    /// # Arguments
    ///
    /// - `&mut proc_macro2::TokenStream`: The target token stream to append to.
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let node_tokens: proc_macro2::TokenStream = children_to_node_tokens(self.get_children());
        tokens.extend(node_tokens);
    }
}

/// Implementation of `Parse` for `HtmlNode`, parsing HTML input into a node.
impl Parse for HtmlNode {
    /// Parses a single HTML node from the token stream.
    ///
    /// # Arguments
    ///
    /// - `ParseStream`: The syn parse stream to read from.
    ///
    /// # Returns
    ///
    /// - `syn::Result<Self>`: The parsed `HtmlNode`, or a syntax error.
    fn parse(input: ParseStream) -> syn::Result<Self> {
        if input.peek(LitStr) {
            let lit: LitStr = input.parse()?;
            return Ok(HtmlNode::Text(lit.value()));
        }
        if input.peek(Token![if]) {
            let html_if: HtmlIf = input.parse()?;
            return Ok(HtmlNode::If(html_if));
        }
        if input.peek(Token![match]) {
            let html_match: HtmlMatch = input.parse()?;
            return Ok(HtmlNode::Match(html_match));
        }
        if input.peek(Token![for]) {
            let html_for: HtmlFor = input.parse()?;
            return Ok(HtmlNode::For(html_for));
        }
        if input.peek(Brace) {
            let content;
            braced!(content in input);
            let expr: Expr = content.parse()?;
            return Ok(HtmlNode::Dynamic(expr));
        }
        if input.peek(Ident) {
            if input.peek2(Brace) {
                let element: HtmlElement = input.parse()?;
                return Ok(HtmlNode::Element(element));
            }
            let expr: Expr = input.parse()?;
            return Ok(HtmlNode::Expr(expr));
        }
        Err(input.error("expected an element, string literal, if, match, for, or expression"))
    }
}

/// Implementation of `Parse` for `HtmlIf`, parsing reactive `if` conditionals.
impl Parse for HtmlIf {
    /// Parses a reactive `if` conditional into an `HtmlIf` AST.
    ///
    /// # Arguments
    ///
    /// - `ParseStream`: The syn parse stream to read from.
    ///
    /// # Returns
    ///
    /// - `syn::Result<Self>`: The parsed `HtmlIf`, or a syntax error.
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut branches: Vec<(Option<Expr>, Vec<HtmlNode>)> = Vec::new();
        input.parse::<Token![if]>()?;
        let cond_content;
        braced!(cond_content in input);
        let condition: Expr = cond_content.parse()?;
        let body_content;
        braced!(body_content in input);
        let body: Vec<HtmlNode> = parse_html_children(&body_content)?;
        branches.push((Some(condition), body));
        while input.peek(Token![else]) {
            input.parse::<Token![else]>()?;
            if input.peek(Token![if]) {
                input.parse::<Token![if]>()?;
                let cond_content;
                braced!(cond_content in input);
                let condition: Expr = cond_content.parse()?;
                let body_content;
                braced!(body_content in input);
                let body: Vec<HtmlNode> = parse_html_children(&body_content)?;
                branches.push((Some(condition), body));
            } else {
                let body_content;
                braced!(body_content in input);
                let body: Vec<HtmlNode> = parse_html_children(&body_content)?;
                branches.push((None, body));
                break;
            }
        }
        Ok(HtmlIf { branches })
    }
}

/// Implementation of `Parse` for `HtmlMatch`, parsing reactive match expressions.
///
/// Each arm body can be any valid HTML content (elements, expressions, if, etc.)
/// without requiring outer braces. Bodies are terminated by `,` or end of the
/// match block.
impl Parse for HtmlMatch {
    /// Parses a reactive `match` expression into an `HtmlMatch` AST.
    ///
    /// # Arguments
    ///
    /// - `ParseStream`: The syn parse stream to read from.
    ///
    /// # Returns
    ///
    /// - `syn::Result<Self>`: The parsed `HtmlMatch`, or a syntax error.
    fn parse(input: ParseStream) -> syn::Result<Self> {
        input.parse::<Token![match]>()?;
        let scrutinee_content;
        braced!(scrutinee_content in input);
        let scrutinee: Expr = scrutinee_content.parse()?;
        let arms_content;
        braced!(arms_content in input);
        let mut arms: Vec<(proc_macro2::TokenStream, Vec<HtmlNode>)> = Vec::new();
        while !arms_content.is_empty() {
            let mut pattern_tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
            while !arms_content.peek(Token![=>]) {
                let tt: proc_macro2::TokenTree = arms_content.parse()?;
                pattern_tokens.extend([tt]);
            }
            arms_content.parse::<Token![=>]>()?;
            let body: Vec<HtmlNode> = parse_match_arm_body(&arms_content)?;
            arms.push((pattern_tokens, body));
            if arms_content.peek(Token![,]) {
                arms_content.parse::<Token![,]>()?;
            }
        }
        Ok(HtmlMatch { scrutinee, arms })
    }
}

/// Implementation of `Parse` for `HtmlFor`, parsing reactive for loops.
impl Parse for HtmlFor {
    /// Parses a reactive `for` loop into an `HtmlFor` AST.
    ///
    /// # Arguments
    ///
    /// - `ParseStream`: The syn parse stream to read from.
    ///
    /// # Returns
    ///
    /// - `syn::Result<Self>`: The parsed `HtmlFor`, or a syntax error.
    fn parse(input: ParseStream) -> syn::Result<Self> {
        input.parse::<Token![for]>()?;
        let mut pattern_tokens: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
        while !input.peek(Token![in]) {
            let tt: proc_macro2::TokenTree = input.parse()?;
            pattern_tokens.extend([tt]);
        }
        input.parse::<Token![in]>()?;
        let iter_content;
        braced!(iter_content in input);
        let iterable: Expr = iter_content.parse()?;
        let body_content;
        braced!(body_content in input);
        let body: Vec<HtmlNode> = parse_html_children(&body_content)?;
        Ok(HtmlFor {
            pattern: pattern_tokens,
            iterable,
            body,
        })
    }
}

/// Implementation of `Parse` for `HtmlElement`, parsing HTML element syntax.
impl Parse for HtmlElement {
    /// Parses an HTML element with its attributes and children.
    ///
    /// # Arguments
    ///
    /// - `ParseStream`: The syn parse stream to read from.
    ///
    /// # Returns
    ///
    /// - `syn::Result<Self>`: The parsed `HtmlElement`, or a syntax error.
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let tag: Ident = input.parse()?;
        let tag_str: String = tag.to_string();
        let is_component: bool = tag_str.contains('_');
        let content;
        braced!(content in input);
        let mut attributes: Vec<(Ident, HtmlAttrValue)> = Vec::new();
        let mut children: Vec<HtmlNode> = Vec::new();
        while !content.is_empty() {
            if content.peek(Token![if]) {
                let html_if: HtmlIf = content.parse()?;
                children.push(HtmlNode::If(html_if));
            } else if content.peek(Token![match]) {
                let html_match: HtmlMatch = content.parse()?;
                children.push(HtmlNode::Match(html_match));
            } else if content.peek(Token![for]) {
                let html_for: HtmlFor = content.parse()?;
                children.push(HtmlNode::For(html_for));
            } else if content.peek(Brace) {
                let child_content;
                braced!(child_content in content);
                let expr: Expr = child_content.parse()?;
                children.push(HtmlNode::Dynamic(expr));
            } else if content.peek(LitStr) && content.peek2(Colon) {
                let lit_str: LitStr = content.parse()?;
                let key: Ident = Ident::new(&lit_str.value(), lit_str.span());
                content.parse::<Colon>()?;
                let key_str: String = key.to_string();
                let value: HtmlAttrValue = parse_attr_value(&content, &key_str)?;
                attributes.push((key, value));
            } else if content.peek(Ident) && (content.peek2(Colon) || content.peek2(Token![-])) {
                let key_string: String = parse_kebab_name(&content)?;
                let key_clean: &str = key_string.strip_prefix("r#").unwrap_or(&key_string);
                let key: Ident = Ident::new(key_clean, content.span());
                content.parse::<Colon>()?;
                let key_str: String = key.to_string();
                let value: HtmlAttrValue = parse_attr_value(&content, &key_str)?;
                attributes.push((key, value));
            } else if content.peek(LitStr) {
                let lit: LitStr = content.parse()?;
                children.push(HtmlNode::Text(lit.value()));
            } else if content.peek(Ident) {
                if content.peek2(Brace) {
                    let element: HtmlElement = content.parse()?;
                    children.push(HtmlNode::Element(element));
                } else {
                    let expr: Expr = content.parse()?;
                    children.push(HtmlNode::Expr(expr));
                }
            } else {
                return Err(content.error("unexpected token in HTML element"));
            }
        }
        let merged_attributes: Vec<(Ident, HtmlAttrValue)> = merge_same_key_attributes(attributes);
        Ok(HtmlElement {
            tag,
            attributes: merged_attributes,
            children,
            is_component,
        })
    }
}

/// Implementation of `ToTokens` for `HtmlNode`, converting HTML nodes into virtual node tokens.
impl ToTokens for HtmlNode {
    /// Converts this HTML node into its corresponding virtual node token stream.
    ///
    /// # Arguments
    ///
    /// - `&mut proc_macro2::TokenStream`: The target token stream to append to.
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match self {
            HtmlNode::Element(element) => element.to_tokens(tokens),
            HtmlNode::Text(text) => {
                tokens.extend(quote! {
                    ::euv_core::VirtualNode::Text(::euv_core::TextNode::new(#text.to_string(), None))
                });
            }
            HtmlNode::Expr(expr) => {
                tokens.extend(quote! {
                    ::euv_core::IntoNode::into_node(#expr)
                });
            }
            HtmlNode::Dynamic(expr) => {
                tokens.extend(quote! {
                    ::euv_core::create_dynamic_node(move || ::euv_core::IntoNode::into_node(#expr))
                });
            }
            HtmlNode::If(html_if) => {
                let mut if_chain: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
                for (i, (condition, body)) in html_if.get_branches().iter().enumerate() {
                    let body_expr: proc_macro2::TokenStream = children_to_node_tokens(body);
                    match (i, condition) {
                        (0, Some(cond)) => {
                            let stripped_cond: &Expr = strip_braces_from_expr(cond);
                            if_chain.extend(quote! {
                                if #stripped_cond {
                                    #body_expr
                                }
                            });
                        }
                        (_, Some(cond)) => {
                            let stripped_cond: &Expr = strip_braces_from_expr(cond);
                            if_chain.extend(quote! {
                                else if #stripped_cond {
                                    #body_expr
                                }
                            });
                        }
                        (_, None) => {
                            if_chain.extend(quote! {
                                else {
                                    #body_expr
                                }
                            });
                        }
                    }
                }
                tokens.extend(quote! {
                    ::euv_core::create_dynamic_node(move || { #if_chain })
                });
            }
            HtmlNode::Match(html_match) => {
                let scrutinee: &Expr = strip_braces_from_expr(html_match.get_scrutinee());
                let arm_tokens: Vec<proc_macro2::TokenStream> = html_match
                    .get_arms()
                    .iter()
                    .enumerate()
                    .map(|(arm_index, (pattern, body))| {
                        let body_expr: proc_macro2::TokenStream = children_to_node_tokens(body);
                        quote! {
                            #pattern => {
                                __euv_hook_context.set_arm_changed(#arm_index);
                                #body_expr
                            }
                        }
                    })
                    .collect();
                tokens.extend(quote! {
                    ::euv_core::create_dynamic_node_with_context(move |__euv_hook_context: &mut ::euv_core::HookContext| {
                        match #scrutinee {
                            #(#arm_tokens)*
                        }
                    })
                });
            }
            HtmlNode::For(html_for) => {
                let pattern: &proc_macro2::TokenStream = html_for.get_pattern();
                let iterable: &Expr = html_for.get_iterable();
                let body_tokens: proc_macro2::TokenStream = children_to_tokens(html_for.get_body());
                tokens.extend(quote! {
                    ::euv_core::create_dynamic_node_with_context(move |__euv_hook_context: &mut ::euv_core::HookContext| {
                        let mut __euv_for_nodes: Vec<::euv_core::VirtualNode> = Vec::new();
                        for #pattern in #iterable {
                            __euv_hook_context.reset_hook_index();
                            let __euv_for_body: Vec<::euv_core::VirtualNode> = #body_tokens;
                            __euv_for_nodes.extend(__euv_for_body);
                        }
                        ::euv_core::VirtualNode::Fragment(__euv_for_nodes)
                    })
                });
            }
        }
    }
}

/// Implementation of `ToTokens` for `HtmlStylePropValue`, converting style property values into tokens.
impl ToTokens for HtmlStylePropValue {
    /// Converts this style property value into its token stream representation.
    ///
    /// # Arguments
    ///
    /// - `&mut proc_macro2::TokenStream`: The target token stream to append to.
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match self {
            HtmlStylePropValue::Literal(s) => s.to_tokens(tokens),
            HtmlStylePropValue::Expr(expr) => expr.to_tokens(tokens),
            HtmlStylePropValue::If(html_attr_if) => {
                let if_chain: proc_macro2::TokenStream = attr_if_to_tokens(html_attr_if);
                if_chain.to_tokens(tokens);
            }
        }
    }
}

/// Implementation of `ToTokens` for `HtmlAttrValue`, converting attribute values into tokens.
///
/// For `HtmlAttrValue::If`, generates a reactive signal via `create_reactive_attr_signal`.
/// For `HtmlAttrValue::Style` containing `If` conditions, generates a reactive
/// signal via `create_reactive_style_attribute`.
/// For static values (`Expr` and `Style` without `If`), the value is emitted directly.
impl ToTokens for HtmlAttrValue {
    /// Converts this attribute value into its token stream representation.
    ///
    /// # Arguments
    ///
    /// - `&mut proc_macro2::TokenStream`: The target token stream to append to.
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match self {
            HtmlAttrValue::Expr(expr) => expr.to_tokens(tokens),
            HtmlAttrValue::If(html_attr_if) => {
                let if_chain: proc_macro2::TokenStream = attr_if_to_tokens(html_attr_if);
                tokens.extend(quote! {
                    ::euv_core::create_reactive_attr_signal(move || ::euv_core::IntoReactiveString::into_reactive_string(#if_chain))
                });
            }
            HtmlAttrValue::Style(props) => {
                let has_if: bool = props
                    .iter()
                    .any(|(_, value)| matches!(value, HtmlStylePropValue::If(_)));
                let all_literal: bool = props
                    .iter()
                    .all(|(_, value)| matches!(value, HtmlStylePropValue::Literal(_)));
                if has_if {
                    let prop_tokens: Vec<proc_macro2::TokenStream> = props
                        .iter()
                        .map(|(key, value)| {
                            quote! { .property(#key, #value) }
                        })
                        .collect();
                    tokens.extend(quote! {
                        ::euv_core::create_reactive_style_attribute(move || ::euv_core::Style::default()#(#prop_tokens)*.to_css_string())
                    });
                } else if all_literal {
                    let mut css_string: String = String::new();
                    for (key, value) in props {
                        if !css_string.is_empty() {
                            css_string.push(' ');
                        }
                        css_string.push_str(&key.replace('_', "-"));
                        css_string.push_str(": ");
                        if let HtmlStylePropValue::Literal(lit) = value {
                            css_string.push_str(lit);
                        }
                        css_string.push(';');
                    }
                    tokens.extend(quote! {
                        #css_string.to_string()
                    });
                } else {
                    let kv_tokens: Vec<proc_macro2::TokenStream> = props
                        .iter()
                        .map(|(key, value)| {
                            quote! { (#key, #value) }
                        })
                        .collect();
                    tokens.extend(quote! {
                        ::euv_core::Style::create_style_string(&[#(#kv_tokens),*])
                    });
                }
            }
            HtmlAttrValue::Classes(values) => {
                let value_tokens: Vec<proc_macro2::TokenStream> = values
                    .iter()
                    .map(|v| attr_value_to_attribute_value_tokens(v, "class", false))
                    .collect();
                tokens.extend(quote! {
                    ::euv_core::merge_class_values(&[#(#value_tokens),*])
                });
            }
            HtmlAttrValue::Styles(values) => {
                let value_tokens: Vec<proc_macro2::TokenStream> = values
                    .iter()
                    .map(style_value_to_attribute_value_tokens)
                    .collect();
                tokens.extend(quote! {
                    ::euv_core::merge_style_values(&[#(#value_tokens),*])
                });
            }
        }
    }
}

/// Implementation of `ToTokens` for `HtmlElement`, converting HTML elements into virtual element tokens.
///
/// Only the tag name uses `quote_spanned!` so that errors related to the tag
/// (e.g., unknown element) point to the tag identifier in the user's source code.
/// All other generated code uses `quote!` with default spans, preventing the
/// entire macro invocation from being highlighted when a type error occurs in
/// generated framework code.
impl ToTokens for HtmlElement {
    /// Converts this HTML element into its virtual element token stream.
    ///
    /// # Arguments
    ///
    /// - `&mut proc_macro2::TokenStream`: The target token stream to append to.
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        let tag_name: String = self.get_tag().to_string();
        let is_component: bool = self.get_is_component();
        let tag_span: Span = self.get_tag().span();
        let tag_literal: proc_macro2::TokenStream =
            quote_spanned!(tag_span=> #tag_name.to_string());
        let attr_tokens: Vec<proc_macro2::TokenStream> = self.get_attributes().iter().map(|(key, value)| {
            let key_str: String = key.to_string();
            let value_tokens: proc_macro2::TokenStream = match value {
                HtmlAttrValue::Style(props) => {
                    let has_if: bool = props.iter().any(|(_, v)| matches!(v, HtmlStylePropValue::If(_)));
                    if has_if {
                        quote! { #value }
                    } else {
                        quote! { ::euv_core::AttributeValue::Text(#value) }
                    }
                }
                HtmlAttrValue::If(_) => {
                    quote! { #value }
                }
                HtmlAttrValue::Classes(_) => {
                    quote! { #value }
                }
                HtmlAttrValue::Styles(_) => {
                    quote! { #value }
                }
                HtmlAttrValue::Expr(expr) => {
                    if let Some(event_name_str) = key_str.strip_prefix("on") {
                        if is_component {
                            let callback_name: String = key_str.replace('_', "-");
                            quote! {
                                ::euv_core::AttrValueAdapter::new(#expr).into_callback_attribute_value_with_name(#callback_name.to_string())
                            }
                        } else {
                            let event_name_ident: Ident = Ident::new(
                                &camel_case_event_name(event_name_str),
                                key.span(),
                            );
                            quote! {
                                ::euv_core::EventAdapter::new(#expr).into_attribute(::euv_core::NativeEventName::#event_name_ident)
                            }
                        }
                    } else if key_str == "children" {
                        quote! { ::euv_core::AttributeValue::Dynamic(Box::new(#expr)) }
                    } else {
                        quote! {
                            ::euv_core::AttrValueAdapter::new(#expr).into_reactive_attribute_value()
                        }
                    }
                }
            };
            let raw_key: String = if is_component {
                key_str.replace('_', "-")
            } else {
                key_str.strip_prefix("on").unwrap_or(&key_str).replace('_', "-")
            };
            let attr_name_str: String = raw_key.strip_prefix("r#").unwrap_or(&raw_key).to_string();
            quote! {
                ::euv_core::AttributeEntry::new(#attr_name_str.to_string(), #value_tokens)
            }
        }).collect();
        let child_tokens: Vec<proc_macro2::TokenStream> = self
            .get_children()
            .iter()
            .map(|child| {
                let mut ts: proc_macro2::TokenStream = proc_macro2::TokenStream::new();
                child.to_tokens(&mut ts);
                ts
            })
            .collect();
        if is_component {
            let component_fn: Ident = self.get_tag().clone();
            let component_span: Span = self.get_tag().span();
            let component_call: proc_macro2::TokenStream =
                quote_spanned!(component_span=> #component_fn(__props));
            tokens.extend(quote! {
                {
                    let __children: Vec<::euv_core::VirtualNode> = vec![#(#child_tokens),*];
                    let __props = ::euv_core::VirtualNode::Element {
                        tag: ::euv_core::Tag::Component(#tag_literal),
                        attributes: vec![#(#attr_tokens),*],
                        children: __children,
                        key: None,
                    };
                    #component_call
                }
            });
        } else {
            tokens.extend(quote! {
                ::euv_core::VirtualNode::Element {
                    tag: ::euv_core::Tag::Element(#tag_literal),
                    attributes: vec![#(#attr_tokens),*],
                    children: vec![#(#child_tokens),*],
                    key: None,
                }
            });
        }
    }
}
