#![deny(clippy::dbg_macro)]
#![deny(unused_crate_dependencies)]

use proc_macro2::{Span, TokenStream};
use syn::spanned::Spanned;

mod types;

pub(crate) use self::types::{DeriveInput, fatality, split};

fn fatality_inner(
    input: proc_macro2::TokenStream,
) -> syn::Result<proc_macro2::TokenStream> {
    let input_span = input.span();
    let DeriveInput {
        attrs,
        vis,
        ident,
        generics,
        data,
    } = syn::parse2(input)?;
    let bail_if_has_generics =
        |generics: &syn::Generics, span: Span| -> syn::Result<()> {
            if !generics.params.is_empty() {
                return Err(syn::Error::new(
                    span,
                    "Generics  `enum`-types are currently supported",
                ));
            }
            Ok(())
        };
    match data {
        syn::Data::Enum(data_enum) => {
            bail_if_has_generics(&generics, generics.span())?;
            fatality::enum_gen(DeriveInput {
                attrs,
                vis,
                ident,
                generics,
                data: data_enum,
            })
        }
        syn::Data::Struct(data_struct) => {
            bail_if_has_generics(&generics, generics.span())?;
            fatality::struct_gen(DeriveInput {
                attrs,
                vis,
                ident,
                generics,
                data: data_struct,
            })
        }
        syn::Data::Union(_) => Err(syn::Error::new(
            input_span,
            "Only `enum` and `struct` types are supported",
        )),
    }
}

fn derive_fatality2(
    input: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
    let ts =
        fatality_inner(input).unwrap_or_else(|e| -> proc_macro2::TokenStream {
            let mut ts = proc_macro2::TokenStream::new();
            ts.extend(e.to_compile_error());
            ts
        });

    expander::Expander::new("fatality")
        .add_comment(
            "Generated by `#[derive(::fatality::Fatality)]`".to_owned(),
        )
        // .fmt(expander::Edition::_2021)
        .dry(!cfg!(feature = "expand"))
        .write_to_out_dir(ts)
        .unwrap()
}

#[proc_macro_derive(Fatality, attributes(fatal))]
pub fn derive_fatality(
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let input = TokenStream::from(input);
    let output: TokenStream = derive_fatality2(input);
    proc_macro::TokenStream::from(output)
}

fn split_inner(
    input: proc_macro2::TokenStream,
) -> syn::Result<proc_macro2::TokenStream> {
    let input_span = input.span();
    let DeriveInput {
        attrs,
        vis,
        ident,
        generics,
        data,
    } = syn::parse2(input)?;
    let bail_if_has_generics =
        |generics: &syn::Generics, span: Span| -> syn::Result<()> {
            if !generics.params.is_empty() {
                return Err(syn::Error::new(
                    span,
                    "Generics  `enum`-types are currently supported",
                ));
            }
            Ok(())
        };
    match data {
        syn::Data::Enum(data_enum) => {
            bail_if_has_generics(&generics, generics.span())?;
            split::enum_gen(DeriveInput {
                attrs,
                vis,
                ident,
                generics,
                data: data_enum,
            })
        }
        syn::Data::Struct(data_struct) => {
            bail_if_has_generics(&generics, generics.span())?;
            split::struct_gen(
                input_span,
                DeriveInput {
                    attrs,
                    vis,
                    ident,
                    generics,
                    data: data_struct,
                },
            )
        }
        syn::Data::Union(_) => Err(syn::Error::new(
            input_span,
            "Only `enum` and `struct` types are supported",
        )),
    }
}

fn derive_split2(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
    let ts = split_inner(input.clone()).unwrap_or_else(
        |e| -> proc_macro2::TokenStream {
            let mut ts = proc_macro2::TokenStream::new();
            ts.extend(e.to_compile_error());
            ts
        },
    );

    expander::Expander::new("split")
        .add_comment("Generated by `#[derive(::fatality::Split)]`".to_owned())
        // .fmt(expander::Edition::_2021)
        .dry(!cfg!(feature = "expand"))
        .write_to_out_dir(ts)
        .unwrap()
}

#[proc_macro_derive(Split, attributes(split))]
pub fn derive_split(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = TokenStream::from(input);
    let output: TokenStream = derive_split2(input);
    proc_macro::TokenStream::from(output)
}

#[cfg(test)]
mod test;
