Documentation
use std::collections::HashMap;

use procmeta::prelude::*;
use quote::{format_ident, ToTokens};
use syn::{braced, parse::Parse, DeriveInput};

use crate::structure::get_fn_name_suffix;

pub struct TargetPlugFields {
    pub items: Vec<PlugField>,
}

pub struct PlugField {
    pub ident: Ident,
    pub value: Expr,
}

impl Parse for TargetPlugFields {
    fn parse(input: ParseStream) -> Result<Self> {
        let content;
        let _ = braced!(content in input);
        let mut items = vec![];
        while !content.is_empty() {
            let ident: Ident = content.parse()?;
            let _: Token![:] = content.parse()?;
            let value: Expr = content.parse()?;
            if !content.is_empty() {
                let _: Token![,] = content.parse()?;
            }
            items.push(PlugField { ident, value })
        }
        Ok(TargetPlugFields { items })
    }
}

impl Converter<Self> for TargetPlugFields {
    fn into(self) -> Result<Self> {
        Ok(self)
    }
}

#[derive(MetaParser)]
pub enum ParsedTypeAttr {
    #[name("from")]
    From { source: Type, adaptor: Option<Type> },

    #[name("into")]
    Into {
        target: Type,

        adaptor: Option<Type>,

        #[converter(TargetPlugFields)]
        plug: Option<TargetPlugFields>,
    },
}

#[derive(MetaParser)]
pub enum ParsedFieldAttr {
    #[name("from")]
    From {
        source: Option<Type>,
        map: Option<Ident>,
        assign: Option<Expr>,
    },

    #[name("into")]
    Into {
        target: Option<Type>,
        map: Option<Ident>,
        assign: Option<Expr>,
        skip: Option<bool>,
    },
}

/// pub fn from_xxx(source: Source, adaptor: Adaptor) -> Self {
///     Self {
///         xxxx
///     }
/// }
pub struct FromItemContent {
    pub adaptor: TokenStream,

    pub source: Type,

    pub fields: TokenStream,
}

impl FromItemContent {
    pub fn new(source: Type, adaptor: Option<Type>) -> Self {
        Self {
            adaptor: adaptor.map(|t| quote!(, adaptor: #t)).unwrap_or_default(),
            source,
            fields: quote!(),
        }
    }

    pub fn add_field(&mut self, ident: &Option<Ident>, map: Option<Ident>, assign: Option<Expr>) {
        let mut value = quote!(source.#ident);
        if let Some(map) = map {
            value = quote!(source.#map);
        }
        if let Some(assign) = assign {
            value = quote!(#assign);
        }
        let last_fields_token = &self.fields;
        self.fields = quote! {
            #last_fields_token
            #ident: #value,
        }
    }
}

impl From<FromItemContent> for TokenStream {
    fn from(value: FromItemContent) -> Self {
        let source = value.source;
        let fn_name = format_ident!(
            "from{}",
            get_fn_name_suffix(source.to_token_stream().to_string())
        );
        let adaptor = value.adaptor;
        let fields = value.fields;
        quote! {
            pub fn #fn_name (source: #source #adaptor) -> Self {
                Self {
                    #fields
                }
            }
        }
    }
}

///
/// pub fn into_xxx(self, adaptor) -> T {
///     T {
///         xxxx
///     }
/// }
///
///
pub struct IntoItemContent {
    pub adaptor: TokenStream,

    pub target: Type,

    pub fields: TokenStream,
}

impl IntoItemContent {
    pub fn new(target: Type, adaptor: Option<Type>, plug: Option<TargetPlugFields>) -> Self {
        Self {
            adaptor: adaptor.map(|t| quote!(, adaptor: #t)).unwrap_or_default(),
            target,
            fields: plug
                .map(|t| {
                    let mut fields = quote!();
                    for item in t.items {
                        let ident = item.ident;
                        let value = item.value;
                        fields = quote! {
                            #fields
                            #ident: #value,
                        };
                    }
                    fields
                })
                .unwrap_or_default(),
        }
    }

    pub fn add_field(
        &mut self,
        ident: &Option<Ident>,
        map: Option<Ident>,
        assign: Option<Expr>,
        skip: Option<bool>,
    ) {
        let skip = skip.unwrap_or(false);
        if skip {
            return;
        }
        let mut ident_token = quote!(#ident);
        if let Some(map) = map {
            ident_token = quote!(#map);
        }
        let mut value = quote!(self.#ident);
        if let Some(assign) = assign {
            value = quote!(#assign);
        }
        let last_fields_token = &self.fields;
        self.fields = quote! {
            #last_fields_token
            #ident_token: #value,
        }
    }
}

impl From<IntoItemContent> for TokenStream {
    fn from(value: IntoItemContent) -> Self {
        let target = value.target;
        let fn_name = format_ident!(
            "into{}",
            get_fn_name_suffix(target.to_token_stream().to_string())
        );
        let adaptor = value.adaptor;
        let fields = value.fields;
        quote! {
            pub fn #fn_name (self #adaptor) -> #target {
                #target {
                    #fields
                }
            }
        }
    }
}

pub fn expand(input: DeriveInput) -> Result<TokenStream> {
    let ty = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    match input.data {
        syn::Data::Struct(data) => {
            let mut first_source_name = String::default();
            let mut from_items: HashMap<String, FromItemContent> = HashMap::new();

            let mut first_target_name = String::default();
            let mut into_items: HashMap<String, IntoItemContent> = HashMap::new();

            let mut result = quote!();
            for attr in input.attrs {
                let parsed_attr = <ParsedTypeAttr as MetaParser>::parse(&attr.meta)?;
                match parsed_attr {
                    ParsedTypeAttr::From { source, adaptor } => {
                        let key = source.to_token_stream().to_string();
                        if first_source_name.is_empty() {
                            first_source_name = key.clone();
                        }
                        let content = FromItemContent::new(source, adaptor);
                        from_items.insert(key, content);
                    }
                    ParsedTypeAttr::Into {
                        target,
                        adaptor,
                        plug,
                    } => {
                        let key = target.to_token_stream().to_string();
                        if first_target_name.is_empty() {
                            first_target_name = key.clone();
                        }
                        let content = IntoItemContent::new(target, adaptor, plug);
                        into_items.insert(key, content);
                    }
                }
            }
            for field in data.fields {
                if field.attrs.is_empty() {
                    for v in from_items.values_mut() {
                        v.add_field(&field.ident, None, None);
                    }
                    for v in into_items.values_mut() {
                        v.add_field(&field.ident, None, None, None);
                    }
                    continue;
                }
                for attr in &field.attrs {
                    let parsed_attr = <ParsedFieldAttr as MetaParser>::parse(&attr.meta)?;
                    match parsed_attr {
                        ParsedFieldAttr::From {
                            source,
                            map,
                            assign,
                        } => {
                            let key = source
                                .map(|s| s.to_token_stream().to_string())
                                .unwrap_or(first_source_name.clone());
                            let from_item_content = from_items.get_mut(&key);
                            let from_item_content = from_item_content.ok_or(Error::new(
                                field.span(),
                                "expected already define source type",
                            ))?;
                            from_item_content.add_field(&field.ident, map, assign);
                        }
                        ParsedFieldAttr::Into {
                            target,
                            map,
                            assign,
                            skip,
                        } => {
                            let key = target
                                .map(|s| s.to_token_stream().to_string())
                                .unwrap_or(first_target_name.clone());
                            let into_item_content = into_items.get_mut(&key);
                            let into_item_content = into_item_content.ok_or(Error::new(
                                field.span(),
                                "expected already define target type",
                            ))?;
                            into_item_content.add_field(&field.ident, map, assign, skip);
                        }
                    }
                }
            }
            for v in from_items.into_values() {
                let item_token: TokenStream = TokenStream::from(v);
                result = quote! {
                    #result
                    #item_token
                }
            }
            for v in into_items.into_values() {
                let item_token: TokenStream = TokenStream::from(v);
                result = quote! {
                    #result
                    #item_token
                }
            }
            result = quote! {
                impl #impl_generics #ty #ty_generics #where_clause  {
                    #result
                }
            };
            Ok(result)
        }
        _ => unimplemented!(),
    }
}