use crate::block::{Block, BlockAssets};
use crate::enchantments::AttributeModifierSlot;
use heck::{ToPascalCase, ToShoutySnakeCase};
use proc_macro2::{Span, TokenStream};
use pumpkin_nbt::{Nbt, compound::NbtCompound};
use pumpkin_util::registry::TagType;
use pumpkin_util::text::TextContent;
use pumpkin_util::{registry::RegistryEntryList, text::TextComponent};
use quote::{ToTokens, format_ident, quote};
use serde::Deserialize;
use serde_repr::Deserialize_repr;
use std::{
    collections::{BTreeMap, HashSet},
    fs,
    io::Cursor,
};
use syn::{Ident, LitBool, LitByteStr, LitFloat, LitInt, LitStr};

/// Deserialized item entry from `items.json`.
#[derive(Deserialize)]
pub struct Item {
    /// Numeric protocol ID for this item.
    pub id: u16,
    /// All data components attached to this item.
    pub components: ItemComponents,
}

/// All deserialized data components for a single item as stored in `items.json`.
#[derive(Deserialize)]
pub struct ItemComponents {
    /// Display name translation component.
    #[serde(rename = "minecraft:item_name")]
    pub item_name: TextComponent,
    /// Maximum number of items per stack.
    #[serde(rename = "minecraft:max_stack_size")]
    pub max_stack_size: u8,
    /// Use cooldown component.
    #[serde(rename = "minecraft:use_cooldown")]
    pub use_cooldown: Option<UseCooldownComponent>,
    /// Jukebox song key if this item is a music disc, otherwise `None`.
    #[serde(rename = "minecraft:jukebox_playable")]
    pub jukebox_playable: Option<String>,
    /// Current damage value of a damageable item, if any.
    #[serde(rename = "minecraft:damage")]
    pub damage: Option<u16>,
    /// Maximum durability of a damageable item, if any.
    #[serde(rename = "minecraft:max_damage")]
    pub max_damage: Option<u16>,
    /// Attribute modifiers applied when the item is held or worn, if any.
    #[serde(rename = "minecraft:attribute_modifiers")]
    pub attribute_modifiers: Option<Vec<Modifier>>,
    /// Tool component containing mining rules, if this item is a tool.
    #[serde(rename = "minecraft:tool")]
    pub tool: Option<ToolComponent>,
    /// Food component, present if this item is edible.
    #[serde(rename = "minecraft:food")]
    pub food: Option<FoodComponent>,
    /// Equippable component, present if this item can be worn in an armor slot.
    #[serde(rename = "minecraft:equippable")]
    pub equippable: Option<EquippableComponent>,
    /// Consumable component, present if this item has a custom use animation or duration.
    #[serde(rename = "minecraft:consumable")]
    pub consumable: Option<Consumable>,
    /// Present if this item can block attacks (e.g., shields).
    #[serde(rename = "minecraft:blocks_attacks")]
    pub blocks_attacks: Option<BlocksAttacks>,
    /// Present if this item grants death protection (e.g., totem of undying).
    #[serde(rename = "minecraft:death_protection")]
    pub death_protection: Option<DeathProtection>,
    /// Damage type resistance, if this item is immune to specific damage types.
    #[serde(rename = "minecraft:damage_resistant")]
    pub damage_resistant: Option<DamageResistantComponent>,
    #[serde(rename = "minecraft:weapon")]
    pub weapon: Option<WeaponComponent>,
    #[serde(rename = "minecraft:enchantable")]
    pub enchantable: Option<EnchantableComponent>,
    #[serde(rename = "minecraft:attack_range")]
    pub attack_range: Option<serde_json::Value>,
    #[serde(rename = "minecraft:banner_patterns")]
    pub banner_patterns: Option<serde_json::Value>,
    #[serde(rename = "minecraft:bees")]
    pub bees: Option<serde_json::Value>,
    #[serde(rename = "minecraft:block_state")]
    pub block_state: Option<serde_json::Value>,
    #[serde(rename = "minecraft:break_sound")]
    pub break_sound: Option<serde_json::Value>,
    #[serde(rename = "minecraft:bucket_entity_data")]
    pub bucket_entity_data: Option<serde_json::Value>,
    #[serde(rename = "minecraft:bundle_contents")]
    pub bundle_contents: Option<serde_json::Value>,
    #[serde(rename = "minecraft:charged_projectiles")]
    pub charged_projectiles: Option<serde_json::Value>,
    #[serde(rename = "minecraft:chicken/variant")]
    pub chicken_variant: Option<serde_json::Value>,
    #[serde(rename = "minecraft:container")]
    pub container: Option<serde_json::Value>,
    #[serde(rename = "minecraft:damage_type")]
    pub damage_type: Option<serde_json::Value>,
    #[serde(rename = "minecraft:debug_stick_state")]
    pub debug_stick_state: Option<serde_json::Value>,
    #[serde(rename = "minecraft:dye")]
    pub dye: Option<serde_json::Value>,
    #[serde(rename = "minecraft:enchantment_glint_override")]
    pub enchantment_glint_override: Option<serde_json::Value>,
    #[serde(rename = "minecraft:enchantments")]
    pub enchantments: Option<serde_json::Value>,
    #[serde(rename = "minecraft:entity_data")]
    pub entity_data: Option<serde_json::Value>,
    #[serde(rename = "minecraft:fireworks")]
    pub fireworks: Option<serde_json::Value>,
    #[serde(rename = "minecraft:glider")]
    pub glider: Option<serde_json::Value>,
    #[serde(rename = "minecraft:instrument")]
    pub instrument: Option<serde_json::Value>,
    #[serde(rename = "minecraft:item_model")]
    pub item_model: Option<String>,
    #[serde(rename = "minecraft:kinetic_weapon")]
    pub kinetic_weapon: Option<serde_json::Value>,
    #[serde(rename = "minecraft:lore")]
    pub lore: Option<serde_json::Value>,
    #[serde(rename = "minecraft:map_color")]
    pub map_color: Option<serde_json::Value>,
    #[serde(rename = "minecraft:map_decorations")]
    pub map_decorations: Option<serde_json::Value>,
    #[serde(rename = "minecraft:minimum_attack_charge")]
    pub minimum_attack_charge: Option<serde_json::Value>,
    #[serde(rename = "minecraft:ominous_bottle_amplifier")]
    pub ominous_bottle_amplifier: Option<i32>,
    #[serde(rename = "minecraft:piercing_weapon")]
    pub piercing_weapon: Option<serde_json::Value>,
    #[serde(rename = "minecraft:pot_decorations")]
    pub pot_decorations: Option<serde_json::Value>,
    #[serde(rename = "minecraft:potion_contents")]
    pub potion_contents: Option<serde_json::Value>,
    #[serde(rename = "minecraft:potion_duration_scale")]
    pub potion_duration_scale: Option<f32>,
    #[serde(rename = "minecraft:provides_banner_patterns")]
    pub provides_banner_patterns: Option<serde_json::Value>,
    #[serde(rename = "minecraft:provides_trim_material")]
    pub provides_trim_material: Option<serde_json::Value>,
    #[serde(rename = "minecraft:rarity")]
    pub rarity: Option<serde_json::Value>,
    #[serde(rename = "minecraft:recipes")]
    pub recipes: Option<serde_json::Value>,
    #[serde(rename = "minecraft:repair_cost")]
    pub repair_cost: Option<serde_json::Value>,
    #[serde(rename = "minecraft:repairable")]
    pub repairable: Option<serde_json::Value>,
    #[serde(rename = "minecraft:stored_enchantments")]
    pub stored_enchantments: Option<serde_json::Value>,
    #[serde(rename = "minecraft:suspicious_stew_effects")]
    pub suspicious_stew_effects: Option<serde_json::Value>,
    #[serde(rename = "minecraft:swing_animation")]
    pub swing_animation: Option<serde_json::Value>,
    #[serde(rename = "minecraft:tooltip_display")]
    pub tooltip_display: Option<serde_json::Value>,
    #[serde(rename = "minecraft:use_effects")]
    pub use_effects: Option<serde_json::Value>,
    #[serde(rename = "minecraft:use_remainder")]
    pub use_remainder: Option<serde_json::Value>,
    #[serde(rename = "minecraft:writable_book_content")]
    pub writable_book_content: Option<serde_json::Value>,
}

#[derive(Deserialize)]
pub struct EnchantableComponent {
    pub value: i32,
}

impl ToTokens for ItemComponents {
    /// Emits a sequence of `(DataComponent, &impl DataComponentImpl)` tuple expressions for code generation.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let max_stack_size = LitInt::new(&self.max_stack_size.to_string(), Span::call_site());
        tokens.extend(quote! {
            (MaxStackSize, &MaxStackSizeImpl {
                size: #max_stack_size,
            }),
        });

        if let Some(use_cooldown) = &self.use_cooldown {
            let seconds = LitFloat::new(&format!("{:.1}", use_cooldown.seconds), Span::call_site());
            let cooldown_group = if let Some(cd_group) = &use_cooldown.cooldown_group {
                quote! { Some(#cd_group) }
            } else {
                quote! { None }
            };

            tokens.extend(quote! {
                (UseCooldown, &UseCooldownImpl {
                    seconds: #seconds,
                    cooldown_group: #cooldown_group,
                }),
            });
        }

        if let Some(playable) = &self.jukebox_playable {
            let song = LitStr::new(playable, Span::call_site());
            tokens.extend(quote! {
                (JukeboxPlayable, &JukeboxPlayableImpl{
                    song: #song,
                }),
            });
        }

        let TextContent::Translate {
            translate: text,
            bedrock_translate: _,
            with: _,
        } = *self.item_name.clone().0.content
        else {
            unreachable!()
        };
        let item_name = LitStr::new(&text, Span::call_site());
        tokens.extend(quote! {
            (ItemName, &ItemNameImpl {
                name: #item_name,
            }),
        });

        if let Some(d) = self.damage {
            let damage_lit = LitInt::new(&d.to_string(), Span::call_site());
            tokens.extend(quote! {
                (Damage, &DamageImpl {
                    damage: #damage_lit,
                }),
            });
        }

        if let Some(md) = self.max_damage {
            let max_damage_lit = LitInt::new(&md.to_string(), Span::call_site());
            tokens.extend(quote! {
                (MaxDamage, &MaxDamageImpl {
                    max_damage: #max_damage_lit,
                }),
            });
        }

        if let Some(modifiers) = &self.attribute_modifiers {
            let modifier_code = modifiers.iter().map(|modifier| {
                let r#type = format_ident!(
                    "{}",
                    modifier
                        .r#type
                        .strip_prefix("minecraft:")
                        .unwrap()
                        .to_uppercase()
                );
                let id = LitStr::new(&modifier.id, Span::call_site());
                let amount = modifier.amount;
                let operation = Ident::new(&format!("{:?}", modifier.operation), Span::call_site());
                let slot = modifier.slot.to_tokens();

                quote! {
                    Modifier {
                        r#type: &Attributes::#r#type,
                        id: #id,
                        amount: #amount,
                        operation: Operation::#operation,
                        slot: #slot,
                    }
                }
            });
            tokens.extend(quote! {
                (AttributeModifiers, &AttributeModifiersImpl {
                    attribute_modifiers: Cow::Borrowed(&[#(#modifier_code),*])
                }),
            });
        }

        if let Some(tool) = &self.tool {
            let rules_code = tool.rules.iter().map(|rule| {
                let block_array;

                if let RegistryEntryList::Single(t) = &rule.blocks {
                    if let TagType::Item(str) = t {
                        let ident = format_ident!(
                            "{}",
                            str.strip_prefix("minecraft:").unwrap().to_uppercase()
                        );
                        block_array = quote! {
                            IDs(Cow::Borrowed(&[&Block::#ident]))
                        }
                    } else if let TagType::Tag(str) = t {
                        block_array = quote! {
                            Tag(Cow::Borrowed(#str))
                        }
                    } else {
                        unreachable!();
                    }
                } else if let RegistryEntryList::Many(t) = &rule.blocks {
                    let mut array = vec![];
                    for i in t {
                        let TagType::Item(str) = i else {
                            unreachable!();
                        };
                        let ident = format_ident!(
                            "{}",
                            str.strip_prefix("minecraft:").unwrap().to_uppercase()
                        );
                        array.push(quote! {
                            &Block::#ident
                        });
                    }
                    block_array = quote! {
                        IDs(Cow::Borrowed(&[#(#array),*]))
                    }
                } else {
                    unreachable!();
                }
                let speed = if let Some(speed) = rule.speed {
                    quote! { Some(#speed) }
                } else {
                    quote! { None }
                };
                let correct_for_drops = if let Some(correct_for_drops) = rule.correct_for_drops {
                    let correct_for_drops = LitBool::new(correct_for_drops, Span::call_site());
                    quote! { Some(#correct_for_drops) }
                } else {
                    quote! { None }
                };
                quote! {
                    ToolRule {
                        blocks: #block_array,
                        speed: #speed,
                        correct_for_drops: #correct_for_drops
                    }
                }
            });
            let damage_per_block = {
                let speed = LitInt::new(&tool.damage_per_block.to_string(), Span::call_site());
                quote! { #speed }
            };
            let default_mining_speed = {
                let speed = LitFloat::new(
                    &format!("{:.1}", tool.default_mining_speed),
                    Span::call_site(),
                );
                quote! { #speed }
            };
            let can_destroy_blocks_in_creative =
                LitBool::new(tool.can_destroy_blocks_in_creative, Span::call_site());
            tokens.extend(quote! { (Tool, &ToolImpl {
                rules: Cow::Borrowed(&[#(#rules_code),*]),
                default_mining_speed: #default_mining_speed,
                damage_per_block: #damage_per_block,
                can_destroy_blocks_in_creative: #can_destroy_blocks_in_creative
            }), });
        }

        if let Some(food) = &self.food {
            let nutrition = LitInt::new(&food.nutrition.to_string(), Span::call_site());
            let saturation = LitFloat::new(&format!("{:.1}", food.saturation), Span::call_site());
            let can_always_eat = {
                let can = LitBool::new(food.can_always_eat, Span::call_site());
                quote! { #can }
            };
            tokens.extend(quote! { (Food, &FoodImpl {
                nutrition: #nutrition,
                saturation: #saturation,
                can_always_eat: #can_always_eat,
            }), });
        }

        if let Some(consumable) = &self.consumable {
            let consume_seconds = LitFloat::new(
                &format!("{:.1}", consumable.consume_seconds.unwrap_or(1.6)),
                Span::call_site(),
            );
            let consume_particles = LitBool::new(
                consumable.has_consume_particles.unwrap_or(true),
                Span::call_site(),
            );

            let anim_str = consumable.animation.clone().unwrap_or("eat".to_string());
            let animation = format_ident!("{}", anim_str.to_pascal_case());

            let sound_id = consumable
                .sound
                .clone()
                .unwrap_or("minecraft:entity.generic.eat".to_string());
            let variant_name = format_ident!(
                "{}",
                sound_id
                    .strip_prefix("minecraft:")
                    .unwrap()
                    .to_pascal_case()
            );
            let effects: Vec<ConsumeEffect> =
                consumable.on_consume_effects.clone().unwrap_or(vec![]);
            let mut effect_tokens = TokenStream::new();

            for effect in effects {
                match effect.r#type.as_str() {
                    "minecraft:clear_all_effects" => {
                        effect_tokens.extend(quote! { ConsumeEffect::ClearAllEffects, });
                    }
                    "minecraft:teleport_randomly" => {
                        let diameter = effect.diameter.unwrap_or(16.);
                        effect_tokens
                            .extend(quote! { ConsumeEffect::TeleportRandomly(#diameter), });
                    }
                    "minecraft:play_sound" => {
                        let sound = format_ident!(
                            "{}",
                            effect
                                .sound
                                .unwrap()
                                .strip_prefix("minecraft:")
                                .unwrap()
                                .to_pascal_case()
                        );
                        effect_tokens
                            .extend(quote! { ConsumeEffect::PlaySound(IdOr::Id(Sound::#sound)), });
                    }
                    "minecraft:apply_effects" => {
                        let probability = effect.probability.unwrap_or(1.);
                        if let StringOrStatusEffects::Effects(status_effect_instances) =
                            effect.effects.unwrap()
                        {
                            let mut status_tokens = TokenStream::new();

                            for status in status_effect_instances {
                                let effect_id = status.id;
                                let amplifier = status.amplifier.unwrap_or(0);
                                let duration = status.duration.unwrap_or(1);
                                let ambient = status.ambient.unwrap_or(false);
                                let show_particles = status.show_particles.unwrap_or(true);
                                let show_icon = status.show_icon.unwrap_or(true);
                                status_tokens.extend(quote! {
                                    StatusEffectInstance {
                                        effect_id: Cow::Borrowed(#effect_id),
                                        amplifier: #amplifier,
                                        duration: #duration,
                                        ambient: #ambient,
                                        show_particles: #show_particles,
                                        show_icon: #show_icon
                                    },
                                });
                            }
                            effect_tokens.extend(quote! {
                                ConsumeEffect::ApplyEffects((Cow::Borrowed(&[#status_tokens]), #probability)),
                            });
                        }
                    }
                    "minecraft:remove_effects" => {
                        if let StringOrStatusEffects::String(id) = effect.effects.unwrap() {
                            let effect_id = format_ident!(
                                "{}",
                                id.strip_prefix("minecraft:")
                                    .unwrap()
                                    .to_pascal_case()
                                    .to_uppercase()
                            );

                            effect_tokens.extend(quote! {
                                ConsumeEffect::RemoveEffects(IDSet::IDs(Cow::Borrowed(&[&StatusEffect::#effect_id]))),
                            });
                        }
                    }
                    _ => println!("Unknown CustomEffect type: {}", effect.r#type),
                }
            }

            tokens.extend(quote! { (Consumable, &ConsumableImpl {
                consume_seconds: #consume_seconds,
                animation: ConsumeAnimation::#animation,
                sound_event: IdOr::Id(Sound::#variant_name),
                consume_particles: #consume_particles,
                effects: Cow::Borrowed(&[#effect_tokens])
            }), });
        }

        if self.blocks_attacks.is_some() {
            tokens.extend(quote! { (BlocksAttacks, &BlocksAttacksImpl), });
        }

        if self.death_protection.is_some() {
            tokens.extend(quote! { (DeathProtection, &DeathProtectionImpl), });
        }

        if let Some(weapon) = &self.weapon {
            let damage = LitInt::new(
                &weapon.item_damage_per_attack.to_string(),
                Span::call_site(),
            );
            tokens.extend(quote! { (Weapon, &WeaponImpl { item_damage_per_attack: #damage }), });
        }

        if let Some(damage_resistant) = &self.damage_resistant {
            let res_type_variant = match damage_resistant.types.as_str() {
                // Common canonical and shorthand forms mapped to enum variant names
                "#minecraft:always_hurts_ender_dragons"
                | "minecraft:always_hurts_ender_dragons"
                | "always_hurts_ender_dragons" => "AlwaysHurtsEnderDragons",
                "#minecraft:always_kills_armor_stands"
                | "minecraft:always_kills_armor_stands"
                | "always_kills_armor_stands" => "AlwaysKillsArmorStands",
                "#minecraft:always_most_significant_fall"
                | "minecraft:always_most_significant_fall"
                | "always_most_significant_fall" => "AlwaysMostSignificantFall",
                "#minecraft:always_triggers_silverfish"
                | "minecraft:always_triggers_silverfish"
                | "always_triggers_silverfish" => "AlwaysTriggersSilverfish",
                "#minecraft:avoids_guardian_thorns"
                | "minecraft:avoids_guardian_thorns"
                | "avoids_guardian_thorns" => "AvoidsGuardianThorns",
                "#minecraft:burns_armor_stands"
                | "minecraft:burns_armor_stands"
                | "burns_armor_stands" => "BurnsArmorStands",
                "#minecraft:burn_from_stepping"
                | "minecraft:burn_from_stepping"
                | "burn_from_stepping" => "BurnFromStepping",
                "#minecraft:bypasses_armor" | "minecraft:bypasses_armor" | "bypasses_armor" => {
                    "BypassesArmor"
                }
                "#minecraft:bypasses_cooldown"
                | "minecraft:bypasses_cooldown"
                | "bypasses_cooldown" => "BypassesCooldown",
                "#minecraft:bypasses_effects"
                | "minecraft:bypasses_effects"
                | "bypasses_effects" => "BypassesEffects",
                "#minecraft:bypasses_enchantments"
                | "minecraft:bypasses_enchantments"
                | "bypasses_enchantments" => "BypassesEnchantments",
                "#minecraft:bypasses_invulnerability"
                | "minecraft:bypasses_invulnerability"
                | "bypasses_invulnerability" => "BypassesInvulnerability",
                "#minecraft:bypasses_resistance"
                | "minecraft:bypasses_resistance"
                | "bypasses_resistance" => "BypassesResistance",
                "#minecraft:bypasses_shield" | "minecraft:bypasses_shield" | "bypasses_shield" => {
                    "BypassesShield"
                }
                "#minecraft:bypasses_wolf_armor"
                | "minecraft:bypasses_wolf_armor"
                | "bypasses_wolf_armor" => "BypassesWolfArmor",
                "#minecraft:can_break_armor_stand"
                | "minecraft:can_break_armor_stand"
                | "can_break_armor_stand" => "CanBreakArmorStands",
                "#minecraft:damages_helmet" | "minecraft:damages_helmet" | "damages_helmet" => {
                    "DamagesHelmet"
                }
                "#minecraft:ignites_armor_stands"
                | "minecraft:ignites_armor_stands"
                | "ignites_armor_stands" => "IgnitesArmorStands",
                "#minecraft:is_drowning" | "minecraft:is_drowning" | "is_drowning" => "Drowning",
                "#minecraft:is_explosion"
                | "minecraft:is_explosion"
                | "is_explosion"
                | "explosion" => "Explosion",
                "#minecraft:is_fall" | "minecraft:is_fall" | "is_fall" | "fall" => "Fall",
                "#minecraft:is_fire" | "minecraft:is_fire" | "is_fire" | "fire" | "in_fire"
                | "minecraft:in_fire" => "Fire",
                "#minecraft:is_freezing" | "minecraft:is_freezing" | "is_freezing" => "Freezing",
                "#minecraft:is_lightning" | "minecraft:is_lightning" | "is_lightning" => {
                    "Lightning"
                }
                "#minecraft:is_player_attack"
                | "minecraft:is_player_attack"
                | "is_player_attack" => "PlayerAttack",
                "#minecraft:is_projectile" | "minecraft:is_projectile" | "is_projectile" => {
                    "Projectile"
                }
                "#minecraft:mace_smash" | "minecraft:mace_smash" | "mace_smash" => "MaceSmash",
                "#minecraft:no_anger" | "minecraft:no_anger" | "no_anger" => "NoAnger",
                "#minecraft:no_impact" | "minecraft:no_impact" | "no_impact" => "NoImpact",
                "#minecraft:no_knockback" | "minecraft:no_knockback" | "no_knockback" => {
                    "NoKnockback"
                }
                "#minecraft:panic_causes" | "minecraft:panic_causes" | "panic_causes" => {
                    "PanicCauses"
                }
                "#minecraft:panic_environmental_causes"
                | "minecraft:panic_environmental_causes"
                | "panic_environmental_causes" => "PanicEnvironmentalCauses",
                "#minecraft:witch_resistant_to"
                | "minecraft:witch_resistant_to"
                | "witch_resistant_to" => "WitchResistantTo",
                "#minecraft:wither_immune_to"
                | "minecraft:wither_immune_to"
                | "wither_immune_to" => "WitherImmuneTo",
                _ => "Generic",
            };
            let res_type_ident = format_ident!("{}", res_type_variant);
            tokens.extend(quote! { (DamageResistant, &DamageResistantImpl {
                res_type: DamageResistantType::#res_type_ident,
            }), });
        }

        if let Some(equippable) = &self.equippable {
            let slot = match equippable.slot.as_str() {
                "mainhand" => quote! { &EquipmentSlot::MAIN_HAND },
                "offhand" => quote! { &EquipmentSlot::OFF_HAND },
                "head" => quote! { &EquipmentSlot::HEAD },
                "chest" => quote! { &EquipmentSlot::CHEST },
                "legs" => quote! { &EquipmentSlot::LEGS },
                "feet" => quote! { &EquipmentSlot::FEET },
                "body" => quote! { &EquipmentSlot::BODY },
                "saddle" => quote! { &EquipmentSlot::SADDLE },
                _ => panic!("Unknown equippable slot: {}", equippable.slot),
            };
            let equip_sound = equippable
                .equip_sound
                .as_ref()
                .map(|s| {
                    let variant_name =
                        format_ident!("{}", s.strip_prefix("minecraft:").unwrap().to_pascal_case());
                    quote! { IdOr::Id(Sound::#variant_name) }
                })
                .unwrap_or(quote! { IdOr::Id(Sound::ItemArmorEquipGeneric) });
            let asset_id = equippable
                .asset_id
                .as_ref()
                .map(|s| {
                    let asset_id = LitStr::new(s, Span::call_site());
                    quote! { Some(Cow::Borrowed(#asset_id)) }
                })
                .unwrap_or(quote! { None });
            let camera_overlay = equippable
                .camera_overlay
                .as_ref()
                .map(|s| {
                    let camera_overlay = LitStr::new(s, Span::call_site());
                    quote! { Some(Cow::Borrowed(#camera_overlay)) }
                })
                .unwrap_or(quote! { None });
            let mut entities_option = TokenStream::new();
            if let Some(entities) = equippable.allowed_entities.clone() {
                let mut allowed_entities = TokenStream::new();
                match entities {
                    StringOrList::String(str) => {
                        if str.starts_with("#") {
                            let formatted = str.strip_prefix("#minecraft:").unwrap();
                            allowed_entities.extend(quote! {
                                IDSet::Tag(Cow::Borrowed(#formatted))
                            });
                        } else {
                            let ident = format_ident!(
                                "{}",
                                str.strip_prefix("minecraft:").unwrap().to_uppercase()
                            );
                            allowed_entities.extend(quote! {
                                IDSet::IDs(Cow::Borrowed(&[&crate::entity_type::EntityType::#ident]))
                            });
                        }
                    }
                    StringOrList::List(items) => {
                        let mut ids = TokenStream::new();
                        for x in items {
                            let entity = format_ident!(
                                "{}",
                                x.strip_prefix("minecraft:").unwrap().to_uppercase()
                            );
                            ids.extend(quote! { &crate::entity_type::EntityType::#entity, });
                        }

                        allowed_entities.extend(quote! {
                            IDSet::IDs(Cow::Borrowed(&[#ids]))
                        });
                    }
                }

                entities_option.extend(quote! { Some(#allowed_entities) });
            } else {
                entities_option.extend(quote! { None });
            }
            let dispensable = LitBool::new(equippable.dispensable, Span::call_site());
            let swappable = LitBool::new(equippable.swappable, Span::call_site());
            let damage_on_hurt = LitBool::new(equippable.damage_on_hurt, Span::call_site());
            let equip_on_interact = LitBool::new(equippable.equip_on_interact, Span::call_site());
            let can_be_sheared = LitBool::new(equippable.can_be_sheared, Span::call_site());
            let shearing_sound = equippable
                .shearing_sound
                .as_ref()
                .map(|s| {
                    let variant_name =
                        format_ident!("{}", s.strip_prefix("minecraft:").unwrap().to_pascal_case());
                    quote! { IdOr::Id(Sound::#variant_name) }
                })
                .unwrap_or(quote! { IdOr::Id(Sound::ItemShearsSnip) });

            tokens.extend(quote! { (Equippable, &EquippableImpl {
                slot: #slot,
                equip_sound: #equip_sound,
                asset_id: #asset_id,
                camera_overlay: #camera_overlay,
                allowed_entities: #entities_option,
                dispensable: #dispensable,
                swappable: #swappable,
                damage_on_hurt: #damage_on_hurt,
                equip_on_interact: #equip_on_interact,
                can_be_sheared: #can_be_sheared,
                shearing_sound: #shearing_sound
            }), });
        }

        if let Some(enchantable) = &self.enchantable {
            let value = LitInt::new(&enchantable.value.to_string(), Span::call_site());
            tokens.extend(quote! { (Enchantable, &EnchantableImpl { value: #value }), });
        }

        if self.attack_range.is_some() {
            tokens.extend(quote! { (AttackRange, &AttackRangeImpl), });
        }
        if self.banner_patterns.is_some() {
            tokens.extend(quote! { (BannerPatterns, &BannerPatternsImpl), });
        }
        if self.bees.is_some() {
            tokens.extend(quote! { (Bees, &BeesImpl), });
        }
        if let Some(block_state) = &self.block_state {
            let mut entries = TokenStream::new();
            if let serde_json::Value::Object(map) = block_state {
                for (k, v) in map {
                    let v_str = match v {
                        serde_json::Value::String(s) => s.clone(),
                        _ => v.to_string(),
                    };
                    let k_lit = LitStr::new(k, Span::call_site());
                    let v_lit = LitStr::new(&v_str, Span::call_site());
                    entries.extend(quote! {
                        (Cow::Borrowed(#k_lit), Cow::Borrowed(#v_lit)),
                    });
                }
            }
            tokens.extend(quote! {
                (BlockState, &BlockStateImpl {
                    properties: Cow::Borrowed(&[#entries])
                }),
            });
        }
        if self.break_sound.is_some() {
            tokens.extend(quote! { (BreakSound, &BreakSoundImpl), });
        }
        if self.bucket_entity_data.is_some() {
            tokens.extend(quote! { (BucketEntityData, &BucketEntityDataImpl), });
        }
        if self.bundle_contents.is_some() {
            tokens.extend(quote! { (BundleContents, &BundleContentsImpl { items: Vec::new() }), });
        }
        if self.charged_projectiles.is_some() {
            tokens.extend(quote! { (ChargedProjectiles, &ChargedProjectilesImpl { projectiles: Vec::new() }), });
        }
        if let Some(val) = &self.chicken_variant {
            let val_str = match val {
                serde_json::Value::String(s) => s.clone(),
                _ => val.to_string(),
            };
            let val_lit = LitStr::new(&val_str, Span::call_site());
            tokens.extend(quote! { (ChickenVariant, &ChickenVariantImpl { value: Cow::Borrowed(#val_lit) }), });
        }
        if self.container.is_some() {
            tokens.extend(quote! { (Container, &ContainerImpl { items: Vec::new() }), });
        }
        if self.damage_type.is_some() {
            tokens.extend(quote! { (DamageType, &DamageTypeImpl), });
        }
        if self.debug_stick_state.is_some() {
            tokens.extend(quote! { (DebugStickState, &DebugStickStateImpl), });
        }
        if self.dye.is_some() {
            tokens.extend(quote! { (Dye, &DyeImpl), });
        }
        if self.enchantment_glint_override.is_some() {
            tokens.extend(quote! { (EnchantmentGlintOverride, &EnchantmentGlintOverrideImpl), });
        }
        if self.enchantments.is_some() {
            tokens.extend(
                quote! { (Enchantments, &EnchantmentsImpl { enchantment: Cow::Borrowed(&[]) }), },
            );
        }
        if self.entity_data.is_some() {
            tokens.extend(quote! { (EntityData, &EntityDataImpl), });
        }
        if let Some(fireworks) = &self.fireworks {
            let flight_duration = if let serde_json::Value::Object(map) = fireworks
                && let Some(serde_json::Value::Number(num)) = map.get("flight_duration")
            {
                num.as_i64().unwrap_or(1) as i32
            } else {
                1
            };
            tokens.extend(quote! {
                (Fireworks, &FireworksImpl {
                    flight_duration: #flight_duration,
                    explosions: Vec::new(),
                }),
            });
        }
        if self.glider.is_some() {
            tokens.extend(quote! { (Glider, &GliderImpl), });
        }
        if self.instrument.is_some() {
            tokens.extend(quote! { (Instrument, &InstrumentImpl), });
        }
        if let Some(model) = &self.item_model {
            let model_lit = LitStr::new(model, Span::call_site());
            tokens
                .extend(quote! { (ItemModel, &ItemModelImpl { id: Cow::Borrowed(#model_lit) }), });
        }
        if self.kinetic_weapon.is_some() {
            tokens.extend(quote! { (KineticWeapon, &KineticWeaponImpl), });
        }
        if self.lore.is_some() {
            tokens.extend(quote! { (Lore, &LoreImpl), });
        }
        if self.map_color.is_some() {
            tokens.extend(quote! { (MapColor, &MapColorImpl), });
        }
        if self.map_decorations.is_some() {
            tokens.extend(quote! { (MapDecorations, &MapDecorationsImpl), });
        }
        if self.minimum_attack_charge.is_some() {
            tokens.extend(quote! { (MinimumAttackCharge, &MinimumAttackChargeImpl), });
        }
        if let Some(amp) = self.ominous_bottle_amplifier {
            let amp_lit = LitInt::new(&amp.to_string(), Span::call_site());
            tokens.extend(quote! { (OminousBottleAmplifier, &OminousBottleAmplifierImpl { amplifier: #amp_lit }), });
        }
        if self.piercing_weapon.is_some() {
            tokens.extend(quote! { (PiercingWeapon, &PiercingWeaponImpl), });
        }
        if self.pot_decorations.is_some() {
            tokens.extend(quote! { (PotDecorations, &PotDecorationsImpl), });
        }
        if self.potion_contents.is_some() {
            tokens.extend(quote! {
                (PotionContents, &PotionContentsImpl {
                    potion_id: None,
                    custom_color: None,
                    custom_effects: Vec::new(),
                    custom_name: None,
                }),
            });
        }
        if let Some(scale) = self.potion_duration_scale {
            let scale_lit = LitFloat::new(&format!("{scale:?}f32"), Span::call_site());
            tokens.extend(
                quote! { (PotionDurationScale, &PotionDurationScaleImpl { scale: #scale_lit }), },
            );
        }
        if self.provides_banner_patterns.is_some() {
            tokens.extend(quote! { (ProvidesBannerPatterns, &ProvidesBannerPatternsImpl), });
        }
        if self.provides_trim_material.is_some() {
            tokens.extend(quote! { (ProvidesTrimMaterial, &ProvidesTrimMaterialImpl), });
        }
        if self.rarity.is_some() {
            tokens.extend(quote! { (Rarity, &RarityImpl), });
        }
        if self.recipes.is_some() {
            tokens.extend(quote! { (Recipes, &RecipesImpl), });
        }
        if self.repair_cost.is_some() {
            tokens.extend(quote! { (RepairCost, &RepairCostImpl), });
        }
        if self.repairable.is_some() {
            tokens.extend(quote! { (Repairable, &RepairableImpl), });
        }
        if self.stored_enchantments.is_some() {
            tokens.extend(quote! { (StoredEnchantments, &StoredEnchantmentsImpl { enchantment: Cow::Borrowed(&[]) }), });
        }
        if self.suspicious_stew_effects.is_some() {
            tokens.extend(quote! { (SuspiciousStewEffects, &SuspiciousStewEffectsImpl), });
        }
        if self.swing_animation.is_some() {
            tokens.extend(quote! { (SwingAnimation, &SwingAnimationImpl), });
        }
        if self.tooltip_display.is_some() {
            tokens.extend(quote! { (TooltipDisplay, &TooltipDisplayImpl), });
        }
        if self.use_effects.is_some() {
            tokens.extend(quote! { (UseEffects, &UseEffectsImpl), });
        }
        if self.use_remainder.is_some() {
            tokens.extend(quote! { (UseRemainder, &UseRemainderImpl), });
        }
        if self.writable_book_content.is_some() {
            tokens.extend(
                quote! { (WritableBookContent, &WritableBookContentImpl { pages: Vec::new() }), },
            );
        }
    }
}

/// Serde default helper returning `1f32`.
const fn return_1f32() -> f32 {
    1.
}

/// Serde default helper returning `true`.
const fn return_true() -> bool {
    true
}

/// Deserialized tool component containing mining rules and default speed.
const fn default_item_damage() -> u32 {
    1
}

#[derive(Deserialize)]
pub struct ToolComponent {
    /// Ordered list of mining rules applied per block or block tag.
    rules: Vec<ToolRule>,
    /// Default mining speed when no rule matches, defaults to `1.0`.
    #[serde(default = "return_1f32")]
    default_mining_speed: f32,
    /// Durability consumed per block broken, defaults to `1`.
    #[serde(default = "default_item_damage")]
    damage_per_block: u32,
    /// Whether the tool can destroy blocks in creative mode, defaults to `true`.
    #[serde(default = "return_true")]
    can_destroy_blocks_in_creative: bool,
}

#[derive(Deserialize)]
pub struct UseCooldownComponent {
    seconds: f32,
    cooldown_group: Option<String>,
}

/// Serde default helper returning `false`.
const fn return_false() -> bool {
    false
}

/// Deserialized food component describing nutrition and saturation values.
#[derive(Deserialize, Copy, Clone)]
pub struct FoodComponent {
    /// Hunger points restored when eaten.
    nutrition: u8,
    /// Saturation modifier applied on eating.
    saturation: f32,
    /// Whether the item can be eaten even at full hunger, defaults to `false`.
    #[serde(default = "return_false")]
    can_always_eat: bool,
}

/// A single tool mining rule that maps a block set to an optional speed override.
#[derive(Deserialize, Clone)]
pub struct ToolRule {
    /// The block or block-tag set this rule applies to.
    blocks: RegistryEntryList,
    /// Optional speed override when mining matching blocks.
    speed: Option<f32>,
    /// Whether the tool yields drops for matching blocks, if specified.
    correct_for_drops: Option<bool>,
}

/// A single attribute modifier applied when the item is held or worn.
#[derive(Deserialize, Clone)]
pub struct Modifier {
    /// Namespaced attribute key (e.g., `"minecraft:attack_damage"`).
    pub r#type: String,
    /// Unique identifier for this modifier instance.
    pub id: String,
    /// Numeric value added or multiplied depending on `operation`.
    pub amount: f64,
    /// How `amount` is combined with the base attribute value.
    pub operation: Operation,
    // TODO: Make this an enum
    /// Equipment slot in which this modifier is active.
    pub slot: AttributeModifierSlot,
}

/// Serde default helper returning `true`.
const fn _true() -> bool {
    true
}

/// Deserialized consumable component describing use duration.
#[derive(Deserialize, Clone)]
pub struct Consumable {
    consume_seconds: Option<f32>,
    has_consume_particles: Option<bool>,
    animation: Option<String>,
    sound: Option<String>,
    on_consume_effects: Option<Vec<ConsumeEffect>>,
}
#[derive(Deserialize, Clone)]
pub struct ConsumeEffect {
    r#type: String,
    probability: Option<f32>,
    diameter: Option<f32>,
    sound: Option<String>,
    effects: Option<StringOrStatusEffects>,
}
#[derive(Deserialize, Clone)]
pub struct StatusEffectInstance {
    pub id: String,
    pub amplifier: Option<i32>,
    pub duration: Option<i32>,
    pub ambient: Option<bool>,
    pub show_particles: Option<bool>,
    pub show_icon: Option<bool>,
}
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum StringOrStatusEffects {
    String(String),
    Effects(Vec<StatusEffectInstance>),
}

/// Deserialized death-protection component (e.g., totem of undying); fields are unimplemented.
#[derive(Deserialize, Clone)]
pub struct DeathProtection {
    // TODO
}

/// Deserialized attack-blocking component (e.g., shield); fields are unimplemented.
#[derive(Deserialize, Clone)]
pub struct WeaponComponent {
    #[serde(default = "default_item_damage")]
    pub item_damage_per_attack: u32,
    // TODO: Add disable_blocking_for_seconds parsing when shield-disable mechanic is implemented.
    // This preserves round-trip fidelity for vanilla items and datapacks.
}

#[derive(Deserialize, Clone)]
pub struct BlocksAttacks {
    // TODO
}

/// Deserialized damage-resistance component indicating which damage types the item resists.
#[derive(Deserialize, Clone)]
pub struct DamageResistantComponent {
    /// Namespaced damage type tag the item is immune to.
    pub types: String,
}
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum StringOrList {
    String(String),
    List(Vec<String>),
}

/// Deserialized equippable component describing how an item is worn or equipped.
#[derive(Deserialize, Clone)]
pub struct EquippableComponent {
    /// Equipment slot the item occupies (e.g., `"head"`, `"chest"`).
    pub slot: String,
    /// Sound event played when the item is equipped; uses a generic fallback if absent.
    pub equip_sound: Option<String>,
    /// Texture asset identifier for the equipped model, if any.
    pub asset_id: Option<String>,
    /// Screen overlay texture shown while equipped, if any.
    pub camera_overlay: Option<String>,
    pub allowed_entities: Option<StringOrList>,
    #[serde(default = "_true")]
    pub dispensable: bool,
    /// Whether shift-clicking swaps the item into the equipment slot, defaults to `true`.
    #[serde(default = "_true")]
    pub swappable: bool,
    /// Whether the item takes damage when the wearer is hurt, defaults to `true`.
    #[serde(default = "_true")]
    pub damage_on_hurt: bool,
    /// Whether right-clicking an entity equips the item, defaults to `false`.
    #[serde(default)]
    pub equip_on_interact: bool,
    /// Whether shears can remove this item from an entity, defaults to `false`.
    #[serde(default)]
    pub can_be_sheared: bool,
    /// Sound event played when sheared off, if any.
    pub shearing_sound: Option<String>,
}

/// Arithmetic operation applied when combining an attribute modifier's amount with the base value.
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[expect(clippy::enum_variant_names)]
pub enum Operation {
    /// Adds the modifier amount directly to the base value.
    AddValue,
    /// Adds `amount * base` to the running total.
    AddMultipliedBase,
    /// Multiplies the running total by `1 + amount`.
    AddMultipliedTotal,
}

#[derive(Deserialize_repr, Debug)]
#[repr(i32)]
enum BedrockItemVersion {
    Legacy = 0,
    DataDriven = 1,
    None = 2,
}

#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct BedrockRuntimeItemState {
    name: String,
    id: i16,
    version: BedrockItemVersion,
    component_based: bool,
}

#[derive(Debug)]
enum BedrockToolType {
    Sword,
    Shovel,
    Pickaxe,
    Axe,
    Shears,
    Hoe,
}

#[derive(Debug)]
enum BedrockArmorType {
    Helmet,
    Leggings,
    Chestplate,
    Boots,
}

#[derive(Debug)]
struct BedrockRemapEntry {
    identifier: String,
    data: u32,
    block_runtime_id_range: Option<(i32, i32)>,
    tool_type: Option<BedrockToolType>,
    armor_type: Option<BedrockArmorType>,
    protection_value: Option<i32>,
    is_entity_placer: bool,
    is_edible: bool,
}

#[derive(Debug)]
pub struct BedrockItem {
    id: i16,
    registry_key: String,
    version: BedrockItemVersion,
    component_based: bool,
    components: Nbt,
}

/// Reads `items.json` and generates the complete item registry `TokenStream`.
pub fn build() -> TokenStream {
    let blocks_assets: BlockAssets =
        serde_json::from_str(&fs::read_to_string("../../assets/blocks.json").unwrap())
            .expect("Failed to parse blocks.json");

    let items: BTreeMap<String, Item> =
        serde_json::from_str(&fs::read_to_string("../../assets/items.json").unwrap())
            .expect("Failed to parse items.json");

    let eggs: HashSet<u16> = serde_json::from_str::<BTreeMap<u16, String>>(
        &fs::read_to_string("../../assets/spawn_egg.json").unwrap(),
    )
    .expect("Failed to parse spawn_egg.json")
    .into_keys()
    .collect::<HashSet<_>>();

    let be_item_components: BTreeMap<String, Option<NbtCompound>> = {
        let data = fs::read("../../assets/bedrock/item_components.nbt").unwrap();
        let mut cursor = Cursor::new(data);
        let nbt = pumpkin_nbt::nbt_compress::read_gzip_compound_tag(&mut cursor)
            .expect("Failed to parse bedrock/item_components.nbt");
        nbt.child_tags
            .into_iter()
            .map(|(k, v)| {
                let comp = match v {
                    pumpkin_nbt::tag::NbtTag::Compound(c) => Some(c),
                    _ => None,
                };
                (k.into_string(), comp)
            })
            .collect()
    };

    let be_runtime_item_states: Vec<BedrockRuntimeItemState> = serde_json::from_str(
        &fs::read_to_string("../../assets/bedrock/runtime_item_states.json").unwrap(),
    )
    .expect("Failed to parse bedrock/runtime_item_states.json");

    let be_item_data_overrides: BTreeMap<String, u32> = serde_json::from_str(
        &fs::read_to_string("../../assets/bedrock/item_data_overrides.json").unwrap(),
    )
    .expect("Failed to parse bedrock/item_data_overrides.json");

    let be_valid_item_identifiers = be_runtime_item_states
        .iter()
        .map(|item| &item.name)
        .collect::<HashSet<_>>();

    let mut be_item_remaps = indexmap::IndexMap::new();

    let mut item_to_block: BTreeMap<u16, &Block> = BTreeMap::new();
    for block in &blocks_assets.blocks {
        if block.item_id != 0 {
            item_to_block.entry(block.item_id).or_insert(block);
        }
    }

    for (name, item) in &items {
        let be_identifier = "minecraft:".to_owned()
            + &match &**name {
                "bricks" => "brick_block".into(),
                "cobblestone_stairs" => "stone_stairs".into(),
                "cobweb" => "web".into(),
                "dead_bush" => "deadbush".into(),
                "debug_stick" => "stick".into(),
                "dirt_path" => "grass_path".into(),
                "end_stone_bricks" => "end_bricks".into(),
                "end_stone_brick_stairs" => "end_brick_stairs".into(),
                "flowering_azalea_leaves" => "azalea_leaves_flowered".into(),
                "frogspawn" => "frog_spawn".into(),
                "furnace_minecart" => "hopper_minecart".into(),
                "glow_item_frame" => "glow_frame".into(),
                "item_frame" => "frame".into(),
                "jack_o_lantern" => "lit_pumpkin".into(),
                "light" => "light_block".into(),
                "light_gray_glazed_terracotta" => "silver_glazed_terracotta".into(),
                "lily_pad" => "waterlily".into(),
                "knowledge_book" => "book".into(),
                "magma_block" => "magma".into(),
                "map" => "empty_map".into(),
                "melon" => "melon_block".into(),
                "music_disc_bounce" => "music_disc_13".into(),
                "nether_brick" => "netherbrick".into(),
                "nether_bricks" => "nether_brick".into(),
                "nether_quartz_ore" => "quartz_ore".into(),
                "note_block" => "noteblock".into(),
                "oak_button" => "wooden_button".into(),
                "oak_door" => "wooden_door".into(),
                "oak_fence_gate" => "fence_gate".into(),
                "oak_pressure_plate" => "wooden_pressure_plate".into(),
                "oak_trapdoor" => "trapdoor".into(),
                "powered_rail" => "golden_rail".into(),
                "prismarine_brick_stairs" => "prismarine_bricks_stairs".into(),
                "red_nether_bricks" => "red_nether_brick".into(),
                "rooted_dirt" => "dirt_with_roots".into(),
                "scute" => "turtle_scute".into(),
                "shulker_box" => "undyed_shulker_box".into(),
                "slime_block" => "slime".into(),
                "small_dripleaf" => "small_dripleaf_block".into(),
                "snow" => "snow_layer".into(),
                "snow_block" => "snow".into(),
                "spawner" => "mob_spawner".into(),
                "stonecutter" => "stonecutter_block".into(),
                "stone_stairs" => "normal_stone_stairs".into(),
                "terracotta" => "hardened_clay".into(),
                "test_block" => "unknown".into(),
                "test_instance_block" => "unknown".into(),
                "tipped_arrow" | "spectral_arrow" => "arrow".into(),
                "waxed_copper_block" => "waxed_copper".into(),
                "zombified_piglin_spawn_egg" => "zombie_pigman_spawn_egg".into(),
                n if n.contains("sulfur") => "unknown".into(),
                n => {
                    if n.ends_with("banner") {
                        "banner".into()
                    } else if n.ends_with("bed") {
                        "bed".into()
                    } else if n.starts_with("stone_slab") {
                        n.replace("stone_slab", "stone_block_slab")
                    } else if n.starts_with("double_stone_slab") {
                        n.replace("double_stone_slab", "stone_block_slab")
                    } else {
                        n.into()
                    }
                }
            };

        assert!(
            be_valid_item_identifiers.contains(&be_identifier),
            "Invalid Bedrock identifier `{be_identifier}`. From Java name `{name}`"
        );

        let block = item_to_block.get(&item.id);

        let type_suffix = name
            .split(':')
            .next_back()
            .unwrap()
            .split('_')
            .next_back()
            .unwrap();

        be_item_remaps.insert(
            name,
            BedrockRemapEntry {
                identifier: be_identifier,
                data: *be_item_data_overrides.get(name).unwrap_or(&0),
                block_runtime_id_range: block.and_then(|b| {
                    let min = b.states.first();
                    let max = b.states.last();

                    if let (Some(min), Some(max)) = (min, max) {
                        Some((min.id.0 as i32, max.id.0 as i32))
                    } else {
                        None
                    }
                }),
                tool_type: match type_suffix {
                    "sword" => Some(BedrockToolType::Sword),
                    "shovel" => Some(BedrockToolType::Shovel),
                    "pickaxe" => Some(BedrockToolType::Pickaxe),
                    "axe" => Some(BedrockToolType::Axe),
                    "shears" => Some(BedrockToolType::Shears),
                    "hoe" => Some(BedrockToolType::Hoe),
                    _ => None,
                },
                armor_type: match type_suffix {
                    "helmet" => Some(BedrockArmorType::Helmet),
                    "chestplate" => Some(BedrockArmorType::Chestplate),
                    "leggings" => Some(BedrockArmorType::Leggings),
                    "boots" => Some(BedrockArmorType::Boots),
                    _ => None,
                },
                protection_value: if let (Some(_), Some(_)) = (
                    &item.components.equippable,
                    &item.components.attribute_modifiers,
                ) {
                    Some(1) // TODO: calculate correct value
                } else {
                    None
                },
                is_entity_placer: eggs.contains(&item.id)
                    || matches!(
                        &**name,
                        "firework_rocket"
                            | "minecart"
                            | "tnt_minecart"
                            | "chest_minecart"
                            | "hopper_minecart"
                            | "furnace_minecart"
                            | "command_block_minecart"
                            | "oak_boat"
                            | "oak_chest_boat"
                            | "spruce_boat"
                            | "spruce_chest_boat"
                            | "birch_boat"
                            | "birch_chest_boat"
                            | "jungle_boat"
                            | "jungle_chest_boat"
                            | "acacia_boat"
                            | "acacia_chest_boat"
                            | "dark_oak_boat"
                            | "dark_oak_chest_boat"
                            | "mangrove_boat"
                            | "mangrove_chest_boat"
                            | "cherry_boat"
                            | "cherry_chest_boat"
                            | "pale_oak_boat"
                            | "pale_oak_chest_boat"
                            | "bamboo_raft"
                            | "bamboo_chest_raft"
                    ),
                is_edible: item.components.food.is_some(),
            },
        );
    }

    let mut bedrock_item_definitions = Vec::new();
    for item in be_runtime_item_states {
        let components = be_item_components.get(&item.name).cloned().flatten();

        bedrock_item_definitions.push(BedrockItem {
            id: item.id,
            registry_key: item.name.clone(),
            version: item.version,
            component_based: item.component_based,
            components: if let Some(c) = components {
                Nbt::new("".to_string(), c.clone())
            } else {
                if item.component_based {
                    eprintln!(
                        "Couldn't find the components for a component based item: {}.",
                        item.name
                    );
                }
                Nbt::new("".to_string(), NbtCompound::new())
            },
        });
    }

    let mut type_from_raw_id_arms = TokenStream::new();
    let mut type_from_name = TokenStream::new();

    let mut constants = TokenStream::new();
    let mut bedrock_constants = TokenStream::new();
    let mut mapping_constants = TokenStream::new();

    let mut all_bedrock_items = Vec::new();

    let mut all_java_items = Vec::new();
    let mut all_java_item_ids = Vec::new();

    for (name, item) in &items {
        let const_ident = format_ident!("{}", name.to_shouty_snake_case());

        let components = &item.components;
        let components_tokens = components.to_token_stream();

        let id_lit = LitInt::new(&item.id.to_string(), Span::call_site());

        constants.extend(quote! {
            pub const #const_ident: Self = Self {
                id: #id_lit,
                registry_key: #name,
                components: &[#components_tokens],
            };
        });

        type_from_raw_id_arms.extend(quote! {
            #id_lit => Some(&Self::#const_ident),
        });

        type_from_name.extend(quote! {
            #name => Some(&Self::#const_ident),
        });

        all_java_items.push(const_ident);
        all_java_item_ids.push(id_lit);
    }

    let bedrock_id_by_key: std::collections::HashMap<String, i16> = bedrock_item_definitions
        .iter()
        .map(|item| (item.registry_key.clone(), item.id))
        .collect();

    for item in bedrock_item_definitions {
        let const_ident = format_ident!(
            "{}",
            item.registry_key
                .split(':')
                .next_back()
                .unwrap()
                .to_shouty_snake_case()
        );

        let id = LitInt::new(&item.id.to_string(), Span::call_site());
        let registry_key = item.registry_key;
        let version = format_ident!(
            "{}",
            match item.version {
                BedrockItemVersion::Legacy => "Legacy",
                BedrockItemVersion::DataDriven => "DataDriven",
                BedrockItemVersion::None => "None",
            }
        );
        let component_based = item.component_based;

        let components_bytes_lit =
            LitByteStr::new(&item.components.write_bedrock(), Span::call_site());

        bedrock_constants.extend(quote! {
            pub const #const_ident: Self = Self {
                id: #id,
                registry_key: #registry_key,
                version: BedrockItemVersion::#version,
                component_based: #component_based,
                definition_components: #components_bytes_lit
            };
        });

        all_bedrock_items.push(const_ident);
    }
    let all_bedrock_items_count_lit =
        LitInt::new(&all_bedrock_items.len().to_string(), Span::call_site());

    for (java_name, mapping) in &be_item_remaps {
        let java_const_ident = format_ident!("{}", java_name.to_shouty_snake_case());
        let bedrock_const_ident = format_ident!(
            "{}",
            mapping
                .identifier
                .split(':')
                .next_back()
                .unwrap()
                .to_shouty_snake_case()
        );

        let data_lit = LitInt::new(&mapping.data.to_string(), Span::call_site());

        mapping_constants.extend(quote! {
            pub const #java_const_ident: Self = Self {
                java_item: &Item::#java_const_ident,

                bedrock_item: &BedrockItem::#bedrock_const_ident,
                bedrock_data: #data_lit,
                bedrock_block_state: 0
            };
        });
    }

    let mut bedrock_to_java_arms = TokenStream::new();
    let mut seen_bedrock_pairs = std::collections::HashSet::new();

    let mut bedrock_id_fallback_arms = TokenStream::new();
    let mut seen_bedrock_ids = std::collections::HashSet::new();

    for (java_name, mapping) in &be_item_remaps {
        let java_const_ident = format_ident!("{}", java_name.to_shouty_snake_case());
        let bedrock_id = *bedrock_id_by_key
            .get(&mapping.identifier)
            .unwrap_or_else(|| {
                panic!(
                    "Missing bedrock item for identifier: {}",
                    mapping.identifier
                )
            });
        let bedrock_data = mapping.data;

        if seen_bedrock_pairs.insert((bedrock_id, bedrock_data)) {
            let id_lit = syn::LitInt::new(&bedrock_id.to_string(), proc_macro2::Span::call_site());
            let data_lit =
                syn::LitInt::new(&bedrock_data.to_string(), proc_macro2::Span::call_site());
            bedrock_to_java_arms.extend(quote! {
                (#id_lit, #data_lit) => Some(&Self::#java_const_ident),
            });
        }

        if seen_bedrock_ids.insert(bedrock_id) {
            let id_lit = syn::LitInt::new(&bedrock_id.to_string(), proc_macro2::Span::call_site());
            bedrock_id_fallback_arms.extend(quote! {
                #id_lit => Some(&Self::#java_const_ident),
            });
        }
    }

    quote! {
        #[allow(clippy::wildcard_imports, clippy::enum_glob_use, clippy::too_many_lines)]
        use crate::data_component::DataComponent::*;
        use crate::data_component_impl::*;
        use crate::tag::{RegistryKey, Taggable};
        use pumpkin_util::text::TextComponent;
        use std::borrow::Cow;
        use std::hash::{Hash, Hasher};
        use crate::{tag, AttributeModifierSlot};
        use crate::attributes::Attributes;
        use crate::data_component_impl::IDSet::{IDs, Tag};
        use crate::data_component::DataComponent;
        use crate::effect::StatusEffect;
        use crate::Block;
        use crate::sound::Sound;

        use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};

        #[derive(Clone)]
        pub struct Item {
            pub id: u16,
            pub registry_key: &'static str,
            pub components: &'static [(DataComponent, &'static dyn DataComponentImpl)],
        }

        impl Eq for Item {}

        impl Hash for Item {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.id.hash(state);
            }
        }

        impl PartialEq for Item {
            fn eq(&self, other: &Self) -> bool {
                self.id == other.id
            }
        }

        #[derive(Clone)]
        pub enum BedrockItemVersion {
            Legacy,
            DataDriven,
            None
        }

        #[derive(Clone)]
        pub struct BedrockItem {
            pub id: i16,
            pub registry_key: &'static str,
            pub version: BedrockItemVersion,
            pub component_based: bool,
            pub definition_components: &'static [u8],
        }

        impl Eq for BedrockItem {}

        impl Hash for BedrockItem {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.id.hash(state);
            }
        }

        impl PartialEq for BedrockItem {
            fn eq(&self, other: &Self) -> bool {
                self.id == other.id
            }
        }

        #[derive(Clone)]
        pub struct JavaToBedrockItemMapping {
            pub java_item: &'static Item,

            pub bedrock_item: &'static BedrockItem,
            pub bedrock_data: u32,
            pub bedrock_block_state: u16
        }

        impl Eq for JavaToBedrockItemMapping {}

        impl Hash for JavaToBedrockItemMapping {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.java_item.id.hash(state);
            }
        }

        impl PartialEq for JavaToBedrockItemMapping {
            fn eq(&self, other: &Self) -> bool {
                self.java_item.id == other.java_item.id
            }
        }

        impl Item {
            #constants

            #[must_use]
            #[allow(deprecated)]
            pub fn translated_name(&self) -> TextComponent {
                let name = self
                    .components
                    .iter()
                    .find_map(|(id, data)| {
                        if id == &ItemName {
                            data.as_any().downcast_ref::<ItemNameImpl>().map(|n| n.name)
                        } else {
                            None
                        }
                    })
                    .unwrap_or(self.registry_key);
                TextComponent::translate(name, &[])
            }

            #[doc = "Try to parse an item from a resource location string."]
            #[must_use]
            pub fn from_registry_key(name: &str) -> Option<&'static Self> {
                let name = name.strip_prefix("minecraft:").unwrap_or(name);
                match name {
                    #type_from_name
                    _ => None
                }
            }

            #[doc = "Try to parse an item from a raw id."]
            #[must_use]
            pub const fn from_id(id: u16) -> Option<&'static Self> {
                match id {
                    #type_from_raw_id_arms
                    _ => None
                }
            }
        }

        impl Taggable for Item {
            #[inline]
            fn tag_key() -> RegistryKey {
                RegistryKey::Item
            }

            #[inline]
            fn registry_key(&self) -> &str {
                self.registry_key
            }

            #[inline]
            fn registry_id(&self) -> u16 {
                self.id
            }
        }

        impl BedrockItem {
            #bedrock_constants

            pub const ALL_BEDROCK_ITEMS: [&'static Self; #all_bedrock_items_count_lit] = [
                #( &Self::#all_bedrock_items, )*
            ];
        }

        impl JavaToBedrockItemMapping {
            #mapping_constants

            #[must_use]
            pub const fn from_java_item_id(item_id: u16) -> Option<&'static Self> {
                match item_id {
                    #( #all_java_item_ids => Some(&Self::#all_java_items), )*
                    _ => None,
                }
            }

            #[must_use]
            pub fn from_bedrock(bedrock_id: i16, bedrock_data: u32) -> Option<&'static Self> {
                match (bedrock_id, bedrock_data) {
                    #bedrock_to_java_arms
                    _ => match bedrock_id {
                        #bedrock_id_fallback_arms
                        _ => None,
                    }
                }
            }
        }
    }
}
