//! Derive macros for Oxide's reflection system.
//!
//! This crate exists for exactly one job: `#[derive(Reflect)]`. It is the
//! compile-time half of the engine's **dual-editable types** principle —
//! every component's public fields should be editable from the editor
//! inspector and from scripts through *one* representation, with no
//! hand-written per-type code. The runtime half (the `Reflect` trait, the
//! `FieldInfo` descriptor, and the `TypeRegistry`) lives in
//! `oxide_engine::reflect`; this crate only generates the trait impl.
//!
//! ## What the derive generates
//!
//! For a struct with named fields, `#[derive(Reflect)]` emits an
//! `oxide_engine::reflect::Reflect` impl that exposes each **public**,
//! non-skipped field as:
//!
//! - a static [`FieldInfo`] entry (`name` + syntactic `type_name`), so a
//!   generic inspector can enumerate fields and pick a widget per type, and
//! - per-field RON get/set, so a single field can be read or written without
//!   touching the rest of the component (the unit an inspector edits).
//!
//! Only `pub` fields are reflected — this matches the Unity/Godot convention
//! that *public* fields are the editable surface. Use `#[reflect(skip)]` to
//! exclude a public field.
//!
//! ```ignore
//! use oxide_engine::reflect::Reflect;
//!
//! #[derive(Reflect, serde::Serialize, serde::Deserialize)]
//! struct Timer {
//!     pub repeating: bool,
//!     pub duration: f32,
//!     #[reflect(skip)]
//!     pub elapsed: f32, // runtime state — not an authored field
//! }
//! ```
//!
//! Every reflected field must itself be `serde`-serializable, since get/set
//! round-trip through RON.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Visibility};

/// Derives `oxide_engine::reflect::Reflect` for a struct with named fields.
///
/// See the [crate-level docs](crate) for the field-selection rules
/// (public-only, `#[reflect(skip)]`).
#[proc_macro_derive(Reflect, attributes(reflect))]
pub fn derive_reflect(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    // Named-field structs and tuple structs are both supported. Tuple-struct
    // fields are addressed by their positional index ("0", "1", …), matching
    // Rust's own `self.0` / `self.1` syntax — this lets one-field newtype
    // components like `Layers(pub LayerMask)` reflect without a wrapper.
    let raw_fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(named) => named.named.iter().collect::<Vec<_>>(),
            Fields::Unnamed(unnamed) => unnamed.unnamed.iter().collect::<Vec<_>>(),
            Fields::Unit => {
                return compile_error(name, "Reflect cannot be derived for unit structs")
            }
        },
        _ => return compile_error(name, "Reflect can only be derived for structs"),
    };

    let mut infos = Vec::new();
    let mut get_arms = Vec::new();
    let mut set_arms = Vec::new();

    for (index, field) in raw_fields.iter().enumerate() {
        // Public-only: private fields are implementation detail, not the
        // authored/editable surface.
        if !matches!(field.vis, Visibility::Public(_)) {
            continue;
        }
        let attrs = parse_field_attrs(field);
        if attrs.skip {
            continue;
        }

        // For named structs the field name + accessor is the ident; for tuple
        // structs the name is the index as a string and the accessor is the
        // syn::Index token (which renders as `0`, `1`, ...).
        let (field_name, accessor) = match &field.ident {
            Some(ident) => (ident.to_string(), quote!(#ident)),
            None => {
                let idx = syn::Index::from(index);
                (index.to_string(), quote!(#idx))
            }
        };
        let ty = &field.ty;
        // Syntactic type text, e.g. "f32", "bool", "Vec3", "Handle < Font >".
        // The inspector dispatches a widget on this; unknown types fall back to
        // a raw RON editor.
        let type_name = quote!(#ty).to_string();

        let range_tokens = match attrs.range {
            Some((min, max)) => quote! {
                ::core::option::Option::Some((#min, #max))
            },
            None => quote! { ::core::option::Option::None },
        };

        infos.push(quote! {
            ::oxide_engine::reflect::FieldInfo {
                name: #field_name,
                type_name: #type_name,
                range: #range_tokens,
            }
        });
        get_arms.push(quote! {
            #field_name => ::oxide_engine::reflect::__reflect_to_ron(&self.#accessor),
        });
        set_arms.push(quote! {
            #field_name => {
                self.#accessor = ::oxide_engine::reflect::__reflect_from_ron(#field_name, value)?;
                ::core::result::Result::Ok(())
            }
        });
    }

    let field_count = infos.len();

    quote! {
        impl #impl_generics ::oxide_engine::reflect::Reflect for #name #ty_generics #where_clause {
            fn fields(&self) -> &'static [::oxide_engine::reflect::FieldInfo] {
                static FIELDS: [::oxide_engine::reflect::FieldInfo; #field_count] = [
                    #(#infos),*
                ];
                &FIELDS
            }

            fn get_field(&self, name: &str) -> ::core::option::Option<::std::string::String> {
                match name {
                    #(#get_arms)*
                    _ => ::core::option::Option::None,
                }
            }

            fn set_field(
                &mut self,
                name: &str,
                value: &str,
            ) -> ::core::result::Result<(), ::oxide_engine::reflect::ReflectError> {
                match name {
                    #(#set_arms)*
                    _ => ::core::result::Result::Err(
                        ::oxide_engine::reflect::ReflectError::UnknownField(
                            ::std::string::ToString::to_string(name),
                        ),
                    ),
                }
            }
        }
    }
    .into()
}

/// Parsed `#[reflect(...)]` attributes on a single field.
#[derive(Default)]
struct FieldAttrs {
    /// `#[reflect(skip)]` — exclude this public field from reflection.
    skip: bool,
    /// `#[reflect(min = X, max = Y)]` — numeric bounds passed to inspector
    /// widgets so a normalized `f32` field becomes a slider instead of a drag.
    /// Both must be present for a range to be recorded.
    range: Option<(f32, f32)>,
}

fn parse_field_attrs(field: &syn::Field) -> FieldAttrs {
    let mut out = FieldAttrs::default();
    let mut min: Option<f32> = None;
    let mut max: Option<f32> = None;
    for attr in &field.attrs {
        if !attr.path().is_ident("reflect") {
            continue;
        }
        let _ = attr.parse_nested_meta(|meta| {
            if meta.path.is_ident("skip") {
                out.skip = true;
            } else if meta.path.is_ident("min") {
                let lit: syn::LitFloat = meta.value()?.parse()?;
                min = Some(lit.base10_parse::<f32>()?);
            } else if meta.path.is_ident("max") {
                let lit: syn::LitFloat = meta.value()?.parse()?;
                max = Some(lit.base10_parse::<f32>()?);
            }
            Ok(())
        });
    }
    if let (Some(mn), Some(mx)) = (min, max) {
        out.range = Some((mn, mx));
    }
    out
}

/// Derives `oxide_engine::reflect::ReflectEnum` for a fieldless (C-like) enum,
/// exposing its variant names so a generic inspector can render a dropdown for
/// fields of that enum type.
///
/// Only **unit** variants are supported — a variant carrying data has no single
/// "pick from a list" representation. Variant names round-trip as RON (a unit
/// variant `Foo::Bar` serializes as `Bar`), which is exactly what `set_field`
/// consumes.
///
/// ```ignore
/// use oxide_engine::reflect::ReflectEnum;
///
/// #[derive(ReflectEnum, serde::Serialize, serde::Deserialize)]
/// enum Facing { North, East, South, West }
/// assert_eq!(Facing::variants(), &["North", "East", "South", "West"]);
/// ```
#[proc_macro_derive(ReflectEnum)]
pub fn derive_reflect_enum(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    let data = match &input.data {
        Data::Enum(data) => data,
        _ => return compile_error(name, "ReflectEnum can only be derived for enums"),
    };

    let mut variant_names = Vec::new();
    for variant in &data.variants {
        if !matches!(variant.fields, Fields::Unit) {
            return compile_error(
                &variant.ident,
                "ReflectEnum requires unit (fieldless) variants",
            );
        }
        variant_names.push(variant.ident.to_string());
    }
    let count = variant_names.len();

    quote! {
        impl #impl_generics ::oxide_engine::reflect::ReflectEnum for #name #ty_generics #where_clause {
            fn variants() -> &'static [&'static str] {
                static VARIANTS: [&str; #count] = [ #(#variant_names),* ];
                &VARIANTS
            }
        }
    }
    .into()
}

/// Emit a `compile_error!` at the derived type so the message is attributed
/// to the user's struct, not somewhere inside the generated impl.
fn compile_error(name: &syn::Ident, message: &str) -> TokenStream {
    syn::Error::new(name.span(), message)
        .to_compile_error()
        .into()
}
