use crate::*;

use proc_macro2::TokenStream;
use quote::quote;

pub fn generate(
    payloads: &[&Payload],
    derives: Vec<TokenStream>,
    cfg: &Config,
) -> Result<TokenStream, E> {
    let contexts = payloads_context(payloads)?;
    let context_def = if contexts.is_empty() {
        quote! {
            pub type PayloadContext<'a> = ();
        }
    } else {
        quote! {
            #[allow(dead_code)]
            #[allow(non_snake_case)]
            pub enum PayloadContext<'a> {
                None,
                #(#contexts,)*
            }
        }
    };
    let payloads = payloads
        .iter()
        .copied()
        .filter(|pl| !pl.attrs.is_ctx())
        .collect::<Vec<_>>();
    let mut variants = Vec::new();
    for pl in payloads.iter() {
        let fullname = pl.fullname()?;
        let fullpath = pl.fullpath()?;
        variants.push(quote! {#fullname(#fullpath)});
    }
    let derives = [derives, cfg.get_payload_derive()?].concat();
    let derives = if derives.is_empty() {
        quote! {}
    } else {
        quote! {#[derive(#(#derives,)*)]}
    };
    let deafults = if cfg.is_no_default_payloads() {
        quote! {}
    } else {
        quote! {
            Bytes(Vec<u8>),
            String(String),
        }
    };
    Ok(quote! {
        #context_def

        #derives
        #[allow(non_snake_case)]
        pub enum Payload {
            #(#variants,)*
            #deafults
        }

        impl brec::PayloadSchema for Payload {
            type Context<'a> = PayloadContext<'a>;
        }

        impl brec::PayloadHooks for Payload {}

        impl brec::PayloadInnerDef for Payload {}

        impl brec::PayloadDef<Payload> for Payload {}

    })
}

fn payloads_context(payloads: &[&Payload]) -> Result<Vec<TokenStream>, E> {
    let mut variants = Vec::new();
    let mut has_crypt = false;
    for payload in payloads.iter() {
        if payload.attrs.is_crypt() {
            has_crypt = true;
        }
        if payload.attrs.is_ctx() {
            let fullname = payload.fullname()?;
            let fullpath = payload.fullpath()?;
            variants.push(quote! {#fullname(&'a mut #fullpath)});
        }
    }
    if has_crypt {
        variants.push(quote! {Encrypt(&'a mut brec::prelude::EncryptOptions)});
        variants.push(quote! {Decrypt(&'a mut brec::prelude::DecryptOptions)});
    }
    Ok(variants)
}
