use itertools::Itertools;
use quote::ToTokens;
use std::str::FromStr;
use syn::{parse::Parse, spanned::Spanned};

pub fn filter_config_items(
    item: proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream, proc_macro2::TokenStream> {
    let mut item: syn::ItemStruct = syn::parse2(item).map_err(|e| e.to_compile_error())?;
    item.fields = expand_groups_in_fields(&item.fields)?;

    let name = &item.ident;
    let fields = item
        .fields
        .iter()
        .map(filter_config_field)
        .collect::<crate::utils::CombinedVecResults<_>>()
        .into_result()?;
    validate_filter_config(&item, &fields)?;
    item.fields = clean_fields(&item.fields);
    let to_config_items = impl_to_config_items(&fields);
    let from_config_items = impl_from_filter_config(&fields);
    let default = impl_default(&fields);

    let expanded = quote::quote! {
        #item

        #[automatically_derived]
        impl ::aviutl2::filter::FilterConfigItems for #name {
            #to_config_items

            #from_config_items
        }

        #[automatically_derived]
        impl ::std::default::Default for #name {
            fn default() -> Self {
                #default
            }
        }
    };

    Ok(expanded)
}

fn expand_groups_in_fields(fields: &syn::Fields) -> Result<syn::Fields, proc_macro2::TokenStream> {
    let syn::Fields::Named(fields) = fields else {
        return Err(syn::Error::new(
            proc_macro2::Span::call_site(),
            "only named fields are supported",
        )
        .to_compile_error());
    };

    #[derive(Debug, PartialEq, Eq, Clone)]
    enum FieldType {
        Content,
        GroupStart,
        GroupEnd,
    }

    let new_fields = fields
        .named
        .iter()
        .cloned()
        .map(|f| {
            let Some(group_attr) = f.attrs.iter().find(|attr| {
                attr.path()
                    .get_ident()
                    .is_some_and(|ident| ident == "group")
            }) else {
                return Ok(vec![(FieldType::Content, f)]);
            };
            let syn::Meta::List(ref group_meta) = group_attr.meta else {
                return Err(syn::Error::new_spanned(
                    group_attr,
                    "expected #[group(...)]",
                ));
            };
            let group_meta = &group_meta.tokens;
            let syn::Type::Macro(group_macro) = &f.ty else {
                return Err(syn::Error::new_spanned(
                    &f.ty,
                    "expected `group! { ... }` as type",
                ));
            };
            if !group_macro.mac.path.is_ident("group") {
                return Err(syn::Error::new_spanned(
                    &f.ty,
                    "expected `group! { ... }` as type",
                ));
            }
            if !matches!(&group_macro.mac.delimiter, syn::MacroDelimiter::Brace(_)) {
                return Err(syn::Error::new_spanned(
                    &f.ty,
                    "expected `group! { ... }` as type",
                ));
            }
            let field_ident = &f.ident;
            let group_start = syn::parse2::<syn::FieldsNamed>(quote::quote!({
                #[__internal_group_start(#group_meta)]
                #field_ident: (),
            }))?
            .named
            .into_iter()
            .next()
            .unwrap();
            let group_end = syn::parse2::<syn::FieldsNamed>(quote::quote!({
                #[__internal_group_end]
                __internal_group_end: (),
            }))
            .unwrap()
            .named
            .into_iter()
            .next()
            .unwrap();

            let mut fields = vec![(FieldType::GroupStart, group_start)];
            let group_content = group_macro
                .mac
                .tokens
                .clone()
                .into_iter()
                .collect::<proc_macro2::TokenStream>();
            let inner_fields = syn::parse2::<syn::FieldsNamed>(quote::quote!({
                #group_content
            }))?;
            for inner_field in inner_fields.named {
                fields.push((FieldType::Content, inner_field));
            }
            fields.push((FieldType::GroupEnd, group_end));

            Ok(fields)
        })
        .collect::<crate::utils::CombinedVecResults<_>>()
        .into_result()?
        .into_iter()
        .flatten()
        .collect::<Vec<_>>();

    let mut optimized_fields = vec![];
    let mut index = 0;
    // GroupEnd -> GroupStartをGroupStartに、最後のGroupEndを削除する最適化
    while index < new_fields.len() {
        let (field_type, field) = &new_fields[index];
        if *field_type == FieldType::GroupEnd
            && index + 1 < new_fields.len()
            && new_fields[index + 1].0 == FieldType::GroupStart
        {
            optimized_fields.push(new_fields[index + 1].clone());
            index += 2;
        } else {
            optimized_fields.push((field_type.clone(), field.clone()));
            index += 1;
        }
    }

    // 最後のGroupEndを削除
    if matches!(optimized_fields.last(), Some((FieldType::GroupEnd, _))) {
        optimized_fields.pop();
    }

    let final_fields = optimized_fields
        .into_iter()
        .map(|(_, field)| field)
        .collect::<Vec<_>>();

    Ok(syn::Fields::Named(syn::FieldsNamed {
        brace_token: fields.brace_token,
        named: syn::punctuated::Punctuated::<syn::Field, syn::Token![,]>::from_iter(final_fields),
    }))
}

#[allow(clippy::large_enum_variant)]
enum FilterConfigField {
    Track {
        id: String,
        name: String,
        default: f64,
        min: f64,
        max: f64,
        step: f64,
        group: Option<String>,
        zero_display: Option<String>,
        slider_ratio: f64,
    },
    Check {
        id: String,
        name: String,
        default: bool,
    },
    CheckSection {
        id: String,
        name: String,
        default: bool,
        multi_section: bool,
    },
    Color {
        id: String,
        name: String,
        default: u32,
    },
    Select {
        id: String,
        name: String,
        default: either::Either<i32, syn::ExprPath>,
        items: either::Either<Vec<String>, syn::TypePath>,
    },
    File {
        id: String,
        name: String,
        filters: Vec<FileFilterEntry>,
        default: Option<syn::Expr>,
    },
    String {
        id: String,
        name: String,
        default: Option<syn::Expr>,
    },
    Text {
        id: String,
        name: String,
        default: Option<syn::Expr>,
    },
    Folder {
        id: String,
        name: String,
        default: Option<syn::Expr>,
    },
    Data {
        id: String,
        name: String,
        default: Option<syn::Expr>,
        value_type: syn::Type,
    },
    Button {
        id: String,
        name: String,
        callback: syn::ExprPath,
        error_mode: ButtonErrorMode,
        unwind: bool,
    },
    GroupStart {
        name: String,
        opened: Option<bool>,
    },
    GroupEnd,
    Separator {
        name: String,
    },
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct TrackStep {
    value: decimal_rs::Decimal,
    repr: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ButtonErrorMode {
    Log,
    LogOnly,
    Ignore,
}

impl From<TrackStep> for decimal_rs::Decimal {
    fn from(value: TrackStep) -> Self {
        value.value
    }
}

impl TrackStep {
    fn from_expr(expr: &syn::Expr) -> Result<Self, syn::Error> {
        let compact = expr.to_token_stream().to_string().replace(' ', "");
        if let Some(exponent) = compact.strip_prefix("10^-") {
            let exponent = exponent.parse::<usize>().map_err(|_| {
                syn::Error::new_spanned(expr, "step exponent must be a negative integer")
            })?;
            let repr = if exponent == 0 {
                "1.0".to_string()
            } else {
                format!("0.{}", "0".repeat(exponent - 1) + "1")
            };
            let value = decimal_rs::Decimal::from_str(&repr).unwrap();
            return Ok(Self { value, repr });
        }
        let (value, repr) = match expr {
            syn::Expr::Lit(syn::ExprLit { lit, .. }) => {
                let (value, repr) = parse_decimal_lit(lit)?;
                (value, repr)
            }
            _ => {
                return Err(syn::Error::new_spanned(
                    expr,
                    "step must be 1 or 10^-n (e.g., 0.01 or 10^-2)",
                ));
            }
        };

        validate_track_step(value, &repr, expr)?;
        Ok(Self { value, repr })
    }

    fn as_f64(&self) -> f64 {
        self.repr.parse().unwrap()
    }
}

#[derive(Debug)]
struct FileFilterEntry {
    name: String,
    exts: Vec<String>,
}

impl syn::parse::Parse for FileFilterEntry {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let name: syn::LitStr = input.parse()?;
        input.parse::<syn::Token![=>]>()?;
        let exts: syn::ExprArray = input.parse()?;
        let exts = exts
            .elems
            .iter()
            .map(|e| {
                if let syn::Expr::Lit(syn::ExprLit {
                    lit: syn::Lit::Str(s),
                    ..
                }) = e
                {
                    Ok(s.value())
                } else {
                    Err(syn::Error::new_spanned(
                        e,
                        "expected string literal for file extension",
                    ))
                }
            })
            .collect::<Result<Vec<_>, _>>()?;
        Ok(FileFilterEntry {
            name: name.value(),
            exts,
        })
    }
}

fn impl_to_config_items(fields: &[FilterConfigField]) -> proc_macro2::TokenStream {
    let mut button_callbacks = vec![];
    let mut track_groups = Vec::<(String, Vec<proc_macro2::TokenStream>)>::new();
    let to_filter_config_fields = fields
        .iter()
        .map(|f| match f {
            FilterConfigField::Track {
                id: _,
                name,
                default,
                min,
                max,
                step,
                group,
                zero_display,
                slider_ratio,
            } => {
                let track = quote_filter_config_track(
                    name,
                    *default,
                    *min,
                    *max,
                    *step,
                    zero_display.as_deref(),
                    *slider_ratio,
                );
                if let Some(group) = group {
                    if let Some((_, tracks)) =
                        track_groups.iter_mut().find(|(name, _)| name == group)
                    {
                        tracks.push(track.clone());
                    } else {
                        track_groups.push((group.clone(), vec![track.clone()]));
                    }
                }
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Track(
                        #track
                    )
                }
            }
            FilterConfigField::Check {
                id: _,
                name,
                default,
            } => {
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Checkbox(
                        ::aviutl2::filter::FilterConfigCheckbox {
                            name: #name.to_string(),
                            value: #default,
                        }
                    )
                }
            }
            FilterConfigField::CheckSection {
                id: _,
                name,
                default,
                multi_section,
            } => {
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::CheckSection(
                        ::aviutl2::filter::FilterConfigCheckSection {
                            name: #name.to_string(),
                            value: #default,
                            multi_section: #multi_section,
                        }
                    )
                }
            }
            FilterConfigField::Color {
                id: _,
                name,
                default,
            } => {
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Color(
                        ::aviutl2::filter::FilterConfigColor {
                            name: #name.to_string(),
                            value: #default.into(),
                        }
                    )
                }
            }
            FilterConfigField::Select {
                id: _,
                name,
                default,
                items,
            } => {
                let items = match items {
                    either::Either::Left(items) => {
                        let items = items.iter().enumerate().map(|(i, item)| {
                            quote::quote! {
                                ::aviutl2::filter::FilterConfigSelectItem {
                                    name: #item.to_string(),
                                    value: #i as i32,
                                }
                            }
                        });
                        quote::quote! { vec![#(#items),*] }
                    }
                    either::Either::Right(ty) => {
                        quote::quote! { <#ty as ::aviutl2::filter::FilterConfigSelectItems>::to_select_items() }
                    }
                };
                let default = match default {
                    either::Either::Left(v) => quote::quote! { #v },
                    either::Either::Right(v) => quote::quote! { ::aviutl2::filter::FilterConfigSelectItems::to_select_item_value(&#v) },
                };
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Select(
                        ::aviutl2::filter::FilterConfigSelect {
                            name: #name.to_string(),
                            value: #default,
                            items: #items,
                        }
                    )
                }
            }
            FilterConfigField::File {
                id: _,
                name,
                filters,
                default,
            } => {
                let filter_entries = filters.iter().map(|entry| {
                    let n = &entry.name;
                    let exts = &entry.exts;
                    quote::quote! {
                        ::aviutl2::common::FileFilter {
                            name: #n.to_string(),
                            extensions: vec![#(#exts.to_string()),*],
                        }
                    }
                });
                let value = if let Some(expr) = default {
                    quote::quote! { (#expr).to_string() }
                } else {
                    quote::quote! { String::new() }
                };
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::File(
                        ::aviutl2::filter::FilterConfigFile {
                            name: #name.to_string(),
                            value: #value,
                            filters: vec![#(#filter_entries),*],
                        }
                    )
                }
            }
            FilterConfigField::Data {
                id: _,
                name,
                default,
                value_type,
            } => {
                if let Some(expr) = default {
                    quote::quote! {
                        ::aviutl2::filter::FilterConfigItem::Data(
                            ::aviutl2::filter::ErasedFilterConfigData::with_default_value(
                                #name.to_string(),
                                #expr,
                            )
                        )
                    }
                } else {
                    quote::quote! {
                        ::aviutl2::filter::FilterConfigItem::Data(
                            ::aviutl2::filter::ErasedFilterConfigData::with_default_value(
                                #name.to_string(),
                                <#value_type>::__generics_default_value()
                            )
                        )
                    }
                }
            }
            FilterConfigField::String {
                id: _,
                name,
                default,
            } => {
                let value = default.as_ref().map_or_else(
                    || quote::quote! { ::std::string::String::new() },
                    |expr| quote::quote! { (#expr).to_string() },
                );
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::String(
                        ::aviutl2::filter::FilterConfigString {
                            name: #name.to_string(),
                            value: #value,
                        }
                    )
                }
            }
            FilterConfigField::Text {
                id: _,
                name,
                default,
            } => {
                let value = default.as_ref().map_or_else(
                    || quote::quote! { ::std::string::String::new() },
                    |expr| quote::quote! { (#expr).to_string() },
                );
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Text(
                        ::aviutl2::filter::FilterConfigText {
                            name: #name.to_string(),
                            value: #value,
                        }
                    )
                }
            }
            FilterConfigField::Folder {
                id: _,
                name,
                default,
            } => {
                let value = default.as_ref().map_or_else(
                    || quote::quote! { ::std::string::String::new() },
                    |expr| quote::quote! { (#expr).to_string() },
                );
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Folder(
                        ::aviutl2::filter::FilterConfigFolder {
                            name: #name.to_string(),
                            value: #value,
                        }
                    )
                }
            }
            FilterConfigField::GroupStart { name, opened} => {
                if let Some(opened) = opened {
                    quote::quote! {
                        ::aviutl2::filter::FilterConfigItem::Group(
                            ::aviutl2::filter::FilterConfigGroup::start_with_opened(
                                #name.to_string(),
                                #opened,
                            )
                        )
                    }
                } else {
                    quote::quote! {
                        ::aviutl2::filter::FilterConfigItem::Group(
                            ::aviutl2::filter::FilterConfigGroup::start(
                                #name.to_string(),
                            )
                        )
                    }
                }
            }
            FilterConfigField::GroupEnd => {
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Group(
                        ::aviutl2::filter::FilterConfigGroup::end()
                    )
                }
            }
            FilterConfigField::Separator { name } => {
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Separator(
                        ::aviutl2::filter::FilterConfigSeparator {
                            name: #name.to_string(),
                        }
                    )
                }
            }
            FilterConfigField::Button {
                id,
                name,
                callback,
                error_mode,
                unwind,
            } => {
                let callback_id = syn::Ident::new(
                    &format!("__filter_button_callback_{}", id),
                    callback.span(),
                );
                let call_on_error = match error_mode {
                    ButtonErrorMode::Ignore => {
                        quote::quote! { let _ = ret; }
                    }
                    ButtonErrorMode::LogOnly => {
                        quote::quote! { ::aviutl2::common::__output_log_if_error(ret); }
                    }
                    ButtonErrorMode::Log => {
                        quote::quote! { ::aviutl2::common::__log_and_beep_if_error(ret); }
                    }
                };
                let call_body = quote::quote! {
                    let mut edit_section = unsafe { ::aviutl2::generic::EditSection::from_raw(edit_section) };
                    let ret = #callback(&mut edit_section);
                    #call_on_error
                };
                if *unwind {
                    button_callbacks.push(quote::quote! {
                        extern "C" fn #callback_id(edit_section: *mut ::aviutl2::sys::plugin2::EDIT_SECTION) {
                            if let Err(panic_info) = ::aviutl2::__catch_unwind_with_panic_info(|| {
                                #call_body
                            }) {
                                ::aviutl2::tracing::error!(
                                    "Panic occurred during {}: {}",
                                    #name,
                                    panic_info
                                );
                                let _ = ::aviutl2::logger::write_error_log(&panic_info);
                            }
                        }
                    });
                } else {
                    button_callbacks.push(quote::quote! {
                        extern "C" fn #callback_id(edit_section: *mut ::aviutl2::sys::plugin2::EDIT_SECTION) {
                            #call_body
                        }
                    });
                }
                quote::quote! {
                    ::aviutl2::filter::FilterConfigItem::Button(
                        ::aviutl2::filter::FilterConfigButton {
                            name: #name.to_string(),
                            callback: #callback_id,
                        }
                    )
                }
            }
        })
        .collect::<Vec<_>>();
    let track_groups = track_groups
        .into_iter()
        .map(|(name, tracks)| {
            quote::quote! {
                ::aviutl2::filter::FilterConfigItem::TrackGroup(
                    ::aviutl2::filter::FilterConfigTrackGroup {
                        name: #name.to_string(),
                        tracks: vec![#(#tracks),*],
                    }
                )
            }
        })
        .collect::<Vec<_>>();

    quote::quote! {
        fn to_config_items() -> Vec<::aviutl2::filter::FilterConfigItem> {
            return vec![
                #(#to_filter_config_fields,)*
                #(#track_groups),*
            ];

            #(#button_callbacks)*
        }
    }
}

fn quote_filter_config_track(
    name: &str,
    default: f64,
    min: f64,
    max: f64,
    step: f64,
    zero_display: Option<&str>,
    slider_ratio: f64,
) -> proc_macro2::TokenStream {
    let zero_display = zero_display.map_or_else(
        || quote::quote! { ::std::option::Option::None },
        |zero_display| quote::quote! { ::std::option::Option::Some(#zero_display.to_string()) },
    );
    quote::quote! {
        ::aviutl2::filter::FilterConfigTrack {
            name: #name.to_string(),
            value: #default,
            range: #min..=#max,
            step: #step,
            zero_display: #zero_display,
            slider_ratio: #slider_ratio,
        }
    }
}

fn impl_from_filter_config(config_fields: &[FilterConfigField]) -> proc_macro2::TokenStream {
    let field_assign = config_fields
        .iter()
        .enumerate()
        .filter_map(|(i, f)| match f {
            FilterConfigField::Track { id, step, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                let to_value = if *step == 1.0 {
                    // 一回i32に変換する
                    quote::quote! {
                         (track.value as i32) as _
                    }
                } else {
                    quote::quote! {
                        track.value as _
                    }
                };
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::Track(ref track) => #to_value,
                        _ => panic!("expected Track at index {}", #i),
                    }
                })
            }
            FilterConfigField::Check { id, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::Checkbox(ref check) => check.value,
                        _ => panic!("expected Checkbox at index {}", #i),
                    }
                })
            }
            FilterConfigField::CheckSection { id, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::CheckSection(ref check_section) => check_section.value,
                        _ => panic!("expected CheckSection at index {}", #i),
                    }
                })
            }
            FilterConfigField::Color { id, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::Color(ref color) => color.value.into(),
                        _ => panic!("expected Color at index {}", #i),
                    }
                })
            }
            FilterConfigField::Select {
                id, items, default, ..
            } => {
                // defaultが：
                //   i32（Left）：インデックスで返す
                //   syn::TypePath（Right）：FilterConfigSelectItems::from_select_item_valueで変換して返す
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                let to_value = match default {
                    either::Either::Left(_) => {
                        quote::quote! {
                            (select.value as usize) as _
                        }
                    }
                    either::Either::Right(_) => match items {
                        either::Either::Left(items) => {
                            quote::quote! {
                                [#(#items),*][select.value as usize].into()
                            }
                        }
                        either::Either::Right(type_path) => {
                            let type_path = type_path.to_token_stream();
                            quote::quote! {
                                <#type_path as ::aviutl2::filter::FilterConfigSelectItems>::from_select_item_value(select.value)
                            }
                        }
                    },
                };

                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::Select(ref select) => {
                            #to_value
                        },
                        _ => panic!("expected Select at index {}", #i),
                    }
                })
            }
            FilterConfigField::File { id, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::File(ref file) =>
                            ::aviutl2::filter::__string_to_pathbuf_or_option_pathbuf(&file.value),
                        _ => panic!("expected File at index {}", #i),
                    }
                })
            }
            FilterConfigField::Data { id, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::Data(ref data) => ::aviutl2::filter::FilterConfigDataHandle::__from_erased(data),
                        _ => panic!("expected Data at index {}", #i),
                    }
                })
            }
            FilterConfigField::String { id, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::String(ref string) => string.value.clone(),
                        _ => panic!("expected String at index {}", #i),
                    }
                })
            }
            FilterConfigField::Text { id, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::Text(ref text) => text.value.clone(),
                        _ => panic!("expected Text at index {}", #i),
                    }
                })
            }
            FilterConfigField::Folder { id, .. } => {
                let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
                Some(quote::quote! {
                    #id_ident: match items[#i] {
                        ::aviutl2::filter::FilterConfigItem::Folder(ref folder) =>
                            ::aviutl2::filter::__string_to_pathbuf_or_option_pathbuf(&folder.value),
                        _ => panic!("expected Folder at index {}", #i),
                    }
                })
            }
            FilterConfigField::GroupStart { .. }
            | FilterConfigField::GroupEnd
            | FilterConfigField::Separator { .. } => {
                None
            }
            FilterConfigField::Button { .. } => {
                None
            }
        })
        .collect::<Vec<_>>();
    quote::quote! {
        fn from_config_items(items: &[::aviutl2::filter::FilterConfigItem]) -> Self {
            Self {
                #(
                    #field_assign
                ),*
            }
        }
    }
}

fn impl_default(fields: &[FilterConfigField]) -> proc_macro2::TokenStream {
    let field_inits = fields.iter().filter_map(|f| match f {
        FilterConfigField::Track { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            Some(quote::quote! {
                #id_ident: #default as _
            })
        }
        FilterConfigField::Check { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            Some(quote::quote! {
                #id_ident: #default
            })
        }
        FilterConfigField::CheckSection { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            Some(quote::quote! {
                #id_ident: #default
            })
        }
        FilterConfigField::Color { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            Some(quote::quote! {
                #id_ident: #default.into()
            })
        }
        FilterConfigField::Select { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            Some(match default {
                either::Either::Left(v) => quote::quote! {
                    #id_ident: #v as _
                },
                either::Either::Right(v) => quote::quote! {
                    #id_ident: <_ as ::std::convert::From<_>>::from(#v)
                },
            })
        }
        FilterConfigField::File { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            let default = if let Some(expr) = default {
                quote::quote! { ::aviutl2::filter::__string_to_pathbuf_or_option_pathbuf(&(#expr).to_string()) }
            } else {
                quote::quote! { ::std::default::Default::default() }
            };
            Some(quote::quote! {
                #id_ident: #default
            })
        }
        FilterConfigField::Data { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            let value = if let Some(expr) = default {
                quote::quote! { #expr }
            } else {
                quote::quote! { ::std::default::Default::default() }
            };
            Some(quote::quote! {
                #id_ident: ::aviutl2::filter::FilterConfigDataHandle::__new_owned(#value)
            })
        }
        FilterConfigField::String { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            let value = if let Some(expr) = default {
                quote::quote! { (#expr).to_string() }
            } else {
                quote::quote! { ::std::string::String::new() }
            };
            Some(quote::quote! {
                #id_ident: #value
            })
        }
        FilterConfigField::Text { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            let value = if let Some(expr) = default {
                quote::quote! { (#expr).to_string() }
            } else {
                quote::quote! { ::std::string::String::new() }
            };
            Some(quote::quote! {
                #id_ident: #value
            })
        }
        FilterConfigField::Folder { id, default, .. } => {
            let id_ident = syn::Ident::new(id, proc_macro2::Span::call_site());
            let value = if let Some(expr) = default {
                quote::quote! { (#expr).to_string() }
            } else {
                quote::quote! { ::std::string::String::new() }
            };
            Some(quote::quote! {
                #id_ident: ::aviutl2::filter::__string_to_pathbuf_or_option_pathbuf(&#value)
            })
        }
        FilterConfigField::GroupStart { .. }
        | FilterConfigField::GroupEnd
        | FilterConfigField::Separator { .. }
        | FilterConfigField::Button { .. } => None,
    });
    quote::quote! {
        Self {
            #(#field_inits),*
        }
    }
}

fn validate_filter_config(
    item: &syn::ItemStruct,
    fields: &[FilterConfigField],
) -> Result<(), proc_macro2::TokenStream> {
    assert!(item.fields.len() == fields.len());
    let field_names = fields
        .iter()
        .map(|f| match f {
            FilterConfigField::Track { name, .. } => name,
            FilterConfigField::Check { name, .. } => name,
            FilterConfigField::CheckSection { name, .. } => name,
            FilterConfigField::Color { name, .. } => name,
            FilterConfigField::Select { name, .. } => name,
            FilterConfigField::File { name, .. } => name,
            FilterConfigField::String { name, .. } => name,
            FilterConfigField::Text { name, .. } => name,
            FilterConfigField::Folder { name, .. } => name,
            FilterConfigField::Data { name, .. } => name,
            FilterConfigField::GroupStart { name, .. } => name,
            FilterConfigField::GroupEnd => "__internal_group_end",
            FilterConfigField::Separator { name, .. } => name,
            // NOTE:
            // ボタンは他のフィールドと名前が重複しても問題ないが、ボタン同士では重複してはいけない
            // 本来はformat!("button_{}", name)のようなキーで区別するべきだが、まぁ面倒なので...
            FilterConfigField::Button { name, .. } => name,
        })
        .collect::<Vec<_>>();

    let mut counts = field_names.iter().counts();
    // 終了は重複しても問題ないので除外する
    counts.remove(&"__internal_group_end");

    let errors = counts
        .into_iter()
        .zip(item.fields.iter())
        .filter(|((_, count), _)| *count > 1)
        .map(|((name, count), field)| {
            syn::Error::new_spanned(
                field,
                format!(
                    "Duplicate filter config item name `{}` found {} times",
                    name, count
                ),
            )
            .to_compile_error()
        })
        .collect::<Vec<_>>();
    if !errors.is_empty() {
        return Err(proc_macro2::TokenStream::from_iter(errors));
    }
    Ok(())
}

static RECOGNIZED_FIELDS: &[&str] = &[
    "track",
    "check",
    "checksection",
    "color",
    "select",
    "file",
    "string",
    "text",
    "folder",
    "data",
    "__internal_group_start",
    "__internal_group_end",
    "separator",
    "button",
];
fn filter_config_field(field: &syn::Field) -> Result<FilterConfigField, syn::Error> {
    let recognized_fields = field
        .attrs
        .iter()
        .filter(|attr| {
            if let Some(ident) = attr.path().get_ident() {
                RECOGNIZED_FIELDS.contains(&ident.to_string().as_str())
            } else {
                true
            }
        })
        .collect::<Vec<_>>();
    if recognized_fields.len() != 1 {
        let recognized_fields_list_for_users = RECOGNIZED_FIELDS
            .iter()
            .filter(|s| !s.starts_with("__internal_"))
            .chain(std::iter::once(&"group"))
            .map(|s| format!("`#[{s}]`"))
            .collect::<Vec<_>>()
            .join(", ");
        return Err(syn::Error::new_spanned(
            field,
            format!(
                "Exactly one of {} is required (found {})",
                recognized_fields_list_for_users,
                recognized_fields.len()
            ),
        ));
    }
    let recognized_attr = &recognized_fields[0];
    match recognized_attr
        .path()
        .get_ident()
        .unwrap()
        .to_string()
        .as_str()
    {
        "track" => filter_config_field_track(field, recognized_attr),
        "check" => filter_config_field_check(field, recognized_attr),
        "checksection" => filter_config_field_check_section(field, recognized_attr),
        "color" => filter_config_field_color(field, recognized_attr),
        "select" => filter_config_field_select(field, recognized_attr),
        "file" => filter_config_field_file(field, recognized_attr),
        "string" => filter_config_field_string(field, recognized_attr),
        "text" => filter_config_field_text(field, recognized_attr),
        "folder" => filter_config_field_folder(field, recognized_attr),
        "data" => filter_config_field_data(field, recognized_attr),
        "__internal_group_start" => filter_config_field_group_start(field, recognized_attr),
        "__internal_group_end" => filter_config_field_group_end(field, recognized_attr),
        "separator" => filter_config_field_separator(field, recognized_attr),
        "button" => filter_config_field_button(field, recognized_attr),

        _ => unreachable!(),
    }
}
fn clean_fields(fields: &syn::Fields) -> syn::Fields {
    let new_fields = fields
        .iter()
        .filter_map(|field| {
            let (recognized_attrs, new_attrs): (Vec<_>, Vec<_>) =
                field.attrs.iter().partition(|attr| {
                    if let Some(ident) = attr.path().get_ident() {
                        RECOGNIZED_FIELDS.contains(&ident.to_string().as_str())
                    } else {
                        false
                    }
                });
            let attr_type = recognized_attrs[0].path().get_ident().unwrap().to_string();
            let should_delete = attr_type.starts_with("__internal_")
                || attr_type == "separator"
                || attr_type == "button";
            if should_delete {
                None
            } else {
                let mut new_field = field.clone();
                new_field.attrs = new_attrs.into_iter().cloned().collect();
                Some(new_field)
            }
        })
        .collect::<Vec<_>>();

    match fields {
        syn::Fields::Named(original) => syn::Fields::Named(syn::FieldsNamed {
            brace_token: original.brace_token,
            named: syn::punctuated::Punctuated::<syn::Field, syn::Token![,]>::from_iter(new_fields),
        }),
        syn::Fields::Unnamed(original) => syn::Fields::Unnamed(syn::FieldsUnnamed {
            paren_token: original.paren_token,
            unnamed: syn::punctuated::Punctuated::<syn::Field, syn::Token![,]>::from_iter(
                new_fields,
            ),
        }),
        syn::Fields::Unit => syn::Fields::Unit,
    }
}

fn filter_config_field_track(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;
    let mut min = None;
    let mut max = None;
    let mut step = None;
    let mut group = None;
    let mut zero_display = None;
    let mut slider_ratio = None;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("group") {
            group = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("zero_display") {
            zero_display = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("slider_ratio") {
            let value_token = m.value()?;
            let expr = value_token.parse::<syn::Expr>()?;
            let value = parse_int_or_float(&expr)?;
            if value <= decimal_rs::Decimal::ZERO {
                return Err(syn::Error::new_spanned(
                    expr,
                    "slider_ratio must be positive",
                ));
            }
            if value > decimal_rs::Decimal::ONE {
                return Err(syn::Error::new_spanned(
                    expr,
                    "slider_ratio must be less than or equal to 1",
                ));
            }
            slider_ratio = Some(value);
        } else if m.path.is_ident("step") {
            let value = m.value()?.parse::<syn::Expr>()?;
            step = Some(TrackStep::from_expr(&value)?);
        } else if m.path.is_ident("range") {
            let value_token = m.value()?;
            let expr = value_token.parse::<syn::Expr>()?;
            if let syn::Expr::Range(expr_range) = expr {
                if !matches!(expr_range.limits, syn::RangeLimits::Closed(_)) {
                    return Err(syn::Error::new_spanned(
                        expr_range,
                        "range must be a closed range (e.g., 0.0..=1.0)",
                    ));
                }
                if let Some(ref from) = expr_range.start {
                    min = Some(parse_int_or_float(from)?);
                } else {
                    return Err(syn::Error::new_spanned(
                        expr_range,
                        "range must have a start value",
                    ));
                }
                if let Some(to) = expr_range.end {
                    max = Some(parse_int_or_float(&to)?);
                } else {
                    return Err(syn::Error::new_spanned(
                        expr_range,
                        "range must have an end value",
                    ));
                }
            } else {
                return Err(syn::Error::new_spanned(
                    expr,
                    "range must be a range expression (e.g., 0.0..=1.0)",
                ));
            }
        } else if m.path.is_ident("default") {
            let value_token = m.value()?;
            default = Some(parse_int_or_float(&value_token.parse()?)?);
        } else {
            return Err(m.error("Unknown attribute for track"));
        }
        Ok(())
    })?;

    let Some(step) = step else {
        return Err(syn::Error::new_spanned(recognized_attr, "step is required"));
    };

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    let (Some(default), Some(min), Some(max)) = (default, min, max) else {
        return Err(syn::Error::new_spanned(
            recognized_attr,
            "default, min, and max are required",
        ));
    };
    if !(min <= default && default <= max) {
        return Err(syn::Error::new_spanned(
            recognized_attr,
            "default must be between min and max",
        ));
    }
    let step_value = decimal_rs::Decimal::from(step.clone());
    match (min % step_value, max % step_value, default % step_value) {
        (d, _, _) if d != decimal_rs::Decimal::ZERO => {
            return Err(syn::Error::new_spanned(
                recognized_attr,
                "min must be a multiple of step",
            ));
        }
        (_, d, _) if d != decimal_rs::Decimal::ZERO => {
            return Err(syn::Error::new_spanned(
                recognized_attr,
                "max must be a multiple of step",
            ));
        }
        (_, _, d) if d != decimal_rs::Decimal::ZERO => {
            return Err(syn::Error::new_spanned(
                recognized_attr,
                "default must be a multiple of step",
            ));
        }
        _ => {}
    }
    Ok(FilterConfigField::Track {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default: default.into(),
        min: min.into(),
        max: max.into(),
        step: step.as_f64(),
        group,
        zero_display,
        slider_ratio: slider_ratio.unwrap_or(decimal_rs::Decimal::ONE).into(),
    })
}

fn filter_config_field_check(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("default") {
            default = Some(m.value()?.parse::<syn::LitBool>()?.value);
        } else {
            return Err(m.error("Unknown attribute for check"));
        }
        Ok(())
    })?;

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    let Some(default) = default else {
        return Err(syn::Error::new_spanned(
            recognized_attr,
            "default is required",
        ));
    };
    Ok(FilterConfigField::Check {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default,
    })
}

fn filter_config_field_check_section(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;
    let mut multi_section = true;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("default") {
            default = Some(m.value()?.parse::<syn::LitBool>()?.value);
        } else if m.path.is_ident("multi_section") {
            multi_section = m.value()?.parse::<syn::LitBool>()?.value;
        } else {
            return Err(m.error("Unknown attribute for checksection"));
        }
        Ok(())
    })?;

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    let Some(default) = default else {
        return Err(syn::Error::new_spanned(
            recognized_attr,
            "default is required",
        ));
    };
    Ok(FilterConfigField::CheckSection {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default,
        multi_section,
    })
}

fn filter_config_field_color(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("default") {
            let lit = m.value()?;
            default = Some(
                lit.parse::<syn::Lit>()
                    .and_then(|lit| parse_color_lit(&lit))
                    .or_else(|_| {
                        let expr = lit.parse::<syn::Expr>()?;
                        if let syn::Expr::Tuple(expr_tuple) = expr {
                            parse_color_tuple(&expr_tuple)
                        } else {
                            Err(syn::Error::new_spanned(
                                expr,
                                "Failed to parse color (expected integer, string literal, or tuple)",
                            ))
                        }
                    })?,
                );
        } else {
            return Err(m.error("Unknown attribute for color"));
        }

        Ok(())

    })?;

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    let Some(default) = default else {
        return Err(syn::Error::new_spanned(
            recognized_attr,
            "default is required",
        ));
    };
    return Ok(FilterConfigField::Color {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default,
    });

    fn parse_color_lit(lit: &syn::Lit) -> Result<u32, syn::Error> {
        match lit {
            syn::Lit::Int(lit_int) => {
                let value = lit_int.base10_parse::<u32>()?;
                if value > 0xFFFFFF {
                    return Err(syn::Error::new_spanned(
                        lit,
                        "Color value must be between 0x000000 and 0xFFFFFF",
                    ));
                }
                Ok(value)
            }
            syn::Lit::Str(lit_str) => {
                let s = lit_str.value();
                let s = s.trim_start_matches('#');
                if s.len() != 6 {
                    return Err(syn::Error::new_spanned(
                        lit,
                        "Color string must be in the format #RRGGBB",
                    ));
                }
                let value = u32::from_str_radix(s, 16).map_err(|_| {
                    syn::Error::new_spanned(lit, "Color string must be in the format #RRGGBB")
                })?;
                Ok(value)
            }
            _ => Err(syn::Error::new_spanned(
                lit,
                "Failed to parse color (expected integer or string literal)",
            )),
        }
    }
    fn parse_color_tuple(lit: &syn::ExprTuple) -> Result<u32, syn::Error> {
        if lit.elems.len() != 3 {
            return Err(syn::Error::new_spanned(
                lit,
                "Color tuple must have exactly 3 elements",
            ));
        }
        let mut rgb = [0u8; 3];
        for (i, expr) in lit.elems.iter().enumerate() {
            match expr {
                syn::Expr::Lit(syn::ExprLit {
                    lit: syn::Lit::Int(lit_int),
                    ..
                }) => {
                    let value = lit_int.base10_parse::<u8>()?;
                    rgb[i] = value;
                }
                _ => {
                    return Err(syn::Error::new_spanned(
                        expr,
                        "Color tuple elements must be integer literals",
                    ));
                }
            }
        }
        Ok(((rgb[0] as u32) << 16) | ((rgb[1] as u32) << 8) | (rgb[2] as u32))
    }
}

fn filter_config_field_select(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;
    let mut items = None;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("default") {
            let value = m.value()?;
            let lookahead = value.lookahead1();
            if lookahead.peek(syn::LitInt) {
                let lit = value.parse::<syn::LitInt>()?;
                let v = lit.base10_parse::<i32>()?;
                default = Some(either::Either::Left(v));
            } else if lookahead.peek(syn::Ident) || lookahead.peek(syn::Token![::]) {
                let expr = value.parse::<syn::Expr>()?;
                if let syn::Expr::Path(expr) = expr {
                    default = Some(either::Either::Right(expr.clone()));
                } else {
                    return Err(syn::Error::new_spanned(
                        expr,
                        "default must be an integer literal or a path expression",
                    ));
                }
            } else {
                return Err(lookahead.error());
            }
        } else if m.path.is_ident("items") {
            let value_token = m.value()?;
            let lookahead = value_token.lookahead1();
            if lookahead.peek(syn::token::Bracket) {
                let expr_array = value_token.parse::<syn::ExprArray>()?;
                let mut opts = Vec::new();
                for elem in expr_array.elems.iter() {
                    if let syn::Expr::Lit(syn::ExprLit {
                        lit: syn::Lit::Str(lit_str),
                        ..
                    }) = elem
                    {
                        opts.push(lit_str.value());
                    } else {
                        return Err(syn::Error::new_spanned(
                            elem,
                            "Options must be string literals",
                        ));
                    }
                }
                items = Some(either::Either::Left(opts));
            } else if lookahead.peek(syn::Ident) || lookahead.peek(syn::Token![::]) {
                items = Some(either::Either::Right(value_token.parse::<syn::TypePath>()?));
            } else {
                return Err(lookahead.error());
            }
        } else {
            return Err(m.error("Unknown attribute for select"));
        }
        Ok(())
    })?;

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    let (Some(default), Some(items)) = (default, items) else {
        return Err(syn::Error::new_spanned(
            recognized_attr,
            "default and items are required",
        ));
    };

    if let (either::Either::Left(items), either::Either::Left(&default)) =
        (items.as_ref(), default.as_ref())
        && !(0 <= default && (default as usize) < items.len())
    {
        return Err(syn::Error::new_spanned(
            recognized_attr,
            "default must be a valid index into items",
        ));
    }
    Ok(FilterConfigField::Select {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default,
        items,
    })
}

fn filter_config_field_file(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut filters = None;
    let mut default = None;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("filters") {
            let content;
            syn::braced!(content in &m.value()?);
            filters = Some(
                content
                    .parse_terminated(FileFilterEntry::parse, syn::Token![,])?
                    .into_iter()
                    .collect::<Vec<_>>(),
            );
        } else if m.path.is_ident("default") {
            default = Some(m.value()?.parse::<syn::Expr>()?);
        } else {
            return Err(m.error("Unknown attribute for file"));
        }
        Ok(())
    })?;

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    let Some(filters) = filters else {
        return Err(syn::Error::new_spanned(
            recognized_attr,
            "filters is required",
        ));
    };
    Ok(FilterConfigField::File {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        filters,
        default,
    })
}

fn filter_config_field_data(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;

    // 別になくても良いので、エラーにはしない
    let _ = recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("default") {
            default = Some(m.value()?.parse::<syn::Expr>()?);
        } else {
            return Err(m.error("Unknown attribute for data"));
        }
        Ok(())
    });

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    Ok(FilterConfigField::Data {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default,
        value_type: field.ty.clone(),
    })
}

fn filter_config_field_string(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;

    let _ = recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("default") {
            default = Some(m.value()?.parse::<syn::Expr>()?);
        } else {
            return Err(m.error("Unknown attribute for string"));
        }
        Ok(())
    });

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    Ok(FilterConfigField::String {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default,
    })
}

fn filter_config_field_text(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;

    let _ = recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("default") {
            default = Some(m.value()?.parse::<syn::Expr>()?);
        } else {
            return Err(m.error("Unknown attribute for text"));
        }
        Ok(())
    });

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    Ok(FilterConfigField::Text {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default,
    })
}

fn filter_config_field_folder(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut default = None;

    let _ = recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("default") {
            default = Some(m.value()?.parse::<syn::Expr>()?);
        } else {
            return Err(m.error("Unknown attribute for folder"));
        }
        Ok(())
    });

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    Ok(FilterConfigField::Folder {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        default,
    })
}

fn filter_config_field_group_start(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut opened = None;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("opened") {
            opened = Some(m.value()?.parse::<syn::LitBool>()?.value);
        } else {
            // NOTE: ユーザーには`#[group(...)]`として見えるので、`group`と表示する
            return Err(m.error("Unknown attribute for group"));
        }
        Ok(())
    })?;

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    Ok(FilterConfigField::GroupStart { name, opened })
}
fn filter_config_field_group_end(
    _field: &syn::Field,
    _recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    Ok(FilterConfigField::GroupEnd)
}

fn filter_config_field_separator(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else {
            return Err(m.error("Unknown attribute for separator"));
        }
        Ok(())
    })?;

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    Ok(FilterConfigField::Separator { name })
}

fn filter_config_field_button(
    field: &syn::Field,
    recognized_attr: &syn::Attribute,
) -> Result<FilterConfigField, syn::Error> {
    let mut name = None;
    let mut salt = None;
    let mut error_mode = ButtonErrorMode::Log;
    let mut unwind = true;

    recognized_attr.parse_nested_meta(|m| {
        if m.path.is_ident("name") {
            name = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("salt") {
            salt = Some(m.value()?.parse::<syn::LitStr>()?.value());
        } else if m.path.is_ident("error") {
            let value: syn::LitStr = m.value()?.parse()?;
            match value.value().as_str() {
                "log" => error_mode = ButtonErrorMode::Log,
                "log_only" => error_mode = ButtonErrorMode::LogOnly,
                "ignore" => error_mode = ButtonErrorMode::Ignore,
                _ => {
                    return Err(m.error("expected \"log\", \"log_only\", or \"ignore\""));
                }
            }
        } else if m.path.is_ident("unwind") {
            if m.input.is_empty() {
                unwind = true;
            } else {
                unwind = m.value()?.parse::<syn::LitBool>()?.value;
            }
        } else {
            return Err(m.error("Unknown attribute for button"));
        }
        Ok(())
    })?;

    let name = with_salt(name, salt, field.ident.as_ref().unwrap());
    let callback = if let syn::Type::BareFn(_) = &field.ty {
        syn::parse2(field.ident.to_token_stream())?
    } else {
        syn::parse2(field.ty.to_token_stream())?
    };
    Ok(FilterConfigField::Button {
        id: field.ident.as_ref().unwrap().to_string(),
        name,
        callback,
        error_mode,
        unwind,
    })
}

fn parse_int_or_float(expr: &syn::Expr) -> Result<decimal_rs::Decimal, syn::Error> {
    let mut current = expr;
    let mut neg_count = 0;
    // Iteratively handle nested unary negations
    loop {
        match current {
            syn::Expr::Unary(syn::ExprUnary {
                op: syn::UnOp::Neg(_),
                expr,
                ..
            }) => {
                neg_count += 1;
                current = &**expr;
            }
            syn::Expr::Paren(syn::ExprParen { expr, .. }) => {
                current = &**expr;
            }
            _ => break,
        }
    }
    match current {
        syn::Expr::Lit(syn::ExprLit { lit, .. }) => match lit {
            syn::Lit::Int(lit_int) => {
                let v = lit_int.base10_parse::<decimal_rs::Decimal>()?;
                if neg_count % 2 == 0 { Ok(v) } else { Ok(-v) }
            }
            syn::Lit::Float(lit_float) => {
                let v = lit_float.base10_parse::<decimal_rs::Decimal>()?;
                if neg_count % 2 == 0 { Ok(v) } else { Ok(-v) }
            }
            _ => Err(syn::Error::new_spanned(
                lit,
                "expected integer or float literal",
            )),
        },
        _ => Err(syn::Error::new_spanned(
            current,
            "expected integer or float literal",
        )),
    }
}

fn parse_decimal_lit(lit: &syn::Lit) -> Result<(decimal_rs::Decimal, String), syn::Error> {
    match lit {
        syn::Lit::Int(lit_int) => {
            let repr = lit_int.to_string();
            let value = lit_int.base10_parse::<decimal_rs::Decimal>()?;
            Ok((value, repr))
        }
        syn::Lit::Float(lit_float) => {
            let repr = lit_float.to_string();
            let value = lit_float.base10_parse::<decimal_rs::Decimal>()?;
            Ok((value, repr))
        }
        _ => Err(syn::Error::new_spanned(
            lit,
            "step must be 1 or 10^-n (e.g., 0.01 or 10^-2)",
        )),
    }
}

fn validate_track_step(
    value: decimal_rs::Decimal,
    _repr: &str,
    expr: &syn::Expr,
) -> Result<(), syn::Error> {
    if value <= decimal_rs::Decimal::ZERO {
        return Err(syn::Error::new_spanned(expr, "step must be positive"));
    }

    if is_negative_power_of_ten_decimal(value) {
        Ok(())
    } else {
        Err(syn::Error::new_spanned(
            expr,
            "step must be 1 or 10^-n (e.g., 0.01 or 10^-2)",
        ))
    }
}

fn is_negative_power_of_ten_decimal(value: decimal_rs::Decimal) -> bool {
    let mut current = value;
    for _ in 0..30 {
        if current == decimal_rs::Decimal::ONE {
            return true;
        }
        if current > decimal_rs::Decimal::ONE {
            return false;
        }
        current *= decimal_rs::Decimal::from(10);
    }
    false
}

fn with_salt(name: Option<String>, salt: Option<String>, field_ident: &syn::Ident) -> String {
    let name = name.unwrap_or_else(|| field_ident.to_string());
    if let Some(salt) = salt {
        format!("{salt}::{name}")
    } else {
        name
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_track() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[track(name = "Frequency", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                frequency: f64,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_check() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[check(name = "Enable", default = true)]
                enable: bool,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_checksection() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[checksection(name = "Section Enable", default = true)]
                section_enable: bool,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_color() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[color(name = "IntColor", default = 0xFF00FF)]
                int_color: u32,
                #[color(name = "StrColor", default = "#00FF00")]
                str_color: u32,
                #[color(name = "TupleColor", default = (255, 0, 0))]
                tuple_color: u32,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_select() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[select(name = "Mode", items = ["Easy", "Medium", "Hard"], default = 1)]
                mode: usize,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    #[allow(dead_code)]
    fn test_select_behavior() {
        use aviutl2::filter::FilterConfigItems;

        #[derive(Debug, PartialEq, Eq, aviutl2::filter::FilterConfigSelectItems)]
        enum Behavior {
            #[item(name = "Easy")]
            Easy,
            #[item(name = "Medium")]
            Medium,
            #[item(name = "Hard")]
            Hard,
        }

        #[aviutl2::filter::filter_config_items]
        struct Config {
            #[select(name = "Mode", items = ["Easy", "Medium", "Hard"], default = 1)]
            mode: usize,

            #[select(name = "Behavior 1", items = Behavior, default = Behavior::Medium)]
            behavior1: Behavior,

            #[select(name = "Behavior 2", items = Behavior, default = 1)]
            behavior2: usize,
        }

        let items = Config::to_config_items();
        insta::assert_debug_snapshot!(items);
    }

    #[test]
    fn test_file() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[file(name = "Input File", filters = { "Text Files" => ["*.txt"], "All Files" => ["*.*"] })]
                input_file: std::path::PathBuf,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_duplicate_field_name() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[track(name = "Frequency", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                frequency1: f64,
                #[track(name = "Frequency", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                frequency2: f64,
            }
        };
        let result = filter_config_items(input);
        assert!(result.is_err());
    }

    #[test]
    fn test_group() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[group(name = "Test", opened = true)]
                test_group: group! {
                    #[track(name = "Frequency", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                    frequency: f64,
                    #[check(name = "Enable", default = true)]
                    enable: bool,
                },
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_pub_group() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[group(name = "Test", opened = true)]
                pub test_group: group! {
                    #[track(name = "Frequency", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                    pub frequency: f64,
                    #[check(name = "Enable", default = true)]
                    pub enable: bool,
                },
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_multi_group() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[group(name = "Test 1", opened = true)]
                test_group_1: group! {
                    #[check(name = "Check 1", default = true)]
                    check_1: bool,
                },
                #[group(name = "Test 2", opened = false)]
                test_group_2: group! {
                    #[check(name = "Check 2", default = false)]
                    check_2: bool,
                },
                #[check(name = "Outside Check", default = true)]
                outside_check: bool,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_button() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[button(name = "Reset")]
                reset: fn(),
                #[button(name = "Apply")]
                apply: on_apply_clicked,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_separator() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[check(name = "Enable", default = true)]
                enable: bool,
                #[separator(name = "Advanced")]
                advanced_separator: (),
                #[track(name = "Frequency", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                frequency: f64,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_track_with_salt() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[track(name = "Frequency", salt = "Audio", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                frequency: f64,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_track_with_group() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[track(name = "Frequency", group = "Audio", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                frequency: f64,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_track_with_zero_display_and_slider_ratio() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[track(name = "Balance", range = -100.0..=100.0, step = 1.0, default = 0.0, zero_display = "Center", slider_ratio = 0.5)]
                balance: f64,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_track_with_shared_group() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[track(name = "Frequency", group = "Audio", range = 20.0..=20000.0, step = 1.0, default = 440.0)]
                frequency: f64,
                #[track(name = "Gain", group = "Audio", range = -24.0..=24.0, step = 1.0, default = 0.0)]
                gain: f64,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_track_with_exponent_step() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[track(name = "Frequency", range = 20.0..=20000.0, step = 10^-4, default = 440.0)]
                frequency: f64,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }

    #[test]
    fn test_track_with_decimal_step() {
        let input: proc_macro2::TokenStream = quote::quote! {
            struct Config {
                #[track(name = "Frequency", range = 20.0..=20000.0, step = 0.01, default = 440.0)]
                frequency: f64,
            }
        };
        let output = filter_config_items(input).unwrap();
        insta::assert_snapshot!(rustfmt_wrapper::rustfmt(output).unwrap());
    }
}
