#[cfg(feature = "codegen")]
use proc_macro2::TokenStream;
#[cfg(feature = "codegen")]
use quote::{ToTokens, quote};
use serde::Deserialize;

use crate::random::RandomImpl;

/// Represents a loot table used to generate items or rewards.
#[derive(Clone, Copy, Debug)]
pub struct LootTable {
    /// The type of loot table (e.g., `Entity`, `Block`, `Chest`).
    pub r#type: LootTableType,
    /// Optional random sequence identifier for deterministic rolls.
    pub random_sequence: Option<&'static str>,
    /// Optional array of loot pools that define item generation.
    pub pools: Option<&'static [LootPool]>,
}

/// Defines a loot pool containing entries, rolls, and conditions.
#[derive(Clone, Debug)]
pub struct LootPool {
    /// Entries contained in this loot pool.
    pub entries: &'static [LootPoolEntry],
    /// Number of rolls, specified using a number provider.
    pub rolls: LootNumberProviderTypes,
    /// Additional bonus rolls to apply.
    pub bonus_rolls: LootNumberProviderTypes,
    /// Optional conditions that must be met for this pool to be applied.
    pub conditions: Option<&'static [LootCondition]>,
    /// Optional functions applied to each entry in the pool.
    pub functions: Option<&'static [LootFunction]>,
}

/// Represents an individual item entry in a loot pool.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ItemEntry {
    /// Name of the item.
    pub name: &'static str,
}

/// Represents an alternative loot entry, which chooses one of its children.
#[derive(Clone, Debug)]
pub struct AlternativeEntry {
    /// Child entries for this alternative.
    pub children: &'static [LootPoolEntry],
}

#[derive(Clone, Debug)]
pub struct TagEntry {
    pub name: &'static str,
    pub expand: bool,
}

#[derive(Clone, Debug)]
pub struct SequenceEntry {
    pub children: &'static [LootPoolEntry],
}

#[derive(Clone, Debug)]
pub struct GroupEntry {
    pub children: &'static [LootPoolEntry],
}

#[derive(Clone, Debug)]
pub struct LootTableEntry {
    pub value: &'static str,
}

#[derive(Clone, Debug)]
pub struct DynamicEntry {
    pub name: &'static str,
}

#[derive(Clone, Debug)]
pub enum LootPoolEntryTypes {
    Empty,
    Item(ItemEntry),
    LootTable(LootTableEntry),
    Dynamic(DynamicEntry),
    Tag(TagEntry),
    Alternatives(AlternativeEntry),
    Sequence(SequenceEntry),
    Group(GroupEntry),
}

/// Conditions that can modify whether loot entries are applied.
#[derive(Clone, PartialEq, Debug)]
pub enum LootCondition {
    /// Inverts the result of another condition.
    Inverted(&'static Self),
    /// Passes if any of the given conditions are met.
    AnyOf(&'static [Self]),
    /// Passes only if all the given conditions are met.
    AllOf(&'static [Self]),
    /// Passes based on a random chance.
    RandomChance { chance: f32 },
    /// Passes based on a random chance modified by item enchantments.
    RandomChanceWithEnchantedBonus {
        enchantment: &'static str,
        chances: Option<&'static [f32]>,
    },
    /// Checks properties of the entity (e.g. type, flags, equipment).
    /// Mirrors vanilla `LootItemEntityPropertyCondition`.
    EntityProperties {
        /// The entity target to check: "this", "attacker", or "`direct_attacker`".
        entity: &'static str,
        /// Expected entity type (e.g. "minecraft:cow").
        expected_type: Option<&'static str>,
        /// When `Some`, the entity must be on fire for the condition to pass.
        /// Mirrors vanilla `EntityFlagsPredicate.isOnFire`.
        is_on_fire: Option<bool>,
        /// When `Some`, the entity's mainhand item must have an enchantment in this tag.
        /// Mirrors vanilla `EntityEquipmentPredicate.mainhand` with enchantment tag lookup.
        mainhand_enchantment_tag: Option<&'static str>,
    },
    /// Requires the entity to have been killed by a player.
    KilledByPlayer,
    /// Checks entity scores against specified criteria.
    EntityScores { entity: &'static str },
    /// Checks block state properties.
    BlockStateProperty {
        /// The block to check.
        block: &'static str,
        /// Key-value pairs of properties and their expected values.
        properties: &'static [(&'static str, &'static str)],
    },
    /// Requires a specific tool to match certain criteria.
    MatchTool {
        items: Option<&'static [&'static str]>,
    },
    /// Applies a bonus based on a table.
    TableBonus {
        enchantment: &'static str,
        chances: &'static [f32],
    },
    /// Survives explosion damage.
    SurvivesExplosion,
    /// Checks properties of the damage source.
    DamageSourceProperties {
        expected_source_type: Option<&'static str>,
        expected_direct_type: Option<&'static str>,
    },
    /// Checks the location of the entity.
    LocationCheck {
        offset_x: i32,
        offset_y: i32,
        offset_z: i32,
        expected_biome: Option<&'static str>,
    },
    /// Checks weather conditions.
    WeatherCheck {
        raining: Option<bool>,
        thundering: Option<bool>,
    },
    /// References to another condition or table.
    Reference { name: &'static str },
    /// Checks the in-game time.
    TimeCheck {
        range: (Option<f32>, Option<f32>),
        period: Option<u64>,
    },
    /// Checks against a specific value or range.
    ValueCheck {
        value: LootNumberProviderTypes,
        range: (Option<f32>, Option<f32>),
    },
    /// Checks if a specific enchantment is active.
    EnchantmentActiveCheck { active: bool },
}

/// Functions applied to loot entries.
#[derive(Clone, PartialEq, Debug)]
pub struct LootFunction {
    /// The type of function to apply.
    pub content: LootFunctionTypes,
    /// Optional conditions for the function.
    pub conditions: Option<&'static [LootCondition]>,
}

/// Specific types of loot functions.
#[derive(Clone, PartialEq, Debug)]
pub enum LootFunctionTypes {
    /// Sets the number of items in the stack, optionally adding to an existing count.
    SetCount {
        count: LootFunctionNumberProvider,
        add: bool,
    },
    /// Increases the count of items based on the item's enchantments.
    EnchantedCountIncrease {
        enchantment: &'static str,
        count: LootFunctionNumberProvider,
        limit: Option<f32>,
    },
    /// Smelts the item as if it passed through a furnace.
    FurnaceSmelt,
    /// Sets the potion type for potion items.
    SetPotion { id: &'static str },
    /// Sets the amplifier for ominous bottles.
    SetOminousBottleAmplifier,
    /// Limits the count of items to a minimum and/or maximum value.
    LimitCount { min: Option<f32>, max: Option<f32> },
    /// Applies a bonus based on an enchantment and a formula, optionally with extra parameters.
    ApplyBonus {
        enchantment: &'static str,
        formula: &'static str,
        parameters: Option<LootFunctionBonusParameter>,
    },
    /// Copies specified components from a source entity or item.
    CopyComponents {
        source: &'static str,
        include: &'static [&'static str],
    },
    /// Copies state or properties from a block.
    CopyState {
        block: &'static str,
        properties: &'static [&'static str],
    },
    /// Applies decay to items in case of an explosion.
    ExplosionDecay,
}

/// Numeric providers for loot function counts.
#[derive(Clone, PartialEq, Debug)]
pub enum LootFunctionNumberProvider {
    /// Constant value.
    Constant { value: f32 },
    /// Uniform random value between min and max.
    Uniform { min: f32, max: f32 },
    /// Binomial distribution.
    Binomial { n: f32, p: f32 },
}

/// Parameters for loot bonus functions.
#[derive(Clone, PartialEq, Debug)]
pub enum LootFunctionBonusParameter {
    /// Multiplier applied to bonus rolls.
    Multiplier { bonus_multiplier: i32 },
    /// Probability-based bonus application.
    Probability { extra: i32, probability: f32 },
}

/// Single entry in a loot pool with optional conditions and functions.
#[derive(Clone, Debug)]
pub struct LootPoolEntry {
    /// The type of entry.
    pub content: LootPoolEntryTypes,
    /// Relative probability weight; higher values are more likely.
    pub weight: i32,
    /// Quality of the entry, used to modify weight based on luck.
    pub quality: i32,
    /// Optional conditions for this entry.
    pub conditions: Option<&'static [LootCondition]>,
    /// Optional functions for this entry.
    pub functions: Option<&'static [LootFunction]>,
}

/// Categories of loot tables.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum LootTableType {
    /// Empty loot table.
    Empty,
    /// Entity loot table.
    Entity,
    /// Block loot table.
    Block,
    /// Chest loot table.
    Chest,
}

/// Represents structured number providers used in loot rolls.
#[derive(Deserialize, PartialEq, Clone, Copy, Debug)]
#[serde(tag = "type")]
pub enum LootNumberProviderTypesProvider {
    /// Uniformly distributes a random number between `min` and `max` (inclusive).
    #[serde(rename = "minecraft:uniform")]
    Uniform(UniformLootNumberProvider),
}

#[cfg(feature = "codegen")]
impl ToTokens for LootNumberProviderTypesProvider {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::Uniform(uniform) => {
                tokens.extend(quote! {
                    LootNumberProviderTypesProvider::Uniform(#uniform)
                });
            }
        }
    }
}

/// Uniform numeric provider for loot rolls.
#[derive(Deserialize, PartialEq, Clone, Copy, Debug)]
pub struct UniformLootNumberProvider {
    /// Minimum value (inclusive).
    pub min: f32,
    /// Maximum value (inclusive).
    pub max: f32,
}

#[cfg(feature = "codegen")]
impl ToTokens for UniformLootNumberProvider {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let min_inclusive = self.min;
        let max_inclusive = self.max;

        tokens.extend(quote! {
            UniformLootNumberProvider { min: #min_inclusive, max: #max_inclusive }
        });
    }
}

impl UniformLootNumberProvider {
    /// Generates a random number within the uniform range.
    ///
    /// # Parameters
    /// - `random`: A random number generator implementing `RandomImpl`.
    ///
    /// # Returns
    /// A `f32` between `min` and `max`.
    pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
        random.next_f32().mul_add(self.max - self.min, self.min)
    }
}

/// Numeric providers for loot table rolls (either object or constant).
#[derive(Deserialize, PartialEq, Clone, Copy, Debug)]
#[serde(untagged)]
pub enum LootNumberProviderTypes {
    /// A structured provider object (e.g., uniform).
    Object(LootNumberProviderTypesProvider),
    /// Constant numeric value.
    Constant(f32),
}

#[cfg(feature = "codegen")]
impl ToTokens for LootNumberProviderTypes {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::Object(provider) => {
                tokens.extend(quote! {
                    LootNumberProviderTypes::Object(#provider)
                });
            }
            Self::Constant(i) => tokens.extend(quote! {
                LootNumberProviderTypes::Constant(#i)
            }),
        }
    }
}

impl LootNumberProviderTypes {
    /// Computes a numeric value from this provider.
    ///
    /// # Parameters
    /// - `random`: A random number generator implementing `RandomImpl`.
    ///
    /// # Returns
    /// A `f32` value as determined by the provider (constant or computed).
    pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
        match self {
            Self::Object(int_provider) => match int_provider {
                LootNumberProviderTypesProvider::Uniform(uniform) => uniform.get(random),
            },
            Self::Constant(i) => *i,
        }
    }
}
