use quote::quote;
use syn::spanned::Spanned;

struct FromMapper {
    struct_name: syn::Ident,
    generics: syn::Generics,
    index_fields: Vec<MapField>,
    index: syn::Type,
    core_path: Option<syn::Path>,
}

#[derive(Clone)]
struct MapField {
    field_name: Option<syn::Ident>,
    field_type: syn::Type,
}

#[allow(unused)]
struct IndexParser {
    from_map_index_token: syn::Ident,
    index: syn::Type,
}

impl syn::parse::Parse for IndexParser {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let content;
        let from_map_index_token = input.parse()?;
        syn::parenthesized!(content in input);
        Ok(Self {
            from_map_index_token,
            index: content.parse()?,
        })
    }
}

fn index_from_attributes(
    attrs: &Vec<syn::Attribute>,
    span: proc_macro2::Span,
) -> syn::Result<syn::Type> {
    let candidates = attrs
        .iter()
        .filter(|attr| {
            attr.meta
                .path()
                .get_ident()
                .is_some_and(|ident| ident == "FromMapIndex")
        })
        .map(|attr| {
            let s = &attr.meta;
            let stream = quote!(#s);
            let parsed: IndexParser = syn::parse2(stream)?;
            Ok(parsed)
        })
        .collect::<syn::Result<Vec<_>>>()?;
    if candidates.len() != 1 {
        Err(syn::Error::new(
            span,
            "Expected exactly one #[FromMapIndex(..)] attribute",
        ))
    } else {
        Ok(candidates[0].index.clone())
    }
}

pub struct CorePathParser {
    #[allow(unused)]
    pub from_map_core_path_token: syn::Ident,
    pub core_path: syn::Path,
}

impl syn::parse::Parse for CorePathParser {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let from_map_core_path_token = input.parse()?;
        let content;
        syn::parenthesized!(content in input);
        Ok(Self {
            from_map_core_path_token,
            core_path: content.parse()?,
        })
    }
}

fn core_path_from_attributes(
    attrs: &Vec<syn::Attribute>,
    span: proc_macro2::Span,
) -> syn::Result<Option<syn::Path>> {
    let mut candidates = attrs
        .iter()
        .filter(|attr| {
            attr.meta
                .path()
                .get_ident()
                .is_some_and(|ident| ident == "FromMapCorePath")
        })
        .map(|attr| {
            let s = &attr.meta;
            let stream = quote!(#s);
            let parsed: CorePathParser = syn::parse2(stream)?;
            Ok(parsed)
        })
        .collect::<syn::Result<Vec<_>>>()?;
    if candidates.len() > 1 {
        return Err(syn::Error::new(
            span,
            "Expected at most one #[FromMapCorePath(..)] attribute",
        ));
    }
    if candidates.len() == 1 {
        return Ok(Some(candidates.remove(0).core_path));
    }
    Ok(None)
}

impl syn::parse::Parse for FromMapper {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let struct_def: syn::ItemStruct = input.parse()?;
        let index = index_from_attributes(&struct_def.attrs, struct_def.span())?;
        let core_path = core_path_from_attributes(&struct_def.attrs, struct_def.span())?;
        Ok(Self {
            struct_name: struct_def.ident,
            generics: struct_def.generics,
            index_fields: struct_def
                .fields
                .into_iter()
                .map(|field| MapField {
                    field_name: field.ident,
                    field_type: field.ty,
                })
                .collect::<Vec<_>>(),
            index,
            core_path,
        })
    }
}

impl FromMapper {
    fn implement(&self) -> proc_macro2::TokenStream {
        let struct_name = &self.struct_name;
        let field_names = self
            .index_fields
            .clone()
            .into_iter()
            .map(|field| field.field_name);
        let field_names_maps = field_names
            .clone()
            .map(|name| syn::Ident::new(&format!("{}_map", quote!(#name)), struct_name.span()))
            .collect::<Vec<_>>();
        let field_types = self
            .index_fields
            .clone()
            .into_iter()
            .map(|field| field.field_type);
        let index = &self.index;
        let backend_path = match &self.core_path {
            Some(p) => quote!(#p ::backend::chili::),
            None => quote!(),
        };

        let (impl_generics, ty_generics, where_clause) = &self.generics.split_for_impl();

        let res = quote!(
            #[doc(hidden)]
            const _: () = {
                #[automatically_derived]
                impl #impl_generics #backend_path FromMap<#index>
                for #struct_name #ty_generics #where_clause {
                    fn from_map(
                        map: &std::collections::BTreeMap<#index, std::collections::BTreeSet<
                            #index>
                        >
                    ) -> Result<
                        std::collections::BTreeMap<#index, Self>,
                        #backend_path IndexError
                    >
                    where
                        #index: Eq + core::hash::Hash + Clone + Ord,
                    {
                        #(
                            let mut #field_names_maps = <
                                #field_types as #backend_path FromMap<#index>
                            >::from_map(map)?;
                        )*
                        map.keys().into_iter().map(|key| {
                            Ok((
                                key.clone(),
                                Self {
                                    #(
                                        #field_names : #field_names_maps.remove(&key).ok_or(
                                            #backend_path IndexError(format!("could not find index in map"))
                                        )?,
                                    )*
                                }
                            ))
                        }).collect::<Result<_, _>>()
                    }
                }
            };
        );
        res
    }
}

pub fn derive_from_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let from_mapper = syn::parse_macro_input!(input as FromMapper);
    let stream = from_mapper.implement();
    stream.into()
}
