use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{
    Error, Expr, FnArg, Ident, ItemFn, ItemStruct, Pat, PatIdent, Path, Result, ReturnType, Token,
    Type, braced, parenthesized, parse_macro_input,
};

#[proc_macro_attribute]
pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
    if !proc_macro2::TokenStream::from(attr).is_empty() {
        return Error::new(Span::call_site(), "#[component] takes no arguments")
            .to_compile_error()
            .into();
    }

    let function = parse_macro_input!(item as ItemFn);
    match expand_component(function) {
        Ok(tokens) => tokens.into(),
        Err(error) => error.to_compile_error().into(),
    }
}

#[proc_macro]
pub fn view(input: TokenStream) -> TokenStream {
    let root = parse_macro_input!(input as ViewRoot);
    expand_node(&root.root).into()
}

#[proc_macro_attribute]
pub fn context_provider(attr: TokenStream, item: TokenStream) -> TokenStream {
    let value_ty = parse_macro_input!(attr as Type);
    let item_struct = parse_macro_input!(item as ItemStruct);
    match expand_context_provider(item_struct, value_ty) {
        Ok(tokens) => tokens.into(),
        Err(error) => error.to_compile_error().into(),
    }
}

fn expand_component(mut function: ItemFn) -> Result<proc_macro2::TokenStream> {
    validate_component_function(&function)?;

    let vis = function.vis.clone();
    let component_name = function.sig.ident.clone();
    let render_name = format_ident!("__ruin_app_render_{}", component_name);
    function.sig.ident = render_name.clone();
    function
        .attrs
        .push(syn::parse_quote!(#[allow(non_snake_case)]));

    let mut inputs = function
        .sig
        .inputs
        .iter()
        .map(parse_prop)
        .collect::<Result<Vec<_>>>()?;
    let child_contract = match inputs.last() {
        Some(prop) if prop.ident == "children" => {
            let prop = inputs.pop().expect("last input should exist");
            let kind = parse_children_contract_kind(&prop.ty)?;
            Some(ChildContract { ty: prop.ty, kind })
        }
        _ => None,
    };
    let props = inputs;

    let builder_name = format_ident!("__{}Builder", component_name);
    let child_field_ident = child_contract.as_ref().map(|_| format_ident!("children"));

    let builder_tokens = if props.is_empty() {
        match child_contract.as_ref() {
            None => {
                quote! {
                    #vis struct #builder_name;

                    impl #builder_name {
                        #vis fn children(self, _children: ()) -> #component_name {
                            #component_name
                        }
                    }
                }
            }
            Some(contract) => {
                let child_arg_ty = child_builder_arg_type_tokens(contract.kind);
                let child_value =
                    wrap_special_value_tokens(contract.kind, &format_ident!("children"));
                let child_field_ident = child_field_ident
                    .as_ref()
                    .expect("child field ident should exist");
                quote! {
                    #vis struct #builder_name;

                    impl #builder_name {
                        #vis fn children(self, children: #child_arg_ty) -> #component_name {
                            #component_name {
                                #child_field_ident: #child_value,
                            }
                        }
                    }
                }
            }
        }
    } else {
        let missing_names = props
            .iter()
            .map(|prop| format_ident!("__{}__{}_Missing", component_name, prop.ident))
            .collect::<Vec<_>>();
        let present_names = props
            .iter()
            .map(|prop| format_ident!("__{}__{}_Present", component_name, prop.ident))
            .collect::<Vec<_>>();
        let state_idents = (0..props.len())
            .map(|index| format_ident!("P{index}"))
            .collect::<Vec<_>>();
        let field_idents = props.iter().map(|prop| &prop.ident).collect::<Vec<_>>();
        let marker_structs = props
            .iter()
            .zip(missing_names.iter())
            .zip(present_names.iter())
            .map(|((prop, missing), present)| {
                let ty = &prop.ty;
                quote! {
                    #[allow(non_camel_case_types)]
                    #vis struct #missing;
                    #[allow(non_camel_case_types)]
                    #vis struct #present(#ty);
                }
            });

        let builder_methods = props.iter().enumerate().map(|(index, prop)| {
            let value_ident = &prop.ident;
            let missing_at_index = &missing_names[index];
            let present_at_index = &present_names[index];
            let setter_arg_ty = prop_builder_arg_type_tokens(prop);
            let stored_value = wrap_special_value_tokens(prop.kind, value_ident);

            let impl_generics = state_idents
                .iter()
                .enumerate()
                .filter(|(current, _)| *current != index)
                .map(|(_, ident)| ident);
            let impl_generics = impl_generics.collect::<Vec<_>>();

            let self_builder_args = state_idents
                .iter()
                .enumerate()
                .map(|(current, ident)| {
                    if current == index {
                        quote! { #missing_at_index }
                    } else {
                        quote! { #ident }
                    }
                })
                .collect::<Vec<_>>();

            let next_builder_args = state_idents
                .iter()
                .enumerate()
                .map(|(current, ident)| {
                    if current == index {
                        quote! { #present_at_index }
                    } else {
                        quote! { #ident }
                    }
                })
                .collect::<Vec<_>>();

            let field_initializers = field_idents
                .iter()
                .enumerate()
                .map(|(current, field_ident)| {
                    if current == index {
                        quote! { #field_ident: #present_at_index(#stored_value) }
                    } else {
                        quote! { #field_ident: self.#field_ident }
                    }
                })
                .collect::<Vec<_>>();

            if impl_generics.is_empty() {
                quote! {
                    impl #builder_name<#(#self_builder_args),*> {
                        #vis fn #value_ident(self, #value_ident: #setter_arg_ty) -> #builder_name<#(#next_builder_args),*> {
                            #builder_name {
                                #(#field_initializers),*
                            }
                        }
                    }
                }
            } else {
                quote! {
                    impl<#(#impl_generics),*> #builder_name<#(#self_builder_args),*> {
                        #vis fn #value_ident(self, #value_ident: #setter_arg_ty) -> #builder_name<#(#next_builder_args),*> {
                            #builder_name {
                                #(#field_initializers),*
                            }
                        }
                    }
                }
            }
        });

        let ready_builder_args = present_names
            .iter()
            .map(|present| quote! { #present })
            .collect::<Vec<_>>();
        let missing_builder_args = missing_names
            .iter()
            .map(|missing| quote! { #missing })
            .collect::<Vec<_>>();
        let field_extractors = field_idents
            .iter()
            .map(|field_ident| quote! { #field_ident: builder.#field_ident.0 });
        let component_render_args = props
            .iter()
            .map(|prop| {
                let ident = &prop.ident;
                quote! { ::core::clone::Clone::clone(&self.#ident) }
            })
            .chain(child_field_ident.iter().map(|ident| {
                quote! { ::core::clone::Clone::clone(&self.#ident) }
            }))
            .collect::<Vec<_>>();
        let children_method = match child_contract.as_ref() {
            None => quote! {
                #vis fn children(self, _children: ()) -> #component_name {
                    #component_name::from_builder(self)
                }
            },
            Some(contract) => {
                let child_arg_ty = child_builder_arg_type_tokens(contract.kind);
                let child_value =
                    wrap_special_value_tokens(contract.kind, &format_ident!("children"));
                quote! {
                    #vis fn children(self, children: #child_arg_ty) -> #component_name {
                        #component_name::from_builder(self, #child_value)
                    }
                }
            }
        };
        let from_builder = match child_contract.as_ref() {
            None => quote! {
                fn from_builder(builder: #builder_name<#(#ready_builder_args),*>) -> Self {
                    Self {
                        #(#field_extractors),*
                    }
                }
            },
            Some(contract) => {
                let child_ty = &contract.ty;
                let child_field_ident = child_field_ident
                    .as_ref()
                    .expect("child field ident should exist");
                quote! {
                    fn from_builder(
                        builder: #builder_name<#(#ready_builder_args),*>,
                        #child_field_ident: #child_ty,
                    ) -> Self {
                        Self {
                            #(#field_extractors),*,
                            #child_field_ident,
                        }
                    }
                }
            }
        };

        quote! {
            #(#marker_structs)*

            #vis struct #builder_name<#(#state_idents),*> {
                #(#field_idents: #state_idents),*
            }

            #(#builder_methods)*

            impl #builder_name<#(#ready_builder_args),*> {
                #children_method
            }

            impl #component_name {
                #from_builder
            }

            impl ::ruin_app::Component for #component_name {
                type Builder = #builder_name<#(#missing_builder_args),*>;

                fn builder() -> Self::Builder {
                    #builder_name {
                        #(#field_idents: #missing_names),*
                    }
                }

                fn render(&self) -> ::ruin_app::View {
                    ::ruin_app::IntoView::into_view(#render_name(
                        #(#component_render_args),*
                    ))
                }
            }
        }
    };

    let render_args = props
        .iter()
        .map(|prop| {
            let ident = &prop.ident;
            quote! { ::core::clone::Clone::clone(&self.#ident) }
        })
        .chain(child_field_ident.iter().map(|ident| {
            quote! { ::core::clone::Clone::clone(&self.#ident) }
        }))
        .collect::<Vec<_>>();
    let render_call = quote! { #render_name(#(#render_args),*) };

    let component_tokens = match (props.is_empty(), child_contract.as_ref()) {
        (true, None) => {
            quote! {
                #vis struct #component_name;

                #builder_tokens

                impl ::ruin_app::Component for #component_name {
                    type Builder = #builder_name;

                    fn builder() -> Self::Builder {
                        #builder_name
                    }

                    fn render(&self) -> ::ruin_app::View {
                        ::ruin_app::IntoView::into_view(#render_call)
                    }
                }
            }
        }
        _ => {
            let field_idents = props
                .iter()
                .map(|prop| prop.ident.clone())
                .chain(child_field_ident.iter().cloned())
                .collect::<Vec<_>>();
            let field_types = props
                .iter()
                .map(|prop| prop.ty.clone())
                .chain(child_contract.iter().map(|contract| contract.ty.clone()))
                .collect::<Vec<_>>();
            let component_impl = if props.is_empty() {
                quote! {
                    impl ::ruin_app::Component for #component_name {
                        type Builder = #builder_name;

                        fn builder() -> Self::Builder {
                            #builder_name
                        }

                        fn render(&self) -> ::ruin_app::View {
                            ::ruin_app::IntoView::into_view(#render_call)
                        }
                    }
                }
            } else {
                quote! {}
            };
            quote! {
                #vis struct #component_name {
                    #( #field_idents: #field_types ),*
                }

                #builder_tokens

                #component_impl
            }
        }
    };

    Ok(quote! {
        #function

        #[allow(non_snake_case)]
        #component_tokens
    })
}

fn expand_context_provider(
    item_struct: ItemStruct,
    value_ty: Type,
) -> Result<proc_macro2::TokenStream> {
    if !item_struct.generics.params.is_empty() || item_struct.generics.where_clause.is_some() {
        return Err(Error::new_spanned(
            &item_struct.generics,
            "context providers cannot be generic",
        ));
    }
    if !matches!(item_struct.fields, syn::Fields::Unit) {
        return Err(Error::new_spanned(
            &item_struct.fields,
            "context providers must be unit structs",
        ));
    }

    let ident = &item_struct.ident;

    Ok(quote! {
        #item_struct

        impl ::ruin_app::ContextKey for #ident {
            type Value = #value_ty;
        }
    })
}

fn validate_component_function(function: &ItemFn) -> Result<()> {
    let signature = &function.sig;

    if signature.asyncness.is_some() {
        return Err(Error::new_spanned(
            signature.asyncness,
            "components must be synchronous functions",
        ));
    }
    if !signature.generics.params.is_empty() || signature.generics.where_clause.is_some() {
        return Err(Error::new_spanned(
            &signature.generics,
            "generic components are not supported yet",
        ));
    }
    if signature.constness.is_some() {
        return Err(Error::new_spanned(
            signature.fn_token,
            "components cannot be const",
        ));
    }
    if signature.unsafety.is_some() {
        return Err(Error::new_spanned(
            signature.fn_token,
            "components cannot be unsafe",
        ));
    }
    if signature.abi.is_some() {
        return Err(Error::new_spanned(
            &signature.abi,
            "components cannot declare an ABI",
        ));
    }
    if signature.variadic.is_some() {
        return Err(Error::new_spanned(
            &signature.variadic,
            "components cannot be variadic",
        ));
    }
    let input_count = signature.inputs.len();
    for (index, input) in signature.inputs.iter().enumerate() {
        let FnArg::Typed(typed) = input else {
            return Err(Error::new_spanned(
                input,
                "methods with `self` are not supported by #[component]",
            ));
        };
        let Pat::Ident(PatIdent { ident, .. }) = typed.pat.as_ref() else {
            return Err(Error::new_spanned(
                &typed.pat,
                "component props must be simple identifier bindings",
            ));
        };
        if ident == "children" {
            if index + 1 != input_count {
                return Err(Error::new_spanned(
                    ident,
                    "component `children` contracts must be the final parameter",
                ));
            }
            parse_children_contract_kind(typed.ty.as_ref())?;
        }
    }

    match &signature.output {
        ReturnType::Default => Err(Error::new_spanned(
            &signature.ident,
            "components must return a view-like value",
        )),
        ReturnType::Type(_, _) => Ok(()),
    }
}

struct Prop {
    ident: Ident,
    ty: Type,
    kind: SpecialValueKind,
}

fn parse_prop(input: &FnArg) -> Result<Prop> {
    let FnArg::Typed(typed) = input else {
        return Err(Error::new_spanned(
            input,
            "methods with `self` are not supported by #[component]",
        ));
    };
    let Pat::Ident(PatIdent { ident, .. }) = typed.pat.as_ref() else {
        return Err(Error::new_spanned(
            &typed.pat,
            "component props must be simple identifier bindings",
        ));
    };

    Ok(Prop {
        ident: ident.clone(),
        ty: (*typed.ty).clone(),
        kind: parse_special_value_kind(typed.ty.as_ref()),
    })
}

#[derive(Clone)]
struct ChildContract {
    ty: Type,
    kind: SpecialValueKind,
}

#[derive(Clone, Copy, Eq, PartialEq)]
enum SpecialValueKind {
    Plain,
    ChildViews,
    TextValue,
}

fn parse_special_value_kind(ty: &Type) -> SpecialValueKind {
    match terminal_type_ident(ty).as_deref() {
        Some("ChildViews") => SpecialValueKind::ChildViews,
        Some("TextValue") => SpecialValueKind::TextValue,
        _ => SpecialValueKind::Plain,
    }
}

fn parse_children_contract_kind(ty: &Type) -> Result<SpecialValueKind> {
    match parse_special_value_kind(ty) {
        SpecialValueKind::ChildViews => Ok(SpecialValueKind::ChildViews),
        SpecialValueKind::TextValue => Ok(SpecialValueKind::TextValue),
        SpecialValueKind::Plain => Err(Error::new_spanned(
            ty,
            "component `children` contracts must use `ruin_app::ChildViews` or `ruin_app::TextValue`",
        )),
    }
}

fn terminal_type_ident(ty: &Type) -> Option<String> {
    let Type::Path(type_path) = ty else {
        return None;
    };
    type_path
        .path
        .segments
        .last()
        .map(|segment| segment.ident.to_string())
}

fn prop_builder_arg_type_tokens(prop: &Prop) -> proc_macro2::TokenStream {
    match prop.kind {
        SpecialValueKind::Plain => {
            let ty = &prop.ty;
            quote! { #ty }
        }
        SpecialValueKind::ChildViews => quote! { impl ::ruin_app::Children },
        SpecialValueKind::TextValue => quote! { impl ::ruin_app::TextChildren },
    }
}

fn child_builder_arg_type_tokens(kind: SpecialValueKind) -> proc_macro2::TokenStream {
    match kind {
        SpecialValueKind::Plain => quote! { () },
        SpecialValueKind::ChildViews => quote! { impl ::ruin_app::Children },
        SpecialValueKind::TextValue => quote! { impl ::ruin_app::TextChildren },
    }
}

fn wrap_special_value_tokens(kind: SpecialValueKind, ident: &Ident) -> proc_macro2::TokenStream {
    match kind {
        SpecialValueKind::Plain => quote! { #ident },
        SpecialValueKind::ChildViews => quote! { ::ruin_app::ChildViews::from_children(#ident) },
        SpecialValueKind::TextValue => quote! { ::ruin_app::TextValue::from_text(#ident) },
    }
}

struct ViewRoot {
    root: Node,
}

impl Parse for ViewRoot {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let root = input.parse::<Node>()?;
        if !input.is_empty() {
            return Err(input.error("view! expects a single root node"));
        }
        Ok(Self { root })
    }
}

struct Node {
    path: Path,
    props: Vec<Property>,
    children: Vec<Child>,
}

struct Property {
    name: Ident,
    value: Expr,
}

enum Child {
    Node(Node),
    Expr(Expr),
}

impl Parse for Node {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let path = input.parse::<Path>()?;

        let props_content;
        parenthesized!(props_content in input);
        let props = Punctuated::<Property, Token![,]>::parse_terminated(&props_content)?
            .into_iter()
            .collect::<Vec<_>>();

        let children_content;
        braced!(children_content in input);
        let mut children = Vec::new();
        while !children_content.is_empty() {
            if looks_like_node(&children_content)? {
                children.push(Child::Node(children_content.parse()?));
            } else {
                let expr = children_content.parse::<Expr>()?;
                if children_content.peek(Token![;]) {
                    let _ = children_content.parse::<Token![;]>()?;
                }
                children.push(Child::Expr(expr));
            }
        }

        Ok(Self {
            path,
            props,
            children,
        })
    }
}

impl Parse for Property {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        Ok(Self {
            name: input.parse()?,
            value: {
                let _ = input.parse::<Token![=]>()?;
                input.parse()?
            },
        })
    }
}

fn looks_like_node(input: ParseStream<'_>) -> Result<bool> {
    let fork = input.fork();
    if fork.parse::<Path>().is_err() {
        return Ok(false);
    }
    if !fork.peek(syn::token::Paren) {
        return Ok(false);
    }
    let props_content;
    parenthesized!(props_content in fork);
    if Punctuated::<Property, Token![,]>::parse_terminated(&props_content).is_err() {
        return Ok(false);
    }
    Ok(fork.peek(syn::token::Brace))
}

fn expand_node(node: &Node) -> proc_macro2::TokenStream {
    let path = &node.path;
    if is_provider_path(path) {
        return expand_provider_node(node);
    }
    let prop_calls = node.props.iter().map(|property| {
        let name = &property.name;
        let value = &property.value;
        quote! { .#name(#value) }
    });

    if is_component_path(path) {
        // Component nodes: eager children so #[component] builder impls keep working.
        let children = expand_children(&node.children);
        quote! {
            #path::builder()
                #(#prop_calls)*
                .children(#children)
        }
    } else if is_text_path(path) {
        // text(): children are TextChildren (string/value), not Views — keep eager.
        let children = expand_children(&node.children);
        quote! {
            ::ruin_app::#path()
                #(#prop_calls)*
                .children(#children)
        }
    } else {
        // Primitive container nodes (column, row, block, button, scroll_box, …):
        // Wrap children in a LazyChildren closure so that PartialTextStyle context is
        // pushed BEFORE children run, enabling correct text style inheritance.
        //
        // Each child is spread via Children::into_views(), which handles:
        //   - Child::Node results (View) via impl<T: IntoView> Children for T
        //   - &str literals via Children for &str
        //   - ChildViews (slot values) via Children for ChildViews (flattens)
        let items = node.children.iter().map(expand_child);
        quote! {
            ::ruin_app::#path()
                #(#prop_calls)*
                .children(::ruin_app::LazyChildren(|| {
                    let mut __v: ::std::vec::Vec<::ruin_app::View> = ::std::vec::Vec::new();
                    #(__v.extend(::ruin_app::Children::into_views(#items));)*
                    __v
                }))
        }
    }
}

fn is_text_path(path: &Path) -> bool {
    path.segments.len() == 1 && path.segments[0].ident == "text"
}

fn expand_provider_node(node: &Node) -> proc_macro2::TokenStream {
    let path = &node.path;
    let value = match node.props.as_slice() {
        [Property { name, value }] if name == "value" => value,
        _ => {
            return Error::new_spanned(
                path,
                "provide::<Context>(...) expects exactly one `value = ...` property",
            )
            .to_compile_error();
        }
    };
    let child = match node.children.as_slice() {
        [child] => expand_child(child),
        _ => {
            return Error::new_spanned(path, "provide::<Context>(...) requires exactly one child")
                .to_compile_error();
        }
    };

    quote! {
        ::ruin_app::#path(
            #value,
            || ::ruin_app::IntoView::into_view(#child),
        )
    }
}

fn expand_children(children: &[Child]) -> proc_macro2::TokenStream {
    match children {
        [] => quote! { () },
        [child] => expand_child(child),
        many => {
            let items = many.iter().map(expand_child);
            quote! { ( #(#items),* ) }
        }
    }
}

fn expand_child(child: &Child) -> proc_macro2::TokenStream {
    match child {
        Child::Node(node) => expand_node(node),
        Child::Expr(expr) => quote! { #expr },
    }
}

fn is_component_path(path: &Path) -> bool {
    path.segments
        .last()
        .map(|segment| segment.ident.to_string())
        .and_then(|name| name.chars().next())
        .map(|ch| ch.is_ascii_uppercase())
        .unwrap_or(false)
}

fn is_provider_path(path: &Path) -> bool {
    !is_component_path(path)
        && path
            .segments
            .last()
            .map(|segment| segment.ident == "provide")
            .unwrap_or(false)
}
