use std::{collections::BTreeMap, fs};

use heck::ToShoutySnakeCase;
use proc_macro2::TokenStream;
use pumpkin_util::text::TextComponent;
use pumpkin_util::text::TextContent::Translate;
use quote::{format_ident, quote};
use serde::Deserialize;

/// Raw deserialization shape for a single enchantment entry from `enchantments.json`.
#[derive(Deserialize)]
pub struct Enchantment {
    /// Numeric registry ID for this enchantment.
    pub id: u8,
    /// Anvil repair cost multiplier added when applying this enchantment.
    pub anvil_cost: u32,
    /// Tag path (prefixed with `#`) of items that support this enchantment.
    pub supported_items: String,
    /// Display name component for this enchantment (typically a translation key).
    pub description: TextComponent,
    /// Optional exclusive-set tag; enchantments in the same set are mutually incompatible.
    pub exclusive_set: Option<String>,
    /// Maximum level this enchantment can reach.
    pub max_level: i32,
    /// Equipment slots this enchantment's attribute modifiers apply to.
    pub slots: Vec<AttributeModifierSlot>, // TODO: add more
    /// The weight of this enchantment (used for random selection).
    pub weight: i32,
    /// The minimum cost to get this enchantment.
    pub min_cost: Cost,
    /// The maximum cost to get this enchantment.
    pub max_cost: Cost,
}

#[derive(Deserialize, Clone, Copy)]
pub struct Cost {
    pub base: i32,
    pub per_level_above_first: i32,
}

/// Equipment slot category that an enchantment's attribute modifier applies to.
#[derive(Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum AttributeModifierSlot {
    /// Applies in any equipment slot.
    Any,
    /// Applies only when held in the main hand.
    MainHand,
    /// Applies only when held in the offhand.
    OffHand,
    /// Applies when held in either hand.
    Hand,
    /// Applies when worn on the feet.
    Feet,
    /// Applies when worn on the legs.
    Legs,
    /// Applies when worn on the chest.
    Chest,
    /// Applies when worn on the head.
    Head,
    /// Applies when wearing any piece of armor.
    Armor,
    /// Applies when worn as a body armor piece (e.g. on horses or wolves).
    Body,
    /// Applies when placed in a saddle slot.
    Saddle,
}

impl AttributeModifierSlot {
    /// Converts this slot variant into a `TokenStream` for use in generated code.
    pub fn to_tokens(&self) -> TokenStream {
        match self {
            Self::Any => quote! { AttributeModifierSlot::Any },
            Self::MainHand => quote! { AttributeModifierSlot::MainHand },
            Self::OffHand => quote! { AttributeModifierSlot::OffHand },
            Self::Hand => quote! { AttributeModifierSlot::Hand },
            Self::Feet => quote! { AttributeModifierSlot::Feet },
            Self::Legs => quote! { AttributeModifierSlot::Legs },
            Self::Chest => quote! { AttributeModifierSlot::Chest },
            Self::Head => quote! { AttributeModifierSlot::Head },
            Self::Armor => quote! { AttributeModifierSlot::Armor },
            Self::Body => quote! { AttributeModifierSlot::Body },
            Self::Saddle => quote! { AttributeModifierSlot::Saddle },
        }
    }
}

/// Generates the `TokenStream` for the `Enchantment` struct, `AttributeModifierSlot` enum,
/// and `from_name`/`from_id` lookup methods.
pub fn build() -> TokenStream {
    let enchantments: BTreeMap<String, Enchantment> =
        serde_json::from_str(&fs::read_to_string("../../assets/enchantments.json").unwrap())
            .expect("Failed to parse enchantments.json");

    let mut variants = TokenStream::new();
    let mut all_variants = TokenStream::new();
    let mut name_to_type = TokenStream::new();
    let mut id_to_type = TokenStream::new();

    for (name, enchantment) in enchantments {
        let id = enchantment.id;
        let raw_name = name.strip_prefix("minecraft:").unwrap();
        let format_name = format_ident!("{}", raw_name.to_shouty_snake_case());
        all_variants.extend(quote! { &Self::#format_name, });
        let anvil_cost = enchantment.anvil_cost;
        let supported_items = format_ident!(
            "{}",
            enchantment
                .supported_items
                .strip_prefix("#")
                .unwrap()
                .replace([':', '/'], "_")
                .to_uppercase()
        );
        let max_level = enchantment.max_level;
        let weight = enchantment.weight;
        let min_cost_base = enchantment.min_cost.base;
        let min_cost_per_level = enchantment.min_cost.per_level_above_first;
        let max_cost_base = enchantment.max_cost.base;
        let max_cost_per_level = enchantment.max_cost.per_level_above_first;

        let slots = enchantment.slots;
        let slots = slots.iter().map(AttributeModifierSlot::to_tokens);
        let Translate {
            translate,
            bedrock_translate: _,
            with: _,
        } = &*enchantment.description.0.content
        else {
            panic!()
        };
        let translate = translate.to_string();

        if let Some(exclusive_set) = &enchantment.exclusive_set {
            let exclusive_set = format_ident!(
                "{}",
                exclusive_set
                    .strip_prefix("#")
                    .unwrap()
                    .replace([':', '/'], "_")
                    .to_uppercase()
            );
            variants.extend([quote! {
                pub const #format_name: Self = Self {
                    id: #id,
                    name: #name,
                    registry_key: #raw_name,
                    description: #translate,
                    anvil_cost: #anvil_cost,
                    supported_items: &ItemTag::#supported_items,
                    exclusive_set: Some(&EnchantmentTag::#exclusive_set),
                    max_level: #max_level,
                    slots: &[#(#slots),*],
                    weight: #weight,
                    min_cost: Cost {
                        base: #min_cost_base,
                        per_level_above_first: #min_cost_per_level,
                    },
                    max_cost: Cost {
                        base: #max_cost_base,
                        per_level_above_first: #max_cost_per_level,
                    },
                };
            }]);
        } else {
            variants.extend([quote! {
                pub const #format_name: Self = Self {
                    id: #id,
                    name: #name,
                    description: #translate,
                    registry_key: #raw_name,
                    anvil_cost: #anvil_cost,
                    supported_items: &ItemTag::#supported_items,
                    exclusive_set: None,
                    max_level: #max_level,
                    slots: &[#(#slots),*],
                    weight: #weight,
                    min_cost: Cost {
                        base: #min_cost_base,
                        per_level_above_first: #min_cost_per_level,
                    },
                    max_cost: Cost {
                        base: #max_cost_base,
                        per_level_above_first: #max_cost_per_level,
                    },
                };
            }]);
        }

        name_to_type.extend(quote! { #name | #raw_name => Some(&Self::#format_name), });
        id_to_type.extend(quote! { #id => Some(&Self::#format_name), });
    }

    quote! {
        use crate::item::Item;
        use crate::tag::Enchantment as EnchantmentTag;
        use crate::tag::Item as ItemTag;
        use crate::tag::{RegistryKey, Tag, Taggable};
        use crate::data_component_impl::EnchantmentsImpl;
        use pumpkin_util::text::TextComponent;
        use pumpkin_util::text::color::NamedColor;
        use std::hash::{Hash, Hasher};
        use std::slice::Iter;

        pub struct Enchantment {
            pub id: u8,
            pub name: &'static str,
            pub registry_key: &'static str,
            pub description: &'static str, // TODO use TextComponent
            pub anvil_cost: u32,
            pub supported_items: &'static Tag,
            pub exclusive_set: Option<&'static Tag>,
            pub max_level: i32,
            pub slots: &'static [AttributeModifierSlot],
            pub weight: i32,
            pub min_cost: Cost,
            pub max_cost: Cost,
            // TODO: add more
        }

        #[derive(Clone, Copy, Debug)]
        pub struct Cost {
            pub base: i32,
            pub per_level_above_first: i32,
        }

        impl Cost {
            pub fn calculate(&self, level: i32) -> i32 {
                self.base + self.per_level_above_first * (level - 1)
            }
        }
        impl Taggable for Enchantment {
            #[inline]
            fn tag_key() -> RegistryKey {
                RegistryKey::Enchantment
            }
            #[inline]
            fn registry_key(&self) -> &str {
                self.registry_key
            }
            #[inline]
            fn registry_id(&self) -> u16 {
                self.id as u16
            }
        }
        impl PartialEq for Enchantment {
            fn eq(&self, other: &Self) -> bool {
                self.id == other.id
            }
        }
        impl Eq for Enchantment {}
        impl Hash for Enchantment {
            fn hash<H: Hasher>(&self, state: &mut H) {
                self.id.hash(state);
            }
        }
        #[derive(Debug, Clone, Hash, PartialEq)]
        pub enum AttributeModifierSlot {
            Any,
            MainHand,
            OffHand,
            Hand,
            Feet,
            Legs,
            Chest,
            Head,
            Armor,
            Body,
            Saddle,
        }

        impl Enchantment {
            pub const ALL: &'static [&'static Self] = &[#all_variants];

            pub fn all() -> Iter<'static, &'static Self> {
                Self::ALL.iter()
            }

            #variants

            pub fn from_name(name: &str) -> Option<&'static Self> {
                match name {
                    #name_to_type
                    _ => None
                }
            }
            pub fn from_id(id: u8) -> Option<&'static Self> {
                match id {
                    #id_to_type
                    _ => None
                }
            }

            pub fn can_enchant(&self, item: &'static Item) -> bool {
                self.supported_items.1.contains(&item.id)
            }
            pub fn are_compatible(&self, other: &'static Enchantment) -> bool {
                if self == other {
                    return false;
                }
                if let Some(tag) = self.exclusive_set && tag.1.contains(&(other.id as u16)) {
                    return false;
                }
                if let Some(tag) = other.exclusive_set && tag.1.contains(&(self.id as u16)) {
                    return false;
                }
                true
            }
            pub fn is_enchantment_compatible(&self, other: &EnchantmentsImpl) -> bool {
                for (i, _) in other.enchantment.iter() {
                    if !self.are_compatible(i) {
                        return false;
                    }
                }
                true
            }
            #[allow(deprecated)]
            pub fn get_fullname(&self, level: i32) -> TextComponent {
                let mut ret = TextComponent::translate(self.description, []).color_named(
                    if self.has_tag(&EnchantmentTag::MINECRAFT_CURSE) {
                        NamedColor::Red
                    } else {
                        NamedColor::Gray
                    }
                );
                if level != 1 || self.max_level != 1 {
                    ret = ret.add_text(" ")
                        .add_child(TextComponent::translate("enchantment.level.".to_string() + &level.to_string(), []));
                }
                ret
            }
        }
    }
}
