use crate::{
    math::lerp3,
    random::{RandomDeriverImpl, RandomImpl},
};

use super::GRADIENTS;

/// A 3D Perlin noise sampler implementation.
///
/// Perlin noise is a gradient noise function commonly used for procedural generation
/// of natural-looking textures, terrain, and other organic patterns. This implementation
/// provides 3D noise with optional vertical scaling.
///
/// The sampler uses a permutation table to hash coordinates and interpolates between
/// gradient vectors at integer lattice points to produce smooth, continuous noise.
#[derive(Clone)]
pub struct PerlinNoiseSampler {
    /// Permutation table for hashing coordinates (size 256, duplicated for easy wrapping).
    permutation: [u8; 256],
    /// X-coordinate origin offset (randomized to avoid symmetry).
    x_origin: f64,
    /// Y-coordinate origin offset (randomized to avoid symmetry).
    y_origin: f64,
    /// Z-coordinate origin offset (randomized to avoid symmetry).
    z_origin: f64,
}

impl PerlinNoiseSampler {
    /// Creates a new Perlin noise sampler with randomized origin and permutation table.
    ///
    /// # Arguments
    /// - `random` – The random number generator to use for initialization.
    ///
    /// # Returns
    /// A new `PerlinNoiseSampler` instance.
    pub fn new(random: &mut impl RandomImpl) -> Self {
        let x_origin = random.next_f64() * 256.0;
        let y_origin = random.next_f64() * 256.0;
        let z_origin = random.next_f64() * 256.0;

        let mut permutation = [0u8; 256];

        permutation
            .iter_mut()
            .enumerate()
            .for_each(|(i, x)| *x = i as u8);

        for i in 0..256 {
            let j = random.next_bounded_i32((256 - i) as i32) as usize;
            permutation.swap(i, i + j);
        }

        Self {
            permutation,
            x_origin,
            y_origin,
            z_origin,
        }
    }

    /// Samples noise at the given coordinates with no vertical scaling.
    ///
    /// This is a convenience method that calls `sample_no_fade` with a zero vertical
    /// scale and maximum Y.
    ///
    /// # Arguments
    /// - `x` – The X coordinate.
    /// - `y` – The Y coordinate.
    /// - `z` – The Z coordinate.
    ///
    /// # Returns
    /// The noise value at the given coordinates, typically in the range [-1, 1].
    #[inline]
    #[must_use]
    pub fn sample_flat_y(&self, x: f64, y: f64, z: f64) -> f64 {
        self.sample_no_fade(x, y, z, 0.0, 0.0)
    }

    /// Samples noise with optional vertical scaling and clamping.
    ///
    /// This method applies the origin offsets, computes the integer lattice points,
    /// and interpolates between gradient values.
    ///
    /// # Arguments
    /// - `x` – The X coordinate.
    /// - `y` – The Y coordinate.
    /// - `z` – The Z coordinate.
    /// - `y_scale` – The vertical scale factor (if 0, vertical noise is not scaled).
    /// - `y_max` – The maximum Y value to clamp to.
    ///
    /// # Returns
    /// The noise value at the given coordinates, typically in the range [-1, 1].
    #[must_use]
    pub fn sample_no_fade(&self, x: f64, y: f64, z: f64, y_scale: f64, y_max: f64) -> f64 {
        let true_x = x + self.x_origin;
        let true_y = y + self.y_origin;
        let true_z = z + self.z_origin;

        let x_floor = true_x.floor();
        let y_floor = true_y.floor();
        let z_floor = true_z.floor();

        let x_dec = true_x - x_floor;
        let y_dec = true_y - y_floor;
        let z_dec = true_z - z_floor;

        let y_noise = if y_scale == 0.0 {
            0.0
        } else {
            let raw_y_dec = if y_max >= 0.0 && y_max < y_dec {
                y_max
            } else {
                y_dec
            };
            (raw_y_dec / y_scale + 1E-7).floor() * y_scale
        };

        self.sample(
            x_floor as i32,
            y_floor as i32,
            z_floor as i32,
            x_dec,
            y_dec - y_noise,
            z_dec,
            y_dec,
        )
    }

    /// Computes the dot product of a gradient vector with the given coordinates.
    ///
    /// # Arguments
    /// - `hash` – The hash value used to select a gradient (lower 4 bits).
    /// - `x` – The X coordinate.
    /// - `y` – The Y coordinate.
    /// - `z` – The Z coordinate.
    ///
    /// # Returns
    /// The dot product of the selected gradient with (x, y, z).
    #[inline]
    const fn grad(hash: i32, x: f64, y: f64, z: f64) -> f64 {
        GRADIENTS[(hash & 15) as usize].dot(x, y, z)
    }

    /// Applies the Perlin fade curve to an interpolant.
    ///
    /// The fade function is 6t⁵ - 15t⁴ + 10t³, which has zero-first and second derivatives
    /// at t=0 and t=1, ensuring smooth interpolation.
    ///
    /// # Arguments
    /// - `value` – The interpolant (typically in [0, 1]).
    ///
    /// # Returns
    /// The faded value.
    #[inline]
    const fn perlin_fade(value: f64) -> f64 {
        value * value * value * (value * (value * 6.0 - 15.0) + 10.0)
    }

    /// Maps an integer coordinate through the permutation table.
    ///
    /// # Arguments
    /// - `input` – The input coordinate.
    ///
    /// # Returns
    /// A hashed value in the range [0, 255].
    #[inline]
    fn map(&self, input: i32) -> i32 {
        i32::from(self.permutation[(input & 0xFF) as usize])
    }

    /// Core sampling function that computes noise at a lattice point.
    ///
    /// This method computes the eight corner gradients of the unit cube surrounding
    /// the sample point and interpolates between them using the fade curves.
    ///
    /// # Arguments
    /// - `x` – The integer X coordinate of the lattice cube corner.
    /// - `y` – The integer Y coordinate of the lattice cube corner.
    /// - `z` – The integer Z coordinate of the lattice cube corner.
    /// - `local_x` – The fractional X offset within the cube [0, 1).
    /// - `local_y` – The fractional Y offset within the cube [0, 1).
    /// - `local_z` – The fractional Z offset within the cube [0, 1).
    /// - `fade_local_y` – The Y value to use for fading (may differ from `local_y` for vertical scaling).
    ///
    /// # Returns
    /// The interpolated noise value.
    #[expect(clippy::too_many_arguments)]
    #[expect(clippy::many_single_char_names)]
    fn sample(
        &self,
        x: i32,
        y: i32,
        z: i32,
        local_x: f64,
        local_y: f64,
        local_z: f64,
        fade_local_y: f64,
    ) -> f64 {
        let i = self.map(x);
        let j = self.map(x + 1);
        let k = self.map(i + y);
        let l = self.map(i + y + 1);

        let m = self.map(j + y);
        let n = self.map(j + y + 1);

        let d = Self::grad(self.map(k + z), local_x, local_y, local_z);
        let e = Self::grad(self.map(m + z), local_x - 1.0, local_y, local_z);
        let f = Self::grad(self.map(l + z), local_x, local_y - 1.0, local_z);
        let g = Self::grad(self.map(n + z), local_x - 1.0, local_y - 1.0, local_z);
        let h = Self::grad(self.map(k + z + 1), local_x, local_y, local_z - 1.0);
        let o = Self::grad(self.map(m + z + 1), local_x - 1.0, local_y, local_z - 1.0);
        let p = Self::grad(self.map(l + z + 1), local_x, local_y - 1.0, local_z - 1.0);
        let q = Self::grad(
            self.map(n + z + 1),
            local_x - 1.0,
            local_y - 1.0,
            local_z - 1.0,
        );
        let r = Self::perlin_fade(local_x);
        let s = Self::perlin_fade(fade_local_y);
        let t = Self::perlin_fade(local_z);

        lerp3(r, s, t, d, e, f, g, h, o, p, q)
    }
}

/// Data for a single octave in an octave Perlin noise sampler.
pub struct SamplerData {
    /// The Perlin noise sampler for this octave.
    pub sampler: PerlinNoiseSampler,
    /// The amplitude multiplier for this octave.
    pub amplitude: f64,
    /// The persistence factor (amplitude scaling between octaves).
    pub persistence: f64,
    /// The lacunarity factor (frequency scaling between octaves).
    pub lacunarity: f64,
}

/// A multi-octave Perlin noise sampler that combines multiple noise layers.
///
/// Octave noise, also known as fractal noise, combines multiple octaves of Perlin noise
/// with different frequencies and amplitudes to create more complex, natural-looking patterns.
/// Each octave adds finer detail to the overall noise.
pub struct OctavePerlinNoiseSampler {
    /// The list of samplers for each octave, with their associated parameters.
    pub samplers: Box<[SamplerData]>,
    /// The maximum possible absolute value this sampler can return.
    max_value: f64,
}

impl OctavePerlinNoiseSampler {
    #[must_use]
    /// Returns the maximum possible absolute value this sampler can return.
    ///
    /// This is useful for normalizing the output to a specific range.
    ///
    /// # Returns
    /// The maximum absolute value.
    pub const fn max_value(&self) -> f64 {
        self.max_value
    }

    /// Calculates the total amplitude for a given scale and set of persistences and amplitudes.
    ///
    /// # Arguments
    /// - `scale` – The overall scale factor.
    /// - `persistences` – The persistence values for each octave.
    /// - `amplitudes` – The amplitude values for each octave.
    ///
    /// # Returns
    /// The sum of scaled amplitudes.
    fn get_total_amplitude_generic(scale: f64, persistences: &[f64], amplitudes: &[f64]) -> f64 {
        amplitudes
            .iter()
            .zip(persistences)
            .map(|(amplitude, persistence)| {
                if *amplitude == 0.0 {
                    0.0
                } else {
                    scale * *amplitude * *persistence
                }
            })
            .sum()
    }

    /// Maintains precision by wrapping large values to avoid floating-point artifacts.
    ///
    /// This method subtracts multiples of 2²⁵ to keep values in a manageable range
    /// while preserving the noise pattern.
    ///
    /// # Arguments
    /// - `value` – The value to wrap.
    ///
    /// # Returns
    /// The wrapped value.
    #[inline]
    #[must_use]
    pub const fn maintain_precision(value: f64) -> f64 {
        value - (value / 3.355_443_2E7 + 0.5).floor() * 3.355_443_2E7
    }

    /// Calculates the starting octave and amplitude array from a list of octaves.
    ///
    /// This method sorts the octaves and creates an amplitude array where indices
    /// corresponding to present octaves have amplitude 1.0.
    ///
    /// # Arguments
    /// - `octaves` – The list of octave indices.
    ///
    /// # Returns
    /// A tuple containing:
    /// - The negative of the smallest octave (starting offset).
    /// - A vector of amplitudes for each octave in the range.
    #[must_use]
    pub fn calculate_amplitudes(octaves: &[i32]) -> (i32, Vec<f64>) {
        let mut octaves = Vec::from_iter(octaves);
        octaves.sort();

        let (Some(&&first), Some(&&last)) = (octaves.first(), octaves.last()) else {
            return (0, Vec::new());
        };
        let i = -first;
        let j = last;
        let k = i + j + 1;

        let mut double_list = vec![0.0; k as usize];

        for l in octaves {
            double_list[(l + i) as usize] = 1.0;
        }

        (-i, double_list)
    }

    /// Creates a new octave Perlin noise sampler.
    ///
    /// # Arguments
    /// - `random` – The random number generator to use.
    /// - `first_octave` – The index of the first octave (can be negative).
    /// - `amplitudes` – The amplitude for each octave (starting from `first_octave`).
    /// - `legacy` – Whether to use legacy initialization (compatible with older Minecraft versions).
    ///
    /// # Returns
    /// A new `OctavePerlinNoiseSampler` instance.
    pub fn new(
        random: &mut impl RandomImpl,
        first_octave: i32,
        amplitudes: &[f64],
        legacy: bool,
    ) -> Self {
        let i = amplitudes.len();
        let j = -first_octave;

        let mut samplers: Box<[Option<PerlinNoiseSampler>]> = vec![None; i].into();

        if legacy {
            let sampler = PerlinNoiseSampler::new(random);
            if j >= 0 && j < i as i32 {
                let d = amplitudes[j as usize];
                if d != 0.0 {
                    samplers[j as usize] = Some(sampler);
                }
            }

            for kx in (0..j as usize).rev() {
                if kx < i {
                    let e = amplitudes[kx];
                    if e == 0.0 {
                        random.skip(262);
                    } else {
                        samplers[kx] = Some(PerlinNoiseSampler::new(random));
                    }
                } else {
                    random.skip(262);
                }
            }
        } else {
            let splitter = random.next_splitter();
            for k in 0..i {
                if amplitudes[k] != 0.0 {
                    let l = first_octave + k as i32;
                    samplers[k] = Some(PerlinNoiseSampler::new(
                        &mut splitter.split_string(&format!("octave_{l}")),
                    ));
                }
            }
        }

        let mut persistence = 2f64.powi(i as i32 - 1) / (2f64.powi(i as i32) - 1.0);
        let mut lacunarity = 2f64.powi(-j);

        let persistences: Vec<f64> = (0..amplitudes.len())
            .map(|_| {
                let result = persistence;
                persistence /= 2.0;
                result
            })
            .collect();
        let lacunarities = (0..amplitudes.len()).map(|_| {
            let result = lacunarity;
            lacunarity *= 2.0;
            result
        });

        let max_value = Self::get_total_amplitude_generic(2.0, &persistences, amplitudes);

        let samplers = samplers
            .into_iter()
            .zip(amplitudes)
            .zip(persistences)
            .zip(lacunarities)
            .filter_map(|(((sampler, amplitude), persistence), lacunarity)| {
                sampler.map(|sampler| SamplerData {
                    sampler,
                    amplitude: *amplitude,
                    persistence,
                    lacunarity,
                })
            })
            .collect();

        Self {
            samplers,
            max_value,
        }
    }

    /// Calculates the total amplitude for a given scale.
    ///
    /// # Arguments
    /// - `scale` – The scale factor to apply.
    ///
    /// # Returns
    /// The sum of amplitudes times scale times persistence for all octaves.
    #[inline]
    #[must_use]
    pub fn get_total_amplitude(&self, scale: f64) -> f64 {
        self.samplers
            .iter()
            .map(|data| data.amplitude * scale * data.persistence)
            .sum()
    }

    /// Samples the noise at the given coordinates.
    ///
    /// This method combines all octaves by sampling each at its respective frequency
    /// and summing the results with appropriate amplitudes.
    ///
    /// # Arguments
    /// - `x` – The X coordinate.
    /// - `y` – The Y coordinate.
    /// - `z` – The Z coordinate.
    ///
    /// # Returns
    /// The combined noise value from all octaves.
    #[inline]
    #[must_use]
    pub fn sample(&self, x: f64, y: f64, z: f64) -> f64 {
        self.samplers
            .iter()
            .map(|data| {
                let mapped_x = Self::maintain_precision(x * data.lacunarity);
                let mapped_y = Self::maintain_precision(y * data.lacunarity);
                let mapped_z = Self::maintain_precision(z * data.lacunarity);

                let sample = data
                    .sampler
                    .sample_no_fade(mapped_x, mapped_y, mapped_z, 0.0, 0.0);

                data.amplitude * sample * data.persistence
            })
            .sum()
    }
}

/// Tests for the perlin noise implementations.
#[cfg(test)]
mod tests {
    use crate::{
        assert_eq_delta,
        noise::perlin::{OctavePerlinNoiseSampler, PerlinNoiseSampler},
        random::{RandomImpl, legacy_rand::LegacyRand, xoroshiro128::Xoroshiro},
        read_data_from_file,
    };

    #[test]
    fn create_xoroshiro() {
        let mut rand = Xoroshiro::from_seed(513513513);
        assert_eq!(rand.next_i32(), 404174895);

        let (start, amplitudes) = OctavePerlinNoiseSampler::calculate_amplitudes(&[1, 2, 3]);
        assert_eq!(start, 1);
        assert_eq!(amplitudes, [1.0, 1.0, 1.0]);

        let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, &amplitudes, false);

        let first = sampler.samplers.first().unwrap();
        assert_eq!(first.persistence, 0.5714285714285714);
        assert_eq!(first.lacunarity, 2.0);
        assert_eq!(sampler.max_value, 2.0);

        let coords = [
            (210.19539348148294, 203.08258445596215, 45.29925114984684),
            (24.841250686920773, 181.62678157390076, 69.49871248131629),
            (21.65886467061867, 97.80131502331685, 225.9273676334467),
        ];

        for (data, (x, y, z)) in sampler.samplers.iter().zip(coords) {
            assert_eq!(data.sampler.x_origin, x);
            assert_eq!(data.sampler.y_origin, y);
            assert_eq!(data.sampler.z_origin, z);
        }
    }

    #[test]
    fn create_legacy() {
        let mut rand = LegacyRand::from_seed(513513513);
        assert_eq!(rand.next_i32(), -1302745855);

        let (start, amplitudes) = OctavePerlinNoiseSampler::calculate_amplitudes(&[0]);
        assert_eq!(start, 0);
        assert_eq!(amplitudes, [1.0]);

        let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, &amplitudes, true);
        let first = sampler.samplers.first().unwrap();
        assert_eq!(first.persistence, 1.0);
        assert_eq!(first.lacunarity, 1.0);
        assert_eq!(sampler.max_value, 2.0);

        let coords = [(226.220117499588, 32.67924779023767, 202.84067325597647)];

        for (data, (x, y, z)) in sampler.samplers.iter().zip(coords) {
            assert_eq!(data.sampler.x_origin, x);
            assert_eq!(data.sampler.y_origin, y);
            assert_eq!(data.sampler.z_origin, z);
        }
    }

    #[test]
    fn create() {
        let mut rand = Xoroshiro::from_seed(111);
        assert_eq!(rand.next_i32(), -1467508761);

        let sampler = PerlinNoiseSampler::new(&mut rand);
        assert_eq!(sampler.x_origin, 48.58072036717974);
        assert_eq!(sampler.y_origin, 110.73235882678037);
        assert_eq!(sampler.z_origin, 65.26438852860176);

        let permutation: [u8; 256] = [
            159, 113, 41, 143, 203, 123, 95, 177, 25, 79, 229, 219, 194, 60, 130, 14, 83, 99, 24,
            202, 207, 232, 167, 152, 220, 201, 29, 235, 87, 147, 74, 160, 155, 97, 111, 31, 85,
            205, 115, 50, 13, 171, 77, 237, 149, 116, 209, 174, 169, 109, 221, 9, 166, 84, 54, 216,
            121, 106, 211, 16, 69, 244, 65, 192, 183, 146, 124, 37, 56, 45, 193, 158, 126, 217, 36,
            255, 162, 163, 230, 103, 63, 90, 191, 214, 20, 138, 32, 39, 238, 67, 64, 105, 250, 140,
            148, 114, 68, 75, 200, 161, 239, 125, 227, 199, 101, 61, 175, 107, 129, 240, 170, 51,
            139, 86, 186, 145, 212, 178, 30, 251, 89, 226, 120, 153, 47, 141, 233, 2, 179, 236, 1,
            19, 98, 21, 164, 108, 11, 23, 91, 204, 119, 88, 165, 195, 168, 26, 48, 206, 128, 6, 52,
            118, 110, 180, 197, 231, 117, 7, 3, 135, 224, 58, 82, 78, 4, 59, 222, 18, 72, 57, 150,
            43, 246, 100, 122, 112, 53, 133, 93, 17, 27, 210, 142, 234, 245, 80, 22, 46, 185, 172,
            71, 248, 33, 173, 76, 35, 40, 92, 228, 127, 254, 70, 42, 208, 73, 104, 187, 62, 154,
            243, 189, 241, 34, 66, 249, 94, 8, 12, 134, 132, 102, 242, 196, 218, 181, 28, 38, 15,
            151, 157, 247, 223, 198, 55, 188, 96, 0, 182, 49, 190, 156, 10, 215, 252, 131, 137,
            184, 176, 136, 81, 44, 213, 253, 144, 225, 5,
        ];
        assert_eq!(sampler.permutation, permutation);
    }

    #[test]
    #[expect(clippy::too_many_lines)]
    fn no_y() {
        let mut rand = Xoroshiro::from_seed(111);
        assert_eq!(rand.next_i32(), -1467508761);
        let sampler = PerlinNoiseSampler::new(&mut rand);

        let values = [
            (
                (
                    -3.134738528791615E8,
                    5.676610095659718E7,
                    2.011711832498507E8,
                ),
                0.38582139614602945,
            ),
            (
                (-1369026.560586418, 3.957311252810864E8, 6.797037355570006E8),
                0.15777501333157193,
            ),
            (
                (
                    6.439373693833767E8,
                    -3.36218773041759E8,
                    -3.265494249695775E8,
                ),
                -0.2806135912409497,
            ),
            (
                (
                    1.353820060118252E8,
                    -3.204701624793043E8,
                    -4.612474746056331E8,
                ),
                -0.15052865500837787,
            ),
            (
                (
                    -6906850.625560562,
                    1.0153663948838013E8,
                    2.4923185478305575E8,
                ),
                -0.3079300694558318,
            ),
            (
                (
                    -7.108376621385525E7,
                    -2.029413580824217E8,
                    2.5164602748045415E8,
                ),
                0.03051312670440398,
            ),
            (
                (
                    1.0591429119126628E8,
                    -4.7911044364543396E8,
                    -2918719.2277242197,
                ),
                -0.11775123159138573,
            ),
            (
                (
                    4.04615501401398E7,
                    -3.074409286586152E8,
                    5.089118769334092E7,
                ),
                0.08763639340713025,
            ),
            (
                (
                    -4.8645283544246924E8,
                    -3.922570151180015E8,
                    2.3741632952563038E8,
                ),
                0.08857245482456311,
            ),
            (
                (
                    2.861710031285905E8,
                    -1.8973201372718483E8,
                    -3.2653143323982143E8,
                ),
                -0.2378339698793312,
            ),
            (
                (
                    2.885407603819252E8,
                    -3.358708100884505E7,
                    -1.4480399660676318E8,
                ),
                -0.46661747461279457,
            ),
            (
                (
                    3.6548491156354237E8,
                    7.995429702025633E7,
                    2.509991661702412E8,
                ),
                0.1671543972176835,
            ),
            (
                (
                    1.3298684552869435E8,
                    3.6743804723880893E8,
                    5.791092458225288E7,
                ),
                -0.2704070746642889,
            ),
            (
                (
                    -1.3123184148036437E8,
                    -2.722300890805201E8,
                    2.1601883778132245E7,
                ),
                0.05049887915906969,
            ),
            (
                (
                    -5.56047682304707E8,
                    3.554803693060646E8,
                    3.1647392358159083E8,
                ),
                -0.21178547899422662,
            ),
            (
                (
                    5.638216625134594E8,
                    -2.236907346192737E8,
                    -5.0562852022285646E8,
                ),
                0.03351245780858128,
            ),
            (
                (
                    -5.436956979127073E7,
                    -1.129261611506945E8,
                    -1.7909512156895646E8,
                ),
                0.31670010349494726,
            ),
            (
                (
                    1.0915760091641709E8,
                    1.932642099859593E7,
                    -3.405060533753616E8,
                ),
                -0.13987439655026918,
            ),
            (
                (
                    -6.73911758014991E8,
                    -2.2147483413687566E8,
                    -4.531457195005102E7,
                ),
                0.07824440437151846,
            ),
            (
                (
                    -2.4827386778136212E8,
                    -2.6640208832089204E8,
                    -3.354675096522197E8,
                ),
                -0.2989735599541437,
            ),
        ];

        for ((x, y, z), sample) in values {
            assert_eq!(sampler.sample_flat_y(x, y, z), sample);
        }
    }

    #[test]
    fn no_y_chunk() {
        let expected_data: Vec<(i32, i32, i32, f64)> =
            read_data_from_file!("../../assets/perlin2_7_4.json");

        let mut rand = Xoroshiro::from_seed(0);
        let splitter = rand.next_splitter();
        let mut rand = splitter.split_string("minecraft:terrain");
        assert_eq!(rand.next_i32(), 1374487555);
        let mut rand = splitter.split_string("minecraft:terrain");

        let (first, amplitudes) =
            OctavePerlinNoiseSampler::calculate_amplitudes(&(-15..=0).collect::<Vec<i32>>());
        let sampler = OctavePerlinNoiseSampler::new(&mut rand, first, &amplitudes, true);
        let sampler = &sampler.samplers.last().unwrap().sampler;

        assert_eq!(sampler.x_origin, 18.223354299069797);
        assert_eq!(sampler.y_origin, 93.99298907803595);
        assert_eq!(sampler.z_origin, 184.48198875745823);

        for (x, y, z, sample) in expected_data {
            let scale = 0.005;
            let result =
                sampler.sample_flat_y(x as f64 * scale, y as f64 * scale, z as f64 * scale);
            assert_eq_delta!(result, sample, f64::EPSILON);
        }
    }

    #[test]
    #[expect(clippy::too_many_lines)]
    fn no_fade() {
        let mut rand = Xoroshiro::from_seed(111);
        assert_eq!(rand.next_i32(), -1467508761);
        let sampler = PerlinNoiseSampler::new(&mut rand);

        let values = [
            (
                (
                    -3.134738528791615E8,
                    5.676610095659718E7,
                    2.011711832498507E8,
                    -1369026.560586418,
                    3.957311252810864E8,
                ),
                23234.47859421248,
            ),
            (
                (
                    6.797037355570006E8,
                    6.439373693833767E8,
                    -3.36218773041759E8,
                    -3.265494249695775E8,
                    1.353820060118252E8,
                ),
                -0.016403984198221984,
            ),
            (
                (
                    -3.204701624793043E8,
                    -4.612474746056331E8,
                    -6906850.625560562,
                    1.0153663948838013E8,
                    2.4923185478305575E8,
                ),
                0.3444286491766397,
            ),
            (
                (
                    -7.108376621385525E7,
                    -2.029413580824217E8,
                    2.5164602748045415E8,
                    1.0591429119126628E8,
                    -4.7911044364543396E8,
                ),
                0.03051312670440398,
            ),
            (
                (
                    -2918719.2277242197,
                    4.04615501401398E7,
                    -3.074409286586152E8,
                    5.089118769334092E7,
                    -4.8645283544246924E8,
                ),
                0.3434020232968479,
            ),
            (
                (
                    -3.922570151180015E8,
                    2.3741632952563038E8,
                    2.861710031285905E8,
                    -1.8973201372718483E8,
                    -3.2653143323982143E8,
                ),
                -0.07935517045771859,
            ),
            (
                (
                    2.885407603819252E8,
                    -3.358708100884505E7,
                    -1.4480399660676318E8,
                    3.6548491156354237E8,
                    7.995429702025633E7,
                ),
                -0.46661747461279457,
            ),
            (
                (
                    2.509991661702412E8,
                    1.3298684552869435E8,
                    3.6743804723880893E8,
                    5.791092458225288E7,
                    -1.3123184148036437E8,
                ),
                0.0723439870279631,
            ),
            (
                (
                    -2.722300890805201E8,
                    2.1601883778132245E7,
                    -5.56047682304707E8,
                    3.554803693060646E8,
                    3.1647392358159083E8,
                ),
                -0.656560662515624,
            ),
            (
                (
                    5.638216625134594E8,
                    -2.236907346192737E8,
                    -5.0562852022285646E8,
                    -5.436956979127073E7,
                    -1.129261611506945E8,
                ),
                0.03351245780858128,
            ),
            (
                (
                    -1.7909512156895646E8,
                    1.0915760091641709E8,
                    1.932642099859593E7,
                    -3.405060533753616E8,
                    -6.73911758014991E8,
                ),
                -0.2089142558681482,
            ),
            (
                (
                    -2.2147483413687566E8,
                    -4.531457195005102E7,
                    -2.4827386778136212E8,
                    -2.6640208832089204E8,
                    -3.354675096522197E8,
                ),
                0.38250837565598395,
            ),
            (
                (
                    3.618095500266467E8,
                    -1.785261966631494E8,
                    8.855575989580283E7,
                    -1.3702508894700047E8,
                    -3.564818414428105E8,
                ),
                0.00883370523171791,
            ),
            (
                (
                    3.585592594479808E7,
                    1.8822208340571395E8,
                    -386327.524558296,
                    -2.613548000006699E8,
                    1995562.4304017993,
                ),
                -0.27653878487738676,
            ),
            (
                (
                    3.0800276873619422E7,
                    1.166750302259058E7,
                    8.502636255675305E7,
                    4.347409652503064E8,
                    1.0678086363325526E8,
                ),
                -0.13800758751097497,
            ),
            (
                (
                    -2.797805968820768E8,
                    9.446376468140173E7,
                    2.2821543438325477E8,
                    -4.8176550369786626E8,
                    7.316871126959312E7,
                ),
                0.05505478945301634,
            ),
            (
                (
                    -2.236596113898912E7,
                    1.5296478602495643E8,
                    3.903966235164034E8,
                    9.40479475527148E7,
                    1.0948229366673347E8,
                ),
                0.1158678618158655,
            ),
            (
                (
                    3.5342596632385695E8,
                    3.1584773170834744E8,
                    -2.1860087172846535E8,
                    -1.8126626716239208E8,
                    -2.5263456116162892E7,
                ),
                -0.354953975313882,
            ),
            (
                (
                    -1.2711958434031656E8,
                    -4.541988855460623E7,
                    -1.375878074907788E8,
                    6.72693784001799E7,
                    6815739.665531283,
                ),
                -0.23849179316215247,
            ),
            (
                (
                    1.2660906027019228E8,
                    -3.3769609799741164E7,
                    -3.4331505330046E8,
                    -6.663866659430536E7,
                    -1.6603843763414428E8,
                ),
                0.07974650858448407,
            ),
        ];

        for ((x, y, z, y_scale, y_max), sample) in values {
            assert_eq!(sampler.sample_no_fade(x, y, z, y_scale, y_max), sample);
        }
    }

    // #[test]
    // fn no_fade_chunk() {
    //     let expected_data: Vec<(i32, i32, i32, f64)> =
    //         read_data_from_file!("../../assets/tests/perlin_7_4.json");

    //     let mut rand = Xoroshiro::from_seed(0);
    //     let splitter = rand.next_splitter();
    //     let mut rand = splitter.split_string("minecraft:terrain");
    //     assert_eq!(rand.next_i32(), 1374487555);
    //     let mut rand = splitter.split_string("minecraft:terrain");

    //     let (first, amplitudes) =
    //         OctavePerlinNoiseSampler::calculate_amplitudes(&(-15..=0).collect::<Vec<i32>>());
    //     let sampler = OctavePerlinNoiseSampler::new(&mut rand, first, &amplitudes, true);
    //     let sampler = &sampler.samplers.last().unwrap().sampler;

    //     assert_eq!(sampler.x_origin, 18.223354299069797);
    //     assert_eq!(sampler.y_origin, 93.99298907803595);
    //     assert_eq!(sampler.z_origin, 184.48198875745823);

    //     for (x, y, z, sample) in expected_data {
    //         let scale = 0.005;
    //         let max_y = scale * 2.0;
    //         let result = sampler.sample_no_fade(
    //             x as f64 * scale,
    //             y as f64 * scale,
    //             z as f64 * scale,
    //             scale,
    //             max_y,
    //         );
    //         assert_eq_delta!(result, sample, f64::EPSILON);
    //     }
    // }

    // #[test]
    // fn map() {
    //     let expected_data: Vec<i32> = read_data_from_file!("../../assets/tests/perlin_map.json");
    //     let mut expected_iter = expected_data.iter();

    //     let mut rand = Xoroshiro::from_seed(0);
    //     let splitter = rand.next_splitter();
    //     let mut rand = splitter.split_string("minecraft:terrain");
    //     assert_eq!(rand.next_i32(), 1374487555);
    //     let mut rand = splitter.split_string("minecraft:terrain");

    //     let (first, amplitudes) =
    //         OctavePerlinNoiseSampler::calculate_amplitudes(&(-15..=0).collect::<Vec<i32>>());
    //     let sampler = OctavePerlinNoiseSampler::new(&mut rand, first, &amplitudes, true);
    //     let sampler = &sampler.samplers.last().unwrap().sampler;

    //     for x in -512..512 {
    //         let y = sampler.map(x);
    //         assert_eq!(y, *expected_iter.next().unwrap());
    //     }
    // }

    /// Hashes raw f64 outputs from `PerlinNoiseSampler`/`OctavePerlinNoiseSampler`.
    /// Performance changes to this file must not change any float value by a single
    /// ULP. This test just helps confirm that there really hasn't been a change.
    #[test]
    fn perlin_and_octave_fingerprint_is_stable() {
        fn fnv1a_hash_f64(values: impl Iterator<Item = f64>) -> u64 {
            const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
            const FNV_PRIME: u64 = 0x0000_0100_0000_01B3;
            let mut hash = FNV_OFFSET;
            for value in values {
                for byte in value.to_bits().to_le_bytes() {
                    hash ^= u64::from(byte);
                    hash = hash.wrapping_mul(FNV_PRIME);
                }
            }
            hash
        }

        let mut coord_rand = Xoroshiro::from_seed(0x5EED_C0DE_1234_5678);
        let coords: Vec<(f64, f64, f64)> = (0..2000)
            .map(|_| {
                (
                    (coord_rand.next_f64() - 0.5) * 200_000.0,
                    (coord_rand.next_f64() - 0.5) * 4_000.0,
                    (coord_rand.next_f64() - 0.5) * 200_000.0,
                )
            })
            .collect();

        let mut results = Vec::new();

        for seed in [1u64, 2, 42, 12345, 987_654_321] {
            let mut rand = Xoroshiro::from_seed(seed);
            let sampler = PerlinNoiseSampler::new(&mut rand);
            for &(x, y, z) in &coords {
                results.push(sampler.sample_no_fade(x, y, z, 0.0, 0.0));
                results.push(sampler.sample_no_fade(x, y, z, 0.5, 100.0));
            }
        }

        for seed in [7u64, 99, 555] {
            for legacy in [false, true] {
                let mut rand = Xoroshiro::from_seed(seed);
                let (start, amplitudes) =
                    OctavePerlinNoiseSampler::calculate_amplitudes(&(-4..=2).collect::<Vec<i32>>());
                let sampler = OctavePerlinNoiseSampler::new(&mut rand, start, &amplitudes, legacy);
                for &(x, y, z) in coords.iter().take(500) {
                    results.push(sampler.sample(x, y, z));
                }
            }
        }

        let hash = fnv1a_hash_f64(results.into_iter());
        assert_eq!(
            hash, 0x2c36_e2de_2f21_9be7,
            "Perlin/OctavePerlin fingerprint changed"
        );
    }
}
