use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{parse_quote, ItemTrait, Result, TraitItem};
#[proc_macro_attribute]
pub fn queryable(attr: TokenStream, item: TokenStream) -> TokenStream {
    impl_trait_query(attr, item)
        .unwrap_or_else(syn::Error::into_compile_error)
        .into()
}
fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result<TokenStream2> {
    syn::custom_keyword!(no_bounds);
    let no_bounds: Option<no_bounds> = syn::parse(arg).map_err(|e| {
        syn::Error::new(
            e.span(),
            "Valid forms are: `#[queryable]` and `#[queryable(no_bounds)]`",
        )
    })?;
    let mut trait_definition = syn::parse::<ItemTrait>(item)?;
    let trait_name = trait_definition.ident.clone();
    if no_bounds.is_none() {
        trait_definition.supertraits.push(parse_quote!('static));
        for param in &mut trait_definition.generics.params {
            if let syn::GenericParam::Type(param) = param {
                param.bounds.push(parse_quote!('static));
            }
        }
        for item in &mut trait_definition.items {
            if let TraitItem::Type(assoc) = item {
                assoc.bounds.push(parse_quote!('static));
            }
        }
    }
    let mut impl_generics_list = vec![];
    let mut trait_generics_list = vec![];
    let where_clause = trait_definition.generics.where_clause.clone();
    for param in &trait_definition.generics.params {
        impl_generics_list.push(param.clone());
        match param {
            syn::GenericParam::Type(param) => {
                let ident = ¶m.ident;
                trait_generics_list.push(quote! { #ident });
            }
            syn::GenericParam::Lifetime(param) => {
                let ident = ¶m.lifetime;
                trait_generics_list.push(quote! { #ident });
            }
            syn::GenericParam::Const(param) => {
                let ident = ¶m.ident;
                trait_generics_list.push(quote! { #ident });
            }
        }
    }
    for item in &trait_definition.items {
        if let TraitItem::Type(assoc) = item {
            if !assoc.generics.params.is_empty() {
                return Err(syn::Error::new(
                    assoc.ident.span(),
                    "Generic associated types are not supported in trait queries",
                ));
            }
            let ident = &assoc.ident;
            let lower_ident = format_ident!("__{ident}");
            let bound = &assoc.bounds;
            impl_generics_list.push(parse_quote! { #lower_ident: #bound });
            trait_generics_list.push(quote! { #ident = #lower_ident });
        }
    }
    let impl_generics = quote! { <#( #impl_generics_list ,)*> };
    let trait_generics = quote! { <#( #trait_generics_list ,)*> };
    let trait_object = quote! { dyn #trait_name #trait_generics };
    let my_crate = proc_macro_crate::crate_name("bevy-trait-query").unwrap();
    let my_crate = match my_crate {
        proc_macro_crate::FoundCrate::Itself => quote! { bevy_trait_query },
        proc_macro_crate::FoundCrate::Name(x) => {
            let ident = quote::format_ident!("{x}");
            quote! { #ident }
        }
    };
    let imports = quote! { #my_crate::imports };
    let trait_query = quote! { #my_crate::TraitQuery };
    let mut marker_impl_generics_list = impl_generics_list.clone();
    marker_impl_generics_list
        .push(parse_quote!(__Component: #trait_name #trait_generics + #imports::Component));
    let marker_impl_generics = quote! { <#( #marker_impl_generics_list ,)*> };
    let marker_impl_code = quote! {
        impl #impl_generics #trait_query for #trait_object #where_clause {}
        impl #marker_impl_generics #my_crate::TraitQueryMarker::<#trait_object> for (__Component,)
        #where_clause
        {
            type Covered = __Component;
            fn cast(ptr: *mut u8) -> *mut #trait_object {
                ptr as *mut __Component as *mut _
            }
        }
    };
    let mut impl_generics_with_lifetime = impl_generics_list.clone();
    impl_generics_with_lifetime.insert(0, parse_quote!('__a));
    let impl_generics_with_lifetime = quote! { <#( #impl_generics_with_lifetime ,)*> };
    let trait_object_query_code = quote! {
        unsafe impl #impl_generics #imports::QueryData for &#trait_object
        #where_clause
        {
            type ReadOnly = Self;
        }
        unsafe impl #impl_generics #imports::ReadOnlyQueryData for &#trait_object
        #where_clause
        {}
        unsafe impl #impl_generics_with_lifetime #imports::WorldQuery for &'__a #trait_object
        #where_clause
        {
            type Item<'__w> = #my_crate::ReadTraits<'__w, #trait_object>;
            type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>;
            type State = #my_crate::TraitQueryState<#trait_object>;
            #[inline]
            unsafe fn init_fetch<'w>(
                world: #imports::UnsafeWorldCell<'w>,
                state: &Self::State,
                last_run: #imports::Tick,
                this_run: #imports::Tick,
            ) -> Self::Fetch<'w> {
                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::init_fetch(
                    world,
                    state,
                    last_run,
                    this_run,
                )
            }
            #[inline]
            fn shrink<'wlong: 'wshort, 'wshort>(
                item: Self::Item<'wlong>,
            ) -> Self::Item<'wshort> {
                item
            }
            const IS_DENSE: bool = <#my_crate::All<&#trait_object> as #imports::WorldQuery>::IS_DENSE;
            #[inline]
            unsafe fn set_archetype<'w>(
                fetch: &mut Self::Fetch<'w>,
                state: &Self::State,
                archetype: &'w #imports::Archetype,
                tables: &'w #imports::Table,
            ) {
                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::set_archetype(
                    fetch, state, archetype, tables,
                );
            }
            #[inline]
            unsafe fn set_table<'w>(
                fetch: &mut Self::Fetch<'w>,
                state: &Self::State,
                table: &'w #imports::Table,
            ) {
                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::set_table(fetch, state, table);
            }
            #[inline]
            unsafe fn fetch<'w>(
                fetch: &mut Self::Fetch<'w>,
                entity: #imports::Entity,
                table_row: #imports::TableRow,
            ) -> Self::Item<'w> {
                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::fetch(
                    fetch,
                    entity,
                    table_row,
                )
            }
            #[inline]
            fn update_component_access(
                state: &Self::State,
                access: &mut #imports::FilteredAccess<#imports::ComponentId>,
            ) {
                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::update_component_access(
                    state, access,
                );
            }
            #[inline]
            fn init_state(world: &mut #imports::World) -> Self::State {
                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::init_state(world)
            }
            #[inline]
            fn get_state(_: &#imports::Components) -> Option<Self::State> {
                panic!("transmuting and any other operations concerning the state of a query are currently broken and shouldn't be used. See https://github.com/JoJoJet/bevy-trait-query/issues/59");
            }
            #[inline]
            fn matches_component_set(
                state: &Self::State,
                set_contains_id: &impl Fn(#imports::ComponentId) -> bool,
            ) -> bool {
                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::matches_component_set(state, set_contains_id)
            }
        }
        unsafe impl #impl_generics_with_lifetime #imports::QueryData for &'__a mut #trait_object
        #where_clause
        {
            type ReadOnly = &'__a #trait_object;
        }
        unsafe impl #impl_generics_with_lifetime #imports::WorldQuery for &'__a mut #trait_object
        #where_clause
        {
            type Item<'__w> = #my_crate::WriteTraits<'__w, #trait_object>;
            type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>;
            type State = #my_crate::TraitQueryState<#trait_object>;
            #[inline]
            unsafe fn init_fetch<'w>(
                world: #imports::UnsafeWorldCell<'w>,
                state: &Self::State,
                last_run: #imports::Tick,
                this_run: #imports::Tick,
            ) -> Self::Fetch<'w> {
                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::init_fetch(
                    world,
                    state,
                    last_run,
                    this_run,
                )
            }
            #[inline]
            fn shrink<'wlong: 'wshort, 'wshort>(
                item: Self::Item<'wlong>,
            ) -> Self::Item<'wshort> {
                item
            }
            const IS_DENSE: bool = <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::IS_DENSE;
            #[inline]
            unsafe fn set_archetype<'w>(
                fetch: &mut Self::Fetch<'w>,
                state: &Self::State,
                archetype: &'w #imports::Archetype,
                table: &'w #imports::Table,
            ) {
                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::set_archetype(
                    fetch, state, archetype, table,
                );
            }
            #[inline]
            unsafe fn set_table<'w>(
                fetch: &mut Self::Fetch<'w>,
                state: &Self::State,
                table: &'w #imports::Table,
            ) {
                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::set_table(fetch, state, table);
            }
            #[inline]
            unsafe fn fetch<'w>(
                fetch: &mut Self::Fetch<'w>,
                entity: #imports::Entity,
                table_row: #imports::TableRow,
            ) -> Self::Item<'w> {
                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::fetch(
                    fetch,
                    entity,
                    table_row,
                )
            }
            #[inline]
            fn update_component_access(
                state: &Self::State,
                access: &mut #imports::FilteredAccess<#imports::ComponentId>,
            ) {
                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::update_component_access(
                    state, access,
                );
            }
            #[inline]
            fn init_state(world: &mut #imports::World) -> Self::State {
                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::init_state(world)
            }
            #[inline]
            fn get_state(_: &#imports::Components) -> Option<Self::State> {
                panic!("transmuting and any other operations concerning the state of a query are currently broken and shouldn't be used. See https://github.com/JoJoJet/bevy-trait-query/issues/59");
            }
            #[inline]
            fn matches_component_set(
                state: &Self::State,
                set_contains_id: &impl Fn(#imports::ComponentId) -> bool,
            ) -> bool {
                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::matches_component_set(state, set_contains_id)
            }
        }
    };
    Ok(quote! {
        #trait_definition
        #marker_impl_code
        #trait_object_query_code
    })
}