use crate::random::RandomImpl;
#[cfg(feature = "codegen")]
use proc_macro2::{Span, TokenStream};
#[cfg(feature = "codegen")]
use quote::{ToTokens, quote};
use serde::Deserialize;
#[cfg(feature = "codegen")]
use syn::LitFloat;

/// Represents different types of number providers that generate floating-point values.
#[derive(Deserialize, Clone)]
#[serde(tag = "type")]
pub enum NormalFloatProvider {
    /// Always returns the same constant value.
    #[serde(rename = "minecraft:constant")]
    Constant(ConstantFloatProvider),
    /// Returns uniformly distributed random values within a range [min, max).
    #[serde(rename = "minecraft:uniform")]
    Uniform(UniformFloatProvider),
    /// Returns values from a normal (Gaussian) distribution, clamped to a specified range.
    #[serde(rename = "minecraft:clamped_normal")]
    ClampedNormal(ClampedNormalFloatProvider),
    /// Returns values from a trapezoidal distribution (flat plateau with linear ramps).
    #[serde(rename = "minecraft:trapezoid")]
    Trapezoid(TrapezoidFloatProvider),
}

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

/// A flexible float provider that can be either a constant value or a complex provider.
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum FloatProvider {
    /// A complex provider with configurable distribution.
    Object(NormalFloatProvider),
    /// A simple constant floating-point value.
    Constant(f32),
}

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

impl FloatProvider {
    #[must_use]
    /// The minimum possible value this provider can generate.
    ///
    /// # Returns
    /// The minimum value (inclusive) that can be generated by this provider.
    pub const fn get_min(&self) -> f32 {
        match self {
            Self::Object(inv_provider) => match inv_provider {
                NormalFloatProvider::Constant(constant) => constant.get_min(),
                NormalFloatProvider::Uniform(uniform) => uniform.get_min(),
                NormalFloatProvider::ClampedNormal(clamped_normal) => clamped_normal.get_min(),
                NormalFloatProvider::Trapezoid(trapezoid) => trapezoid.get_min(),
            },
            Self::Constant(i) => *i,
        }
    }

    /// Generates a random float value using the configured distribution.
    ///
    /// # Arguments
    /// - `random` – The random number generator to use.
    ///
    /// # Returns
    /// A random float value according to the provider's distribution strategy.
    pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
        match self {
            Self::Object(inv_provider) => match inv_provider {
                NormalFloatProvider::Constant(constant) => constant.get(random),
                NormalFloatProvider::Uniform(uniform) => uniform.get(random),
                NormalFloatProvider::ClampedNormal(clamped_normal) => clamped_normal.get(random),
                NormalFloatProvider::Trapezoid(trapezoid) => trapezoid.get(random),
            },
            Self::Constant(i) => *i,
        }
    }

    /// Returns the maximum possible value this provider can generate.
    ///
    /// # Returns
    /// The maximum value (inclusive or exclusive depending on a provider type) that can be generated.
    #[must_use]
    pub const fn get_max(&self) -> f32 {
        match self {
            Self::Object(inv_provider) => match inv_provider {
                NormalFloatProvider::Constant(constant) => constant.get_max(),
                NormalFloatProvider::Uniform(uniform) => uniform.get_max(),
                NormalFloatProvider::ClampedNormal(clamped_normal) => clamped_normal.get_max(),
                NormalFloatProvider::Trapezoid(trapezoid) => trapezoid.get_max(),
            },
            Self::Constant(i) => *i,
        }
    }
}

/// A float provider that always returns the same constant value.
#[derive(Deserialize, Clone)]
pub struct ConstantFloatProvider {
    /// The constant value that will always be returned.
    value: f32,
}

#[cfg(feature = "codegen")]
impl ToTokens for ConstantFloatProvider {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let value = LitFloat::new(&self.value.to_string(), Span::call_site());
        tokens.extend(quote! {
            ConstantFloatProvider { value: #value }
        });
    }
}

impl ConstantFloatProvider {
    /// Creates a new constant provider with the given value.
    ///
    /// # Arguments
    /// - `value` – The constant value to return.
    ///
    /// # Returns
    /// A new `ConstantFloatProvider` instance.
    #[must_use]
    pub const fn new(value: f32) -> Self {
        Self { value }
    }

    /// Returns the minimum value (same as the constant value).
    ///
    /// # Returns
    /// The constant value.
    #[must_use]
    pub const fn get_min(&self) -> f32 {
        self.value
    }

    /// Returns the constant value, ignoring the random number generator.
    ///
    /// # Arguments
    /// - `_random` – The random number generator (unused).
    ///
    /// # Returns
    /// The constant value.
    pub const fn get(&self, _random: &mut impl RandomImpl) -> f32 {
        self.value
    }

    /// Returns the maximum value (same as the constant value).
    ///
    /// # Returns
    /// The constant value.
    #[must_use]
    pub const fn get_max(&self) -> f32 {
        self.value
    }
}

/// A float provider that generates uniformly distributed random values.
#[derive(Deserialize, Clone)]
pub struct UniformFloatProvider {
    /// The minimum value (inclusive) that can be generated.
    min_inclusive: f32,
    /// The maximum value (exclusive) that can be generated.
    max_exclusive: f32,
}

#[cfg(feature = "codegen")]
impl ToTokens for UniformFloatProvider {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let min_inclusive = LitFloat::new(&self.min_inclusive.to_string(), Span::call_site());
        let max_exclusive = LitFloat::new(&self.max_exclusive.to_string(), Span::call_site());
        tokens.extend(quote! {
            UniformFloatProvider { min_inclusive: #min_inclusive, max_exclusive: #max_exclusive }
        });
    }
}

impl UniformFloatProvider {
    /// Creates a new uniform provider with the specified range.
    ///
    /// # Arguments
    /// - `min_inclusive` – The minimum value (inclusive).
    /// - `max_exclusive` – The maximum value (exclusive).
    ///
    /// # Returns
    /// A new `UniformFloatProvider` instance.
    #[must_use]
    pub const fn new(min_inclusive: f32, max_exclusive: f32) -> Self {
        Self {
            min_inclusive,
            max_exclusive,
        }
    }

    /// Returns the minimum inclusive value.
    ///
    /// # Returns
    /// The minimum value that can be generated (inclusive).
    #[must_use]
    pub const fn get_min(&self) -> f32 {
        self.min_inclusive
    }

    /// Generates a uniformly distributed random value in the configured range.
    ///
    /// # Arguments
    /// - `random` – The random number generator to use.
    ///
    /// # Returns
    /// A random float in the range [`min_inclusive`, `max_exclusive`].
    pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
        // NOTE: Use the random range in [min_inclusive, max_exclusive)
        let range = self.max_exclusive - self.min_inclusive;
        random.next_f32().mul_add(range, self.min_inclusive)
    }

    /// Returns the maximum exclusive value.
    ///
    /// # Returns
    /// The maximum value that can be generated (exclusive).
    #[must_use]
    pub const fn get_max(&self) -> f32 {
        self.max_exclusive
    }
}

/// A float provider that generates values from a normal (Gaussian) distribution.
#[derive(Deserialize, Clone)]
pub struct ClampedNormalFloatProvider {
    /// The mean (centre) of the normal distribution.
    mean: f32,
    /// The standard deviation of the normal distribution.
    deviation: f32,
    /// The minimum inclusive clamp value.
    min: f32,
    /// The maximum inclusive clamp value.
    max: f32,
}

#[cfg(feature = "codegen")]
impl ToTokens for ClampedNormalFloatProvider {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let mean = LitFloat::new(&self.mean.to_string(), Span::call_site());
        let deviation = LitFloat::new(&self.deviation.to_string(), Span::call_site());
        let min = LitFloat::new(&self.min.to_string(), Span::call_site());
        let max = LitFloat::new(&self.max.to_string(), Span::call_site());
        tokens.extend(quote! {
            ClampedNormalFloatProvider {
                mean: #mean,
                deviation: #deviation,
                min: #min,
                max: #max
            }
        });
    }
}

impl ClampedNormalFloatProvider {
    /// Creates a new clamped normal provider with the specified parameters.
    ///
    /// # Arguments
    /// - `mean` – The mean (centre) of the normal distribution.
    /// - `deviation` – The standard deviation of the normal distribution.
    /// - `min` – The minimum inclusive clamp value.
    /// - `max` – The maximum inclusive clamp value.
    ///
    /// # Returns
    /// A new `ClampedNormalFloatProvider` instance.
    #[must_use]
    pub const fn new(mean: f32, deviation: f32, min: f32, max: f32) -> Self {
        Self {
            mean,
            deviation,
            min,
            max,
        }
    }

    /// Returns the minimum inclusive clamp value.
    ///
    /// # Returns
    /// The minimum value that can be generated after clamping.
    #[must_use]
    pub const fn get_min(&self) -> f32 {
        self.min
    }

    /// Generates a random value from the clamped normal distribution.
    ///
    /// # Arguments
    /// - `random` – The random number generator to use.
    ///
    /// # Returns
    /// A random float from a normal distribution, clamped to [min, max].
    pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
        // NOTE: Generate normal distribution value
        let gaussian = random.next_gaussian() as f32;
        let value = gaussian.mul_add(self.deviation, self.mean);

        // NOTE: Clamp to min/max range
        value.clamp(self.min, self.max)
    }

    /// Returns the maximum inclusive clamp value.
    ///
    /// # Returns
    /// The maximum value that can be generated after clamping.
    #[must_use]
    pub const fn get_max(&self) -> f32 {
        self.max
    }
}

/// A float provider that generates values from a trapezoidal distribution.
#[derive(Deserialize, Clone)]
pub struct TrapezoidFloatProvider {
    /// The minimum value (inclusive) that can be generated.
    min: f32,
    /// The maximum value (inclusive) that can be generated.
    max: f32,
    /// The width of the flat plateau as a fraction of the total range (0.0 to 1.0).
    plateau: f32,
}

#[cfg(feature = "codegen")]
impl ToTokens for TrapezoidFloatProvider {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let min = LitFloat::new(&self.min.to_string(), Span::call_site());
        let max = LitFloat::new(&self.max.to_string(), Span::call_site());
        let plateau = LitFloat::new(&self.plateau.to_string(), Span::call_site());
        tokens.extend(quote! {
            TrapezoidFloatProvider {
                min: #min,
                max: #max,
                plateau: #plateau
            }
        });
    }
}

impl TrapezoidFloatProvider {
    /// Creates a new trapezoid provider with the specified parameters.
    ///
    /// # Arguments
    /// - `min` – The minimum value (inclusive).
    /// - `max` – The maximum value (inclusive).
    /// - `plateau` – The width of the flat plateau as a fraction of the total range (0.0 to 1.0).
    ///
    /// # Returns
    /// A new `TrapezoidFloatProvider` instance.
    #[must_use]
    pub const fn new(min: f32, max: f32, plateau: f32) -> Self {
        Self { min, max, plateau }
    }

    /// Returns the minimum inclusive value.
    ///
    /// # Returns
    /// The minimum value that can be generated.
    #[must_use]
    pub const fn get_min(&self) -> f32 {
        self.min
    }

    /// Generates a random value from the trapezoidal distribution.
    ///
    /// # Arguments
    /// - `random` – The random number generator to use.
    ///
    /// # Returns
    /// A random float from the trapezoidal distribution in the range [min, max].
    pub fn get(&self, random: &mut impl RandomImpl) -> f32 {
        // NOTE: Trapezoid distribution: flat plateau in the middle, linear ramps on the sides.
        let range = self.max - self.min;
        let plateau_range = range * self.plateau;
        let ramp_range = (range - plateau_range) * 0.5;

        let random_value = random.next_f32();

        if random_value < self.plateau.mul_add(-0.5, 0.5) {
            // NOTE: Left ramp: quadratic distribution biased toward plateau
            let scaled = random_value / self.plateau.mul_add(-0.5, 0.5);
            let sqrt_scaled = scaled.sqrt();
            self.min + ramp_range * sqrt_scaled
        } else if random_value > self.plateau.mul_add(0.5, 0.5) {
            // NOTE: Right ramp: quadratic distribution biased toward plateau
            let scaled =
                (random_value - self.plateau.mul_add(0.5, 0.5)) / self.plateau.mul_add(-0.5, 0.5);
            let sqrt_scaled = (1.0 - scaled).sqrt();
            self.max - ramp_range * sqrt_scaled
        } else {
            // NOTE: Plateau: uniform distribution
            let plateau_pos = (random_value - self.plateau.mul_add(-0.5, 0.5)) / self.plateau;
            self.min + ramp_range + plateau_pos * plateau_range
        }
    }

    /// Returns the maximum inclusive value.
    ///
    /// # Returns
    /// The maximum value that can be generated.
    #[must_use]
    pub const fn get_max(&self) -> f32 {
        self.max
    }
}

/// Tests for the float provider implementations.
#[cfg(test)]
mod tests {
    use super::*;
    use crate::random::{RandomGenerator, get_seed};

    #[test]
    fn constant_float_provider() {
        let mut random = RandomGenerator::Xoroshiro(
            crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
        );
        let provider = ConstantFloatProvider::new(5.5);

        assert_eq!(provider.get_min(), 5.5);
        assert_eq!(provider.get_max(), 5.5);
        assert_eq!(provider.get(&mut random), 5.5);
        assert_eq!(provider.get(&mut random), 5.5); // Should always return the same value
    }

    #[test]
    fn uniform_float_provider() {
        let mut random = RandomGenerator::Xoroshiro(
            crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
        );
        let provider = UniformFloatProvider::new(1.0, 5.0);

        assert_eq!(provider.get_min(), 1.0);
        assert_eq!(provider.get_max(), 5.0);

        // Test that values are within range
        for _ in 0..100 {
            let value = provider.get(&mut random);
            assert!(
                (1.0..5.0).contains(&value),
                "Value {value} is outside range [1.0, 5.0)"
            );
        }
    }

    #[test]
    fn clamped_normal_float_provider() {
        let mut random = RandomGenerator::Xoroshiro(
            crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
        );
        let provider = ClampedNormalFloatProvider::new(3.0, 1.0, 1.0, 5.0);

        assert_eq!(provider.get_min(), 1.0);
        assert_eq!(provider.get_max(), 5.0);

        // Test that values are within range
        for _ in 0..100 {
            let value = provider.get(&mut random);
            assert!(
                (1.0..=5.0).contains(&value),
                "Value {value} is outside range [1.0, 5.0]"
            );
        }
    }

    #[test]
    fn trapezoid_float_provider() {
        let mut random = RandomGenerator::Xoroshiro(
            crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
        );
        let provider = TrapezoidFloatProvider::new(0.0, 10.0, 0.5);

        assert_eq!(provider.get_min(), 0.0);
        assert_eq!(provider.get_max(), 10.0);

        // Test that values are within range
        for _ in 0..100 {
            let value = provider.get(&mut random);
            assert!(
                (0.0..=10.0).contains(&value),
                "Value {value} is outside range [0.0, 10.0]"
            );
        }
    }

    #[test]
    fn float_provider_enum_constant() {
        let mut random = RandomGenerator::Xoroshiro(
            crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
        );
        let provider = FloatProvider::Constant(7.5);

        assert_eq!(provider.get_min(), 7.5);
        assert_eq!(provider.get_max(), 7.5);
        assert_eq!(provider.get(&mut random), 7.5);
    }

    #[test]
    fn float_provider_enum_object() {
        let mut random = RandomGenerator::Xoroshiro(
            crate::random::xoroshiro128::Xoroshiro::from_seed(get_seed()),
        );
        let uniform = UniformFloatProvider::new(2.0, 8.0);
        let provider = FloatProvider::Object(NormalFloatProvider::Uniform(uniform));

        assert_eq!(provider.get_min(), 2.0);
        assert_eq!(provider.get_max(), 8.0);

        let value = provider.get(&mut random);
        assert!(
            (2.0..8.0).contains(&value),
            "Value {value} is outside range [2.0, 8.0)"
        );
    }
}
