use crate::{derive_trait, get_custom_label, has_skip_attr};
use proc_macro::TokenStream;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::{ToTokens, quote, quote_spanned};
use syn::{DataStruct, Fields, FieldsNamed, FieldsUnnamed, spanned::Spanned};

pub fn derive_struct(name: &Ident, data: &DataStruct) -> TokenStream {
    let DataStruct { fields, .. } = data;

    match fields {
        Fields::Named(named_fields) => named_field_struct_impl(name, named_fields),
        Fields::Unnamed(unnamed_fields) => tuple_struct_impl(name, unnamed_fields),
        Fields::Unit => generate_unit_struct_impl(name),
    }
}

fn generate_unit_struct_impl(name: &Ident) -> TokenStream {
    derive_trait(
        name,
        proc_macro2::TokenStream::new(),
        proc_macro2::TokenStream::new(),
    )
}

fn tuple_struct_impl(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
    let FieldsUnnamed { unnamed, .. } = fields;
    let (field_blocks, field_blocks_mut) = tuple_struct_field_blocks(unnamed);

    let gui = struct_ui(name, field_blocks);
    let gui_mut = struct_ui(name, field_blocks_mut);
    derive_trait(name, gui, gui_mut)
}

fn tuple_struct_field_blocks(
    named: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
) -> (TokenStream2, TokenStream2) {
    let mut field_blocks = TokenStream2::new();
    let mut field_blocks_mut = TokenStream2::new();

    named.into_iter().enumerate().for_each(|(index, field)| {
        field_blocks.extend(unnamed_field_block(field, index));
        field_blocks_mut.extend(unnamed_field_block_mut(field, index));
    });

    (field_blocks, field_blocks_mut)
}

fn unnamed_field_label(index: usize) -> String {
    format!("field_{}", index)
}

fn unnamed_field_block(field: &syn::Field, index: usize) -> proc_macro2::TokenStream {
    if has_skip_attr(&field.attrs) {
        return quote! {};
    }

    let field_name = unnamed_field_label(index);
    let field_type = &field.ty;
    let index = syn::Index::from(index);

    let label = get_custom_label(&field.attrs).unwrap_or(field_name);

    quote_spanned! { field.span() =>
        ui.horizontal(|ui| {
            ui.label(#label);
            <#field_type as GuiInspect>::ui(&self.#index, ui);
        });
    }
    .to_token_stream()
}

fn unnamed_field_block_mut(field: &syn::Field, index: usize) -> proc_macro2::TokenStream {
    if has_skip_attr(&field.attrs) {
        return quote! {};
    }

    let field_name = unnamed_field_label(index);
    let field_type = &field.ty;
    let index = syn::Index::from(index);

    let label = get_custom_label(&field.attrs).unwrap_or(field_name);

    quote_spanned! { field.span() =>
        ui.horizontal(|ui| {
            ui.label(#label);
            <#field_type as GuiInspect>::ui_mut(&mut self.#index, ui);
        });
    }
    .to_token_stream()
}

fn named_field_struct_impl(name: &Ident, fields: &FieldsNamed) -> TokenStream {
    let FieldsNamed { named, .. } = fields;
    let (field_blocks, field_blocks_mut) = named_struct_field_blocks(named);

    let gui = struct_ui(name, field_blocks);
    let gui_mut = struct_ui(name, field_blocks_mut);
    derive_trait(name, gui, gui_mut)
}

fn named_struct_field_blocks(
    named: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
) -> (TokenStream2, TokenStream2) {
    let mut field_blocks = TokenStream2::new();
    let mut field_blocks_mut = TokenStream2::new();

    named.into_iter().for_each(|field| {
        field_blocks.extend(named_field_block(field));
        field_blocks_mut.extend(named_field_block_mut(field));
    });

    (field_blocks, field_blocks_mut)
}

fn named_field_block(field: &syn::Field) -> proc_macro2::TokenStream {
    if has_skip_attr(&field.attrs) {
        return quote! {};
    }

    let field_name = &field.ident;
    let field_ty = &field.ty;

    let label =
        get_custom_label(&field.attrs).unwrap_or_else(|| field_name.as_ref().unwrap().to_string());

    quote_spanned! { field.span() =>
        ui.horizontal(|ui| {
            ui.label(#label);
            <#field_ty as GuiInspect>::ui(&self.#field_name, ui);
        });
    }
    .to_token_stream()
}

fn named_field_block_mut(field: &syn::Field) -> proc_macro2::TokenStream {
    if has_skip_attr(&field.attrs) {
        return quote! {};
    }

    let field_name = &field.ident;
    let field_ty = &field.ty;

    let label =
        get_custom_label(&field.attrs).unwrap_or_else(|| field_name.as_ref().unwrap().to_string());

    quote_spanned! { field.span() =>
        ui.horizontal(|ui| {
            ui.label(#label);
            <#field_ty as GuiInspect>::ui_mut(&mut self.#field_name, ui);
        });
    }
    .to_token_stream()
}

fn struct_ui(name: &Ident, fields: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
    quote! {
        ui.group(|ui| {
            ui.horizontal(|ui| {
                ui.label(stringify!(#name));
                ui.vertical(|ui| {
                    #fields
                });
            });
        });
    }
    .to_token_stream()
}
