use std::collections::BTreeMap;

use proc_macro2::{Span, TokenStream};
use pumpkin_util::loot_table::LootNumberProviderTypes;
use quote::{ToTokens, quote};
use serde::Deserialize;
use syn::LitStr;

/// Deserialized loot table as stored in the asset files.
///
/// These are required to be defined twice because serde can't deserialize into static context for obvious reasons.
#[derive(Deserialize)]
pub struct LootTableStruct {
    /// Category of this loot table (block, entity, chest, etc.).
    r#type: LootTableTypeStruct,
    /// Namespaced random-sequence key used for deterministic rolls, if any.
    random_sequence: Option<String>,
    /// Roll pools contained in this table, if any.
    pools: Option<Vec<LootPoolStruct>>,
}

impl ToTokens for LootTableStruct {
    /// Emits a `LootTable { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let loot_table_type = self.r#type.to_token_stream();
        let random_sequence = if let Some(seq) = &self.random_sequence {
            quote! { Some(#seq) }
        } else {
            quote! { None }
        };
        let pools = if let Some(pools) = &self.pools {
            let pool_tokens: Vec<_> = pools.iter().map(ToTokens::to_token_stream).collect();
            quote! { Some(&[#(#pool_tokens),*]) }
        } else {
            quote! { None }
        };

        tokens.extend(quote! {
            LootTable {
                r#type: #loot_table_type,
                random_sequence: #random_sequence,
                pools: #pools,
            }
        });
    }
}

/// Deserialized loot pool describing a group of entries rolled together.
#[derive(Deserialize, Clone, Debug)]
pub struct LootPoolStruct {
    /// Entries that can be selected during a roll of this pool.
    entries: Vec<LootPoolEntryStruct>,
    /// Number of times the pool is rolled.
    rolls: LootNumberProviderTypes,
    /// Extra rolls granted by luck-related enchantments.
    #[serde(default = "default_bonus_rolls")]
    bonus_rolls: LootNumberProviderTypes,
    /// Conditions that must all pass for this pool to be rolled, if any.
    conditions: Option<Vec<LootConditionStruct>>,
    /// Functions applied to the selected entries, if any.
    functions: Option<Vec<LootFunctionStruct>>,
}

fn default_bonus_rolls() -> LootNumberProviderTypes {
    LootNumberProviderTypes::Constant(0.0)
}

impl ToTokens for LootPoolStruct {
    /// Emits a `LootPool { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let entries_tokens: Vec<_> = self.entries.iter().map(ToTokens::to_token_stream).collect();
        let rolls = &self.rolls;
        let bonus_rolls = &self.bonus_rolls;
        let conditions_tokens = if let Some(conds) = &self.conditions {
            let cond_tokens: Vec<_> = conds.iter().map(ToTokens::to_token_stream).collect();
            quote! { Some(&[#(#cond_tokens),*]) }
        } else {
            quote! { None }
        };
        let functions_tokens = if let Some(fns) = &self.functions {
            let cond_tokens: Vec<_> = fns.iter().map(ToTokens::to_token_stream).collect();
            quote! { Some(&[#(#cond_tokens),*]) }
        } else {
            quote! { None }
        };

        tokens.extend(quote! {
            LootPool {
                entries: &[#(#entries_tokens),*],
                rolls: #rolls,
                bonus_rolls: #bonus_rolls,
                conditions: #conditions_tokens,
                functions: #functions_tokens,
            }
        });
    }
}

/// Deserialized single-item loot entry holding the item's registry key.
#[derive(Deserialize, Clone, Debug)]
pub struct ItemEntryStruct {
    /// Namespaced item key (e.g., `"minecraft:diamond"`).
    name: String,
}

impl ToTokens for ItemEntryStruct {
    /// Emits an `ItemEntry { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = LitStr::new(&self.name, Span::call_site());

        tokens.extend(quote! {
            ItemEntry {
                name: #name,
            }
        });
    }
}

/// Deserialized loot table reference entry holding the target loot table key.
#[derive(Deserialize, Clone, Debug)]
pub struct LootTableEntryStruct {
    /// Namespaced loot table key.
    value: String,
}

impl ToTokens for LootTableEntryStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let value = LitStr::new(&self.value, Span::call_site());

        tokens.extend(quote! {
            LootTableEntry {
                value: #value,
            }
        });
    }
}

/// Deserialized alternatives loot entry that tries each child in order until one succeeds.
#[derive(Deserialize, Clone, Debug)]
pub struct AlternativeEntryStruct {
    /// Child entries evaluated sequentially until the first successful one.
    children: Vec<LootPoolEntryStruct>,
}

impl ToTokens for AlternativeEntryStruct {
    /// Emits an `AlternativeEntry { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let children = self.children.iter().map(ToTokens::to_token_stream);

        tokens.extend(quote! {
            AlternativeEntry {
                children: &[#(#children),*],
            }
        });
    }
}

#[derive(Deserialize, Clone, Debug)]
pub struct SequenceEntryStruct {
    /// Child entries evaluated in order until the first condition failure.
    children: Vec<LootPoolEntryStruct>,
}

impl ToTokens for SequenceEntryStruct {
    /// Emits a `SequenceEntry { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let children = self.children.iter().map(ToTokens::to_token_stream);

        tokens.extend(quote! {
            SequenceEntry {
                children: &[#(#children),*],
            }
        });
    }
}

/// Deserialized group loot entry that evaluates all children regardless of individual success.
#[derive(Deserialize, Clone, Debug)]
pub struct GroupEntryStruct {
    /// Child entries all evaluated unconditionally.
    children: Vec<LootPoolEntryStruct>,
}

impl ToTokens for GroupEntryStruct {
    /// Emits a `GroupEntry { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let children = self.children.iter().map(ToTokens::to_token_stream);

        tokens.extend(quote! {
            GroupEntry {
                children: &[#(#children),*],
            }
        });
    }
}

/// Deserialized dynamic loot entry holding the dynamic drop's namespaced key.
#[derive(Deserialize, Clone, Debug)]
pub struct DynamicEntryStruct {
    /// Namespaced key identifying the dynamic drop type (e.g., `"minecraft:contents"`).
    name: String,
}

impl ToTokens for DynamicEntryStruct {
    /// Emits a `DynamicEntry { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = LitStr::new(&self.name, Span::call_site());

        tokens.extend(quote! {
            DynamicEntry {
                name: #name,
            }
        });
    }
}

/// Deserialized tag loot entry holding the item tag's namespaced key.
#[derive(Deserialize, Clone, Debug)]
pub struct TagEntryStruct {
    /// Namespaced item tag key (e.g., `"minecraft:planks"`).
    name: String,
    /// If `true`, yields one random item from the tag instead of all items.
    #[serde(default)]
    expand: bool,
}

impl ToTokens for TagEntryStruct {
    /// Emits a `TagEntry { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = LitStr::new(&self.name, Span::call_site());
        let expand = self.expand;

        tokens.extend(quote! {
            TagEntry {
                name: #name,
                expand: #expand,
            }
        });
    }
}

/// Deserialized variant of a loot pool entry, tagged by the `"type"` field.
#[derive(Deserialize, Clone, Debug)]
#[serde(tag = "type")]
pub enum LootPoolEntryTypesStruct {
    /// Yields nothing.
    #[serde(rename = "minecraft:empty")]
    Empty,
    /// Yields a specific item.
    #[serde(rename = "minecraft:item")]
    Item(ItemEntryStruct),
    /// References another loot table by namespaced key.
    #[serde(rename = "minecraft:loot_table")]
    LootTable(LootTableEntryStruct),
    /// Yields dynamically determined drops (e.g., shulker box contents).
    #[serde(rename = "minecraft:dynamic")]
    Dynamic(DynamicEntryStruct),
    /// Yields all items (or one random item) in an item tag.
    #[serde(rename = "minecraft:tag")]
    Tag(TagEntryStruct),
    /// Tries each child entry until one succeeds.
    #[serde(rename = "minecraft:alternatives")]
    Alternatives(AlternativeEntryStruct),
    /// Evaluates all children in order, stopping on the first failure.
    #[serde(rename = "minecraft:sequence")]
    Sequence(SequenceEntryStruct),
    /// Evaluates all children regardless of individual success.
    #[serde(rename = "minecraft:group")]
    Group(GroupEntryStruct),
}

impl ToTokens for LootPoolEntryTypesStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::Empty => {
                tokens.extend(quote! { LootPoolEntryTypes::Empty });
            }
            Self::Item(item) => {
                tokens.extend(quote! { LootPoolEntryTypes::Item(#item) });
            }
            Self::LootTable(entry) => {
                tokens.extend(quote! { LootPoolEntryTypes::LootTable(#entry) });
            }
            Self::Dynamic(entry) => {
                tokens.extend(quote! { LootPoolEntryTypes::Dynamic(#entry) });
            }
            Self::Tag(entry) => {
                tokens.extend(quote! { LootPoolEntryTypes::Tag(#entry) });
            }
            Self::Alternatives(alt) => {
                tokens.extend(quote! { LootPoolEntryTypes::Alternatives(#alt) });
            }
            Self::Sequence(seq) => {
                tokens.extend(quote! { LootPoolEntryTypes::Sequence(#seq) });
            }
            Self::Group(grp) => {
                tokens.extend(quote! { LootPoolEntryTypes::Group(#grp) });
            }
        }
    }
}

#[derive(Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum StringOrVec {
    String(String),
    Vec(Vec<String>),
}

#[derive(Deserialize, Clone, Debug)]
pub struct ItemPredicateStruct {
    pub items: Option<StringOrVec>,
}

/// Entity flags predicate from loot table JSON.
/// Mirrors vanilla `EntityFlagsPredicate`. Only `is_on_fire` is extracted;
/// other flags (is_on_ground, is_baby, etc.) are ignored until needed.
#[derive(Deserialize, Clone, Debug)]
pub struct EntityFlagsPredicateStruct {
    /// Whether the entity must be on fire. Mirrors vanilla `isOnFire: Optional<Boolean>`.
    #[serde(default)]
    pub is_on_fire: Option<bool>,
}

/// Entity equipment predicate from loot table JSON.
/// Mirrors vanilla `EntityEquipmentPredicate`. Only mainhand enchantment tag is extracted;
/// other slots (head, chest, legs, feet, offhand) and full item predicates are ignored.
#[derive(Deserialize, Clone, Debug)]
pub struct EntityEquipmentPredicatesStruct {
    /// Mainhand slot predicate. Only enchantment tag is forwarded to runtime.
    /// Mirrors vanilla `EntityEquipmentPredicate.mainhand`.
    #[serde(default)]
    pub mainhand: Option<EntityMainhandPredicateStruct>,
}

/// Mainhand item predicate for equipment slot checks.
/// Mirrors vanilla `ItemPredicate` used inside `EntityEquipmentPredicate.mainhand`.
#[derive(Deserialize, Clone, Debug)]
pub struct EntityMainhandPredicateStruct {
    /// Component predicates for the item. Mirrors vanilla `DataComponentMatchers`.
    #[serde(default)]
    pub predicates: Option<EntityEquipmentPredicatesInner>,
}

/// Inner predicates structure for equipment slot checks.
/// Contains the actual enchantment tag to match against.
#[derive(Deserialize, Clone, Debug)]
pub struct EntityEquipmentPredicatesInner {
    /// Enchantment tag to match (e.g. `"minecraft:smelts_loot"` → Fire Aspect).
    /// Mirrors vanilla `EnchantmentPredicate.enchantments` as a `HolderSet` reference.
    #[serde(default, rename = "minecraft:enchantments")]
    pub minecraft_enchantments: Option<Vec<EntityEnchantmentPredicate>>,
}

/// Single enchantment predicate entry from loot table JSON.
/// Mirrors one entry in vanilla's `EnchantmentPredicate.enchantments` array.
#[derive(Deserialize, Clone, Debug)]
pub struct EntityEnchantmentPredicate {
    /// The enchantment(s) to check — single ID or tag reference (e.g. `"#minecraft:smelts_loot"`).
    pub enchantments: Option<StringOrVec>,
}

/// Entity predicate from loot table JSON.
/// Mirrors vanilla `EntityPredicate` — a conjunction of sub-predicates.
/// Currently extracts entity_type, flags.is_on_fire, and equipment.mainhand.
/// Other sub-predicates (effects, distance, location, nbt) are silently ignored.
#[derive(Deserialize, Clone, Debug)]
pub struct EntityPredicateStruct {
    /// Entity type to match. Mirrors vanilla `EntityTypePredicate`.
    #[serde(rename = "minecraft:entity_type")]
    pub entity_type: Option<StringOrVec>,
    /// Entity state flags. Mirrors vanilla `EntityFlagsPredicate`.
    #[serde(rename = "minecraft:flags")]
    pub flags: Option<EntityFlagsPredicateStruct>,
    /// Equipment slot predicates. Mirrors vanilla `EntityEquipmentPredicate`.
    #[serde(rename = "minecraft:equipment")]
    pub equipment: Option<EntityEquipmentPredicatesStruct>,
}

impl ToTokens for EntityPredicateStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(quote! {});
    }
}

#[derive(Deserialize, Clone, Debug)]
pub struct DamageSourcePredicateStruct {
    pub source_entity: Option<EntityPredicateStruct>,
    pub direct_entity: Option<EntityPredicateStruct>,
}

impl ToTokens for DamageSourcePredicateStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(quote! { () });
    }
}

#[derive(Deserialize, Clone, Debug)]
pub struct LocationPredicateStruct {
    pub biome: Option<StringOrVec>,
    pub dimension: Option<StringOrVec>,
}

impl ToTokens for LocationPredicateStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(quote! { () });
    }
}

/// Deserialized loot condition, tagged by the `"condition"` field.
#[derive(Deserialize, Clone, Debug)]
#[serde(tag = "condition")]
pub enum LootConditionStruct {
    /// Passes if the wrapped condition fails.
    #[serde(rename = "minecraft:inverted")]
    Inverted { term: Box<LootConditionStruct> },
    /// Passes if any of the child conditions pass.
    #[serde(rename = "minecraft:any_of")]
    AnyOf { terms: Vec<LootConditionStruct> },
    /// Passes if all child conditions pass.
    #[serde(rename = "minecraft:all_of")]
    AllOf { terms: Vec<LootConditionStruct> },
    /// Passes with the given probability.
    #[serde(rename = "minecraft:random_chance")]
    RandomChance { chance: f32 },
    /// Passes with probability scaled by an enchantment level.
    #[serde(rename = "minecraft:random_chance_with_enchanted_bonus")]
    RandomChanceWithEnchantedBonus {
        enchantment: String,
        chances: Option<Vec<f32>>,
    },
    /// Passes based on entity NBT predicates.
    #[serde(rename = "minecraft:entity_properties")]
    EntityProperties {
        entity: Option<String>,
        predicate: Option<EntityPredicateStruct>,
    },
    /// Passes if the block was killed by a player.
    #[serde(rename = "minecraft:killed_by_player")]
    KilledByPlayer,
    /// Passes based on entity scoreboard values.
    #[serde(rename = "minecraft:entity_scores")]
    EntityScores {
        entity: Option<String>,
        scores: Option<BTreeMap<String, StringOrVec>>,
    },
    /// Passes if the source block has the specified block-state properties.
    #[serde(rename = "minecraft:block_state_property")]
    BlockStateProperty {
        /// Namespaced block key to match.
        block: String,
        /// Required block-state property key-value pairs.
        properties: BTreeMap<String, String>,
    },
    /// Passes if the tool matches an item predicate.
    #[serde(rename = "minecraft:match_tool")]
    MatchTool {
        predicate: Option<ItemPredicateStruct>,
    },
    /// Passes with probability based on an enchantment's level.
    #[serde(rename = "minecraft:table_bonus")]
    TableBonus {
        enchantment: String,
        chances: Vec<f32>,
    },
    /// Passes if the item survives an explosion.
    #[serde(rename = "minecraft:survives_explosion")]
    SurvivesExplosion,
    /// Passes based on the damage source's properties.
    #[serde(rename = "minecraft:damage_source_properties")]
    DamageSourceProperties {
        predicate: Option<DamageSourcePredicateStruct>,
    },
    /// Passes based on the block's location.
    #[serde(rename = "minecraft:location_check")]
    LocationCheck {
        predicate: Option<LocationPredicateStruct>,
        #[serde(rename = "offsetX")]
        offset_x: Option<i32>,
        #[serde(rename = "offsetY")]
        offset_y: Option<i32>,
        #[serde(rename = "offsetZ")]
        offset_z: Option<i32>,
    },
    /// Passes based on current weather conditions.
    #[serde(rename = "minecraft:weather_check")]
    WeatherCheck {
        raining: Option<bool>,
        thundering: Option<bool>,
    },
    /// References an external predicate by ID.
    #[serde(rename = "minecraft:reference")]
    Reference { name: String },
    /// Passes based on the current in-game time.
    #[serde(rename = "minecraft:time_check")]
    TimeCheck {
        #[serde(rename = "value")]
        range: LootFunctionLimitCountStruct,
        period: Option<u64>,
    },
    /// Passes based on a numeric value range check.
    #[serde(rename = "minecraft:value_check")]
    ValueCheck {
        value: LootFunctionNumberProviderStruct,
        range: LootFunctionLimitCountStruct,
    },
    /// Passes if an enchantment is currently active.
    #[serde(rename = "minecraft:enchantment_active_check")]
    EnchantmentActiveCheck { active: bool },
}

impl ToTokens for LootConditionStruct {
    /// Emits the matching `LootCondition::*` token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = match self {
            Self::Inverted { term } => quote! { LootCondition::Inverted(&#term) },
            Self::AnyOf { terms } => quote! { LootCondition::AnyOf(&[#(#terms),*]) },
            Self::AllOf { terms } => quote! { LootCondition::AllOf(&[#(#terms),*]) },
            Self::RandomChance { chance } => {
                quote! { LootCondition::RandomChance { chance: #chance } }
            }
            Self::RandomChanceWithEnchantedBonus {
                enchantment,
                chances,
            } => {
                let e = LitStr::new(enchantment, Span::call_site());
                if let Some(chances) = chances {
                    quote! { LootCondition::RandomChanceWithEnchantedBonus { enchantment: #e, chances: Some(&[#(#chances),*]) } }
                } else {
                    quote! { LootCondition::RandomChanceWithEnchantedBonus { enchantment: #e, chances: None } }
                }
            }
            Self::EntityProperties { entity, predicate } => {
                let entity = entity.as_deref().unwrap_or("this");
                let e = LitStr::new(entity, Span::call_site());
                let expected_type = predicate
                    .as_ref()
                    .and_then(|p| p.entity_type.as_ref())
                    .map(|t| match t {
                        StringOrVec::String(s) => quote! { Some(#s) },
                        StringOrVec::Vec(v) => {
                            v.first().map_or(quote! { None }, |s| quote! { Some(#s) })
                        }
                    })
                    .unwrap_or(quote! { None });

                let is_on_fire = predicate
                    .as_ref()
                    .and_then(|p| p.flags.as_ref())
                    .and_then(|f| f.is_on_fire)
                    .map(|v| quote! { Some(#v) })
                    .unwrap_or(quote! { None });

                let mainhand_enchantment_tag = predicate
                    .as_ref()
                    .and_then(|p| p.equipment.as_ref())
                    .and_then(|eq| eq.mainhand.as_ref())
                    .and_then(|mh| mh.predicates.as_ref())
                    .and_then(|pred| pred.minecraft_enchantments.as_ref())
                    .and_then(|enchs| enchs.first())
                    .and_then(|ench| ench.enchantments.as_ref())
                    .map(|t| match t {
                        StringOrVec::String(s) => quote! { Some(#s) },
                        StringOrVec::Vec(v) => {
                            v.first().map_or(quote! { None }, |s| quote! { Some(#s) })
                        }
                    })
                    .unwrap_or(quote! { None });

                quote! { LootCondition::EntityProperties { entity: #e, expected_type: #expected_type, is_on_fire: #is_on_fire, mainhand_enchantment_tag: #mainhand_enchantment_tag } }
            }
            Self::KilledByPlayer => quote! { LootCondition::KilledByPlayer },
            Self::EntityScores { entity, scores: _ } => {
                let entity = entity.as_deref().unwrap_or("this");
                let e = LitStr::new(entity, Span::call_site());
                quote! { LootCondition::EntityScores { entity: #e } }
            }
            Self::BlockStateProperty { block, properties } => {
                let properties: Vec<_> = properties
                    .iter()
                    .map(|(k, v)| quote! { (#k, #v) })
                    .collect();
                quote! { LootCondition::BlockStateProperty { block: #block, properties: &[#(#properties),*] } }
            }
            Self::MatchTool { predicate } => {
                if let Some(pred) = predicate {
                    if let Some(items) = &pred.items {
                        match items {
                            StringOrVec::String(s) => {
                                let s = LitStr::new(s, Span::call_site());
                                quote! { LootCondition::MatchTool { items: Some(&[#s]) } }
                            }
                            StringOrVec::Vec(v) => {
                                let v = v.iter().map(|s| LitStr::new(s, Span::call_site()));
                                quote! { LootCondition::MatchTool { items: Some(&[#(#v),*]) } }
                            }
                        }
                    } else {
                        quote! { LootCondition::MatchTool { items: None } }
                    }
                } else {
                    quote! { LootCondition::MatchTool { items: None } }
                }
            }
            Self::TableBonus {
                enchantment,
                chances,
            } => {
                let e = LitStr::new(enchantment, Span::call_site());
                quote! { LootCondition::TableBonus { enchantment: #e, chances: &[#(#chances),*] } }
            }
            Self::SurvivesExplosion => quote! { LootCondition::SurvivesExplosion },
            Self::DamageSourceProperties { predicate } => {
                let expected_source_type = predicate
                    .as_ref()
                    .and_then(|p| p.source_entity.as_ref())
                    .and_then(|e| e.entity_type.as_ref())
                    .map(|t| match t {
                        StringOrVec::String(s) => quote! { Some(#s) },
                        StringOrVec::Vec(v) => {
                            v.first().map_or(quote! { None }, |s| quote! { Some(#s) })
                        }
                    })
                    .unwrap_or(quote! { None });

                let expected_direct_type = predicate
                    .as_ref()
                    .and_then(|p| p.direct_entity.as_ref())
                    .and_then(|e| e.entity_type.as_ref())
                    .map(|t| match t {
                        StringOrVec::String(s) => quote! { Some(#s) },
                        StringOrVec::Vec(v) => {
                            v.first().map_or(quote! { None }, |s| quote! { Some(#s) })
                        }
                    })
                    .unwrap_or(quote! { None });

                quote! { LootCondition::DamageSourceProperties { expected_source_type: #expected_source_type, expected_direct_type: #expected_direct_type } }
            }
            Self::LocationCheck {
                predicate,
                offset_x,
                offset_y,
                offset_z,
            } => {
                let ox = offset_x.unwrap_or(0);
                let oy = offset_y.unwrap_or(0);
                let oz = offset_z.unwrap_or(0);
                let expected_biome = predicate
                    .as_ref()
                    .and_then(|p| p.biome.as_ref())
                    .map(|b| match b {
                        StringOrVec::String(s) => quote! { Some(#s) },
                        StringOrVec::Vec(v) => {
                            v.first().map_or(quote! { None }, |s| quote! { Some(#s) })
                        }
                    })
                    .unwrap_or(quote! { None });
                quote! { LootCondition::LocationCheck { offset_x: #ox, offset_y: #oy, offset_z: #oz, expected_biome: #expected_biome } }
            }
            Self::WeatherCheck {
                raining,
                thundering,
            } => {
                let r = raining
                    .map(|b| quote! { Some(#b) })
                    .unwrap_or(quote! { None });
                let t = thundering
                    .map(|b| quote! { Some(#b) })
                    .unwrap_or(quote! { None });
                quote! { LootCondition::WeatherCheck { raining: #r, thundering: #t } }
            }
            Self::Reference { name } => {
                let n = LitStr::new(name, Span::call_site());
                quote! { LootCondition::Reference { name: #n } }
            }
            Self::TimeCheck { range, period } => {
                let r = range.to_token_stream();
                let p = period
                    .map(|val| quote! { Some(#val) })
                    .unwrap_or(quote! { None });
                quote! { LootCondition::TimeCheck { range: #r, period: #p } }
            }
            Self::ValueCheck { value, range } => {
                let v = value.to_token_stream();
                let r = range.to_token_stream();
                quote! { LootCondition::ValueCheck { value: #v, range: #r } }
            }
            Self::EnchantmentActiveCheck { active } => {
                quote! { LootCondition::EnchantmentActiveCheck { active: #active } }
            }
        };

        tokens.extend(name);
    }
}

/// Deserialized loot function wrapper combining a function type with optional conditions.
#[derive(Deserialize, Clone, Debug)]
pub struct LootFunctionStruct {
    /// The concrete function to apply (e.g., set count, apply bonus).
    #[serde(flatten)]
    content: LootFunctionTypesStruct,
    /// Conditions that must all pass for this function to be applied, if any.
    conditions: Option<Vec<LootConditionStruct>>,
}

impl ToTokens for LootFunctionStruct {
    /// Emits a `LootFunction { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let functions_tokens = &self.content.to_token_stream();

        let conditions_tokens = if let Some(conds) = &self.conditions {
            let cond_tokens: Vec<_> = conds.iter().map(ToTokens::to_token_stream).collect();
            quote! { Some(&[#(#cond_tokens),*]) }
        } else {
            quote! { None }
        };

        tokens.extend(quote! {
            LootFunction {
                content: #functions_tokens,
                conditions: #conditions_tokens,
            }
        });
    }
}

/// Deserialized loot function variant, tagged by the `"function"` field.
#[derive(Deserialize, Clone, Debug)]
#[serde(tag = "function")]
pub enum LootFunctionTypesStruct {
    /// Sets the stack count using a number provider.
    #[serde(rename = "minecraft:set_count")]
    SetCount {
        /// Number provider determining the new stack size.
        count: LootFunctionNumberProviderStruct,
        /// If `true`, adds to the existing count instead of replacing it.
        add: Option<bool>,
    },
    /// Increases count based on the level of a relevant enchantment.
    #[serde(rename = "minecraft:enchanted_count_increase")]
    EnchantedCountIncrease {
        enchantment: String,
        count: LootFunctionNumberProviderStruct,
        limit: Option<f32>,
    },
    /// Smelts the item as if processed in a furnace.
    #[serde(rename = "minecraft:furnace_smelt")]
    FurnaceSmelt,
    /// Sets the potion type on the item.
    #[serde(rename = "minecraft:set_potion")]
    SetPotion { id: String },
    /// Sets the amplifier on an ominous bottle item.
    #[serde(rename = "minecraft:set_ominous_bottle_amplifier")]
    SetOminousBottleAmplifier,
    /// Clamps the stack count to a min/max range.
    #[serde(rename = "minecraft:limit_count")]
    LimitCount {
        /// The min/max bounds to clamp the count to.
        limit: LootFunctionLimitCountStruct,
    },
    /// Applies an enchantment bonus using a named formula.
    #[serde(rename = "minecraft:apply_bonus")]
    ApplyBonus {
        /// Namespaced enchantment ID whose level feeds the formula.
        enchantment: String,
        /// Name of the bonus formula to apply.
        formula: String,
        /// Optional numeric parameters for the chosen formula.
        parameters: Option<LootFunctionBonusParameterStruct>,
    },
    /// Copies data components from the block entity source to the dropped item.
    #[serde(rename = "minecraft:copy_components")]
    CopyComponents {
        /// The source to copy components from (e.g., `"block_entity"`).
        source: String,
        /// List of component keys to copy.
        include: Vec<String>,
    },
    /// Copies specified block-state properties onto the item's `block_state` component.
    #[serde(rename = "minecraft:copy_state")]
    CopyState {
        /// Namespaced block whose state properties are copied.
        block: String,
        /// Names of the block-state properties to copy.
        properties: Vec<String>,
    },
    /// Randomly removes items from the stack to simulate explosion damage.
    #[serde(rename = "minecraft:explosion_decay")]
    ExplosionDecay,
}

impl ToTokens for LootFunctionTypesStruct {
    /// Emits the matching `LootFunctionTypes::*` token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = match self {
            Self::SetCount { count, add } => {
                let count = count.to_token_stream();
                let add = add.unwrap_or(false);
                quote! { LootFunctionTypes::SetCount { count: #count, add: #add } }
            }
            Self::SetOminousBottleAmplifier => {
                quote! { LootFunctionTypes::SetOminousBottleAmplifier }
            }
            Self::FurnaceSmelt => {
                quote! { LootFunctionTypes::FurnaceSmelt }
            }
            Self::SetPotion { id } => {
                let i = LitStr::new(id, Span::call_site());
                quote! { LootFunctionTypes::SetPotion { id: #i } }
            }
            Self::EnchantedCountIncrease {
                enchantment,
                count,
                limit,
            } => {
                let e = LitStr::new(enchantment, Span::call_site());
                let c = count.to_token_stream();
                let l = limit
                    .map(|val| quote! { Some(#val) })
                    .unwrap_or(quote! { None });
                quote! { LootFunctionTypes::EnchantedCountIncrease { enchantment: #e, count: #c, limit: #l } }
            }
            Self::LimitCount { limit } => {
                let min = if let Some(min) = limit.min {
                    quote! { Some(#min) }
                } else {
                    quote! { None }
                };
                let max = if let Some(max) = limit.max {
                    quote! { Some(#max) }
                } else {
                    quote! { None }
                };
                quote! { LootFunctionTypes::LimitCount { min: #min, max: #max } }
            }
            Self::ApplyBonus {
                enchantment,
                formula,
                parameters,
            } => {
                let parameters = if let Some(params) = parameters {
                    let params = params.to_token_stream();
                    quote! { Some(#params) }
                } else {
                    quote! { None }
                };

                quote! {
                    LootFunctionTypes::ApplyBonus {
                        enchantment: #enchantment,
                        formula: #formula,
                        parameters: #parameters,
                    }
                }
            }
            Self::CopyComponents { source, include } => {
                quote! {
                    LootFunctionTypes::CopyComponents {
                        source: #source,
                        include: &[#(#include),*],
                    }
                }
            }
            Self::CopyState { block, properties } => {
                quote! {
                    LootFunctionTypes::CopyState {
                        block: #block,
                        properties: &[#(#properties),*],
                    }
                }
            }
            Self::ExplosionDecay => {
                quote! { LootFunctionTypes::ExplosionDecay }
            }
        };

        tokens.extend(name);
    }
}

/// Deserialized number provider for loot function count values.
#[derive(Deserialize, Clone, Debug)]
#[serde(tag = "type")]
pub enum LootFunctionNumberProviderStruct {
    /// Draws a uniformly random value between `min` and `max` (inclusive).
    #[serde(rename = "minecraft:uniform")]
    Uniform {
        /// Lower bound of the uniform range.
        min: f32,
        /// Upper bound of the uniform range.
        max: f32,
    },
    /// Draws from a binomial distribution with `n` trials and success probability `p`.
    #[serde(rename = "minecraft:binomial")]
    Binomial {
        /// Number of trials.
        n: f32,
        /// Probability of success per trial.
        p: f32,
    },
    /// Always returns the fixed value.
    #[serde(rename = "minecraft:constant", untagged)]
    Constant(f32),
}

impl ToTokens for LootFunctionNumberProviderStruct {
    /// Emits the matching `LootFunctionNumberProvider::*` token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = match self {
            Self::Constant(value) => {
                quote! { LootFunctionNumberProvider::Constant { value: #value } }
            }
            Self::Uniform { min, max } => {
                quote! { LootFunctionNumberProvider::Uniform { min: #min, max: #max } }
            }
            Self::Binomial { n, p } => {
                quote! { LootFunctionNumberProvider::Binomial { n: #n, p: #p } }
            }
        };

        tokens.extend(name);
    }
}

/// Deserialized min/max bounds used by the `LimitCount` loot function.
#[derive(Deserialize, Clone, Debug)]
pub struct LootFunctionLimitCountStruct {
    /// Inclusive lower bound; count will not go below this value.
    min: Option<f32>,
    /// Inclusive upper bound; count will not exceed this value.
    max: Option<f32>,
}

impl ToTokens for LootFunctionLimitCountStruct {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let min = self
            .min
            .map(|val| quote! { Some(#val) })
            .unwrap_or(quote! { None });
        let max = self
            .max
            .map(|val| quote! { Some(#val) })
            .unwrap_or(quote! { None });
        tokens.extend(quote! { (#min, #max) });
    }
}

/// Deserialized bonus parameters for the `ApplyBonus` loot function.
#[derive(Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum LootFunctionBonusParameterStruct {
    /// A flat multiplier applied to the enchantment level.
    Multiplier {
        /// The bonus multiplier value.
        #[serde(rename = "bonusMultiplier")]
        bonus_multiplier: i32,
    },
    /// Probability-based bonus using extra attempts per enchantment level.
    Probability {
        /// Extra drop attempts added per enchantment level.
        extra: i32,
        /// Per-attempt success probability.
        probability: f32,
    },
}

impl ToTokens for LootFunctionBonusParameterStruct {
    /// Emits the matching `LootFunctionBonusParameter::*` token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = match self {
            Self::Multiplier { bonus_multiplier } => {
                quote! { LootFunctionBonusParameter::Multiplier { bonus_multiplier: #bonus_multiplier } }
            }
            Self::Probability { extra, probability } => {
                quote! { LootFunctionBonusParameter::Probability { extra: #extra, probability: #probability } }
            }
        };

        tokens.extend(name);
    }
}

/// Deserialized loot pool entry combining an entry type with optional conditions and functions.
#[derive(Deserialize, Clone, Debug)]
pub struct LootPoolEntryStruct {
    /// The concrete entry type (item, alternatives, etc.).
    #[serde(flatten)]
    content: LootPoolEntryTypesStruct,
    /// Relative probability weight; higher values are more likely.
    #[serde(default = "default_weight")]
    weight: i32,
    /// Quality of the entry, used to modify weight based on luck.
    #[serde(default)]
    quality: i32,
    /// Conditions that must all pass for this entry to be evaluated.
    conditions: Option<Vec<LootConditionStruct>>,
    /// Functions applied to the item if this entry is selected.
    functions: Option<Vec<LootFunctionStruct>>,
}

fn default_weight() -> i32 {
    1
}

impl ToTokens for LootPoolEntryStruct {
    /// Emits a `LootPoolEntry { … }` struct literal token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let content = &self.content;
        let weight = self.weight;
        let quality = self.quality;
        let conditions_tokens = if let Some(conds) = &self.conditions {
            let cond_tokens: Vec<_> = conds.iter().map(ToTokens::to_token_stream).collect();
            quote! { Some(&[#(#cond_tokens),*]) }
        } else {
            quote! { None }
        };
        let functions_tokens = if let Some(fns) = &self.functions {
            let cond_tokens: Vec<_> = fns.iter().map(ToTokens::to_token_stream).collect();
            quote! { Some(&[#(#cond_tokens),*]) }
        } else {
            quote! { None }
        };

        tokens.extend(quote! {
            LootPoolEntry {
                content: #content,
                weight: #weight,
                quality: #quality,
                conditions: #conditions_tokens,
                functions: #functions_tokens,
            }
        });
    }
}

/// Deserialized loot table category, tagged by its `"type"` field.
#[derive(Deserialize, Clone, Debug)]
#[serde(rename = "snake_case")]
pub enum LootTableTypeStruct {
    /// Nothing will be dropped.
    #[serde(rename = "minecraft:empty")]
    Empty,
    /// Entity loot will be dropped.
    #[serde(rename = "minecraft:entity")]
    Entity,
    /// Block drops will be generated.
    #[serde(rename = "minecraft:block")]
    Block,
    /// Chest loot will be generated.
    #[serde(rename = "minecraft:chest")]
    Chest,
}

impl ToTokens for LootTableTypeStruct {
    /// Emits the matching `LootTableType::*` token stream for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = match self {
            Self::Empty => quote! { LootTableType::Empty },
            Self::Entity => quote! { LootTableType::Entity },
            Self::Block => quote! { LootTableType::Block },
            Self::Chest => quote! { LootTableType::Chest },
        };

        tokens.extend(name);
    }
}
