use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use serde_json::Value;
use std::fs;

use crate::placed_feature::{
    value_to_block_direction, value_to_block_predicate, value_to_block_state,
    value_to_block_state_codec, value_to_height_provider, value_to_int_provider,
};

/// Reads `configured_features.json` and emits the complete `ConfiguredFeature` enum `TokenStream`.
pub fn build_enum() -> TokenStream {
    let json_content = fs::read_to_string("../../assets/configured_features.json")
        .expect("Failed to read configured_features.json");
    let json: Value =
        serde_json::from_str(&json_content).expect("Failed to parse configured_features.json");

    let mut from_name_arms = Vec::new();
    let mut to_name_arms = Vec::new();

    let variants: Vec<TokenStream> = json
        .as_object()
        .unwrap()
        .iter()
        .map(|(name, _)| {
            let variant_name = format_ident!("{}", name.to_pascal_case());
            from_name_arms.push(quote! {
                #name => Some(Self::#variant_name),
            });
            to_name_arms.push(quote! {
                Self::#variant_name => #name,
            });
            quote! {
                #variant_name,
            }
        })
        .collect();

    quote! {
        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
        pub enum ConfiguredFeature {
            #(#variants)*
        }

        impl ConfiguredFeature {
            pub fn from_name(name: &str) -> Option<Self> {
                let name = name.strip_prefix("minecraft:").unwrap_or(name);
                match name {
                    #(#from_name_arms)*
                    _ => None,
                }
            }

            pub const fn to_name(&self) -> &'static str {
                match self {
                    #(#to_name_arms)*
                }
            }
        }
    }
}

/// Reads `configured_features.json` and emits a `build_configured_features()` function `TokenStream`.
pub fn build() -> TokenStream {
    let json_content = fs::read_to_string("../../assets/configured_features.json")
        .expect("Failed to read configured_features.json");
    let json: Value =
        serde_json::from_str(&json_content).expect("Failed to parse configured_features.json");

    let entries: Vec<TokenStream> = json
        .as_object()
        .unwrap()
        .iter()
        .map(|(name, value)| {
            let cf = value_to_configured_feature(value);
            let variant_name = format_ident!("{}", name.to_pascal_case());
            quote! {
                map.insert(pumpkin_data::configured_feature::ConfiguredFeature::#variant_name, #cf);
            }
        })
        .collect();

    quote! {
        #[allow(clippy::all, unused_imports, dead_code, clippy::large_stack_frames, clippy::too_many_lines)]
        fn build_configured_features() -> std::collections::HashMap<pumpkin_data::configured_feature::ConfiguredFeature, ConfiguredFeature> {
            use crate::generation::block_predicate::{
                AllOfBlockPredicate, AnyOfBlockPredicate, BlockPredicate,
                HasSturdyFacePredicate, InsideWorldBoundsBlockPredicate,
                MatchingBlockTagPredicate, MatchingBlocksBlockPredicate, MatchingBlocksWrapper,
                MatchingFluidsBlockPredicate, NotBlockPredicate, OffsetBlocksBlockPredicate,
                ReplaceableBlockPredicate, SolidBlockPredicate, WouldSurviveBlockPredicate,
            };
            use crate::generation::height_provider::{
                HeightProvider, TrapezoidHeightProvider, UniformHeightProvider,
                VeryBiasedToBottomHeightProvider,
            };
            use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset};
            use pumpkin_util::math::int_provider::{
                BiasedToBottomIntProvider, ClampedIntProvider, TrapezoidIntProvider, ClampedNormalIntProvider,
                ConstantIntProvider, IntProvider, NormalIntProvider, UniformIntProvider,
                WeightedEntry, WeightedListIntProvider,
            };
            use crate::block::BlockStateCodec;
            use crate::generation::block_state_provider::{
                BlockStateProvider, BlockStateRule, DualNoiseBlockStateProvider,
                NoiseBlockStateProvider, NoiseBlockStateProviderBase,
                NoiseThresholdBlockStateProvider, PillarBlockStateProvider,
                RandomizedIntBlockStateProvider, RuleBasedBlockStateProvider, SimpleStateProvider,
                WeightedBlockStateProvider,
            };
            use pumpkin_util::math::pool::Weighted;
            use pumpkin_util::DoublePerlinNoiseParametersCodec;
            use crate::generation::rule::{
                RuleTest,
                block_match::BlockMatchRuleTest,
                block_state_match::BlockStateMatchRuleTest,
                tag_match::TagMatchRuleTest,
                random_block_match::RandomBlockMatchRuleTest,
                random_block_state_match::RandomBlockStateMatchRuleTest,
            };
            use crate::generation::feature::placed_features::{
                Feature, PlacedFeature, PlacementModifier,
                BiomePlacementModifier, BlockFilterPlacementModifier,
                CountOnEveryLayerPlacementModifier, CountPlacementModifier,
                EnvironmentScanPlacementModifier, HeightRangePlacementModifier,
                HeightmapPlacementModifier, NoiseBasedCountPlacementModifier,
                NoiseThresholdCountPlacementModifier, RandomOffsetPlacementModifier,
                RarityFilterPlacementModifier, SquarePlacementModifier,
                SurfaceThresholdFilterPlacementModifier, SurfaceWaterDepthFilterPlacementModifier,
                PlacedFeatureWrapper,
            };
            use crate::generation::feature::features::{
                bamboo::BambooFeature,
                block_column::{BlockColumnFeature, Layer},
                end_spike::{EndSpikeFeature, Spike},
                fallen_tree::FallenTreeFeature,
                nether_forest_vegetation::NetherForestVegetationFeature,
                netherrack_replace_blobs::ReplaceBlobsFeature,
                ore::{OreFeature, OreTarget},
                random_boolean_selector::RandomBooleanFeature,
                random_patch::RandomPatchFeature,
                random_selector::{RandomFeature, RandomFeatureEntry},
                sea_pickle::SeaPickleFeature,
                seagrass::SeagrassFeature,
                simple_block::SimpleBlockFeature,
                simple_random_selector::SimpleRandomFeature,
                spring_feature::{BlockWrapper, SpringFeatureFeature},
                geode::GeodeFeature,
                tree::TreeFeature,
                vegetation_patch::VegetationPatchFeature,
                waterlogged_vegetation_patch::WaterloggedVegetationPatchFeature,
                tree::trunk::{TrunkPlacer, TrunkType,
                    bending::BendingTrunkPlacer,
                    cherry::CherryTrunkPlacer,
                    dark_oak::DarkOakTrunkPlacer,
                    fancy::FancyTrunkPlacer,
                    forking::ForkingTrunkPlacer,
                    giant::GiantTrunkPlacer,
                    mega_jungle::MegaJungleTrunkPlacer,
                    straight::StraightTrunkPlacer,
                    upwards_branching::UpwardsBranchingTrunkPlacer,
                },
                tree::foliage::{FoliagePlacer, FoliageType,
                    acacia::AcaciaFoliagePlacer,
                    blob::BlobFoliagePlacer,
                    bush::BushFoliagePlacer,
                    cherry::CherryFoliagePlacer,
                    dark_oak::DarkOakFoliagePlacer,
                    fancy::LargeOakFoliagePlacer,
                    jungle::JungleFoliagePlacer,
                    mega_pine::MegaPineFoliagePlacer,
                    pine::PineFoliagePlacer,
                    random_spread::RandomSpreadFoliagePlacer,
                    spruce::SpruceFoliagePlacer,
                },
                tree::decorator::{
                    TreeDecorator,
                    alter_ground::AlterGroundTreeDecorator,
                    attached_to_leaves::AttachedToLeavesTreeDecorator,
                    attached_to_logs::AttachedToLogsTreeDecorator,
                    beehive::BeehiveTreeDecorator,
                    cocoa::CocoaTreeDecorator,
                    creaking_heart::CreakingHeartTreeDecorator,
                    leave_vine::LeavesVineTreeDecorator,
                    pale_moss::PaleMossTreeDecorator,
                    place_on_ground::PlaceOnGroundTreeDecorator,
                    trunk_vine::TrunkVineTreeDecorator,
                },
                tree::root::{
                    RootPlacer,
                    mangrove::{AboveRootPlacement, MangroveRootPlacement, MangroveRootPlacer},
                },
            };
            use crate::generation::feature::size::{FeatureSize, FeatureSizeType, ThreeLayersFeatureSize, TwoLayersFeatureSize};
            use pumpkin_data::{Block, BlockDirection};
            use pumpkin_util::math::vector3::Vector3;
            use pumpkin_util::HeightMap;
            use crate::generation::feature::features::drip_stone::small::SmallDripstoneFeature;
            let mut map = std::collections::HashMap::new();
            #(#entries)*
            map
        }
    }
}

fn value_to_fossil_processor(value: &Value) -> TokenStream {
    let variant = match value
        .as_str()
        .expect("fossil processor names must be strings")
    {
        "minecraft:fossil_rot" => format_ident!("FossilRot"),
        "minecraft:fossil_coal" => format_ident!("Coal"),
        "minecraft:fossil_diamonds" => format_ident!("Diamonds"),
        processor => panic!("Unsupported fossil processor: {processor}"),
    };

    quote! {
        crate::generation::feature::features::fossil::FossilProcessor::#variant
    }
}

/// Converts a single configured-feature JSON value into its `ConfiguredFeature` token stream.
///
/// # Arguments
/// – `v` – the JSON object for the feature, expected to contain `"type"` and `"config"` fields.
pub fn value_to_configured_feature(v: &Value) -> TokenStream {
    let type_str = v["type"].as_str().unwrap_or("");
    let config = &v["config"];
    match type_str {
        "minecraft:no_op" => quote! { ConfiguredFeature::NoOp },
        "minecraft:bamboo" => {
            let prob = config["probability"].as_f64().unwrap_or(0.0) as f32;
            quote! { ConfiguredFeature::Bamboo(BambooFeature { probability: #prob }) }
        }
        "minecraft:seagrass" => {
            let prob = config["probability"].as_f64().unwrap_or(0.0) as f32;
            quote! { ConfiguredFeature::Seagrass(SeagrassFeature { probability: #prob }) }
        }
        "minecraft:sea_pickle" => {
            let count = value_to_int_provider(&config["count"]);
            quote! { ConfiguredFeature::SeaPickle(SeaPickleFeature { count: #count }) }
        }
        "minecraft:nether_forest_vegetation" => {
            let provider = value_to_block_state_provider(&config["state_provider"]);
            let w = config["spread_width"].as_i64().unwrap_or(8) as i32;
            let h = config["spread_height"].as_i64().unwrap_or(4) as i32;
            quote! {
                ConfiguredFeature::NetherForestVegetation(NetherForestVegetationFeature {
                    state_provider: #provider,
                    spread_width: #w,
                    spread_height: #h,
                })
            }
        }
        "minecraft:netherrack_replace_blobs" => {
            let target = value_to_block_state(&config["target"]);
            let state = value_to_block_state(&config["state"]);
            let radius = value_to_int_provider(&config["radius"]);
            quote! {
                ConfiguredFeature::NetherrackReplaceBlobs(ReplaceBlobsFeature {
                    target: #target,
                    state: #state,
                    radius: #radius,
                })
            }
        }
        "minecraft:simple_block" => {
            let to_place = value_to_block_state_provider(&config["to_place"]);
            let schedule = if config["schedule_tick"].is_boolean() {
                let b = config["schedule_tick"].as_bool().unwrap_or(false);
                quote! { Some(#b) }
            } else {
                quote! { None }
            };
            quote! {
                ConfiguredFeature::SimpleBlock(SimpleBlockFeature {
                    to_place: #to_place,
                    schedule_tick: #schedule,
                })
            }
        }
        "minecraft:ore" | "minecraft:scattered_ore" => {
            let size = config["size"].as_i64().unwrap_or(0) as i32;
            let discard = config["discard_chance_on_air_exposure"]
                .as_f64()
                .unwrap_or(0.0) as f32;
            let targets: Vec<TokenStream> = config["targets"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .map(|t| {
                            let rule = value_to_rule_test(&t["target"]);
                            let state = value_to_block_state(&t["state"]);
                            quote! { OreTarget { target: #rule, state: #state } }
                        })
                        .collect()
                })
                .unwrap_or_default();
            if type_str == "minecraft:scattered_ore" {
                quote! {
                    ConfiguredFeature::ScatteredOre(crate::generation::feature::features::scattered_ore::ScatteredOreFeature {
                        size: #size,
                        discard_chance_on_air_exposure: #discard,
                        targets: vec![#(#targets),*],
                    })
                }
            } else {
                quote! {
                    ConfiguredFeature::Ore(OreFeature {
                        size: #size,
                        discard_chance_on_air_exposure: #discard,
                        targets: vec![#(#targets),*],
                    })
                }
            }
        }
        "minecraft:spring_feature" => {
            let state = value_to_block_state(&config["state"]);
            let req = config["requires_block_below"].as_bool().unwrap_or(true);
            let rock = config["rock_count"].as_i64().unwrap_or(4) as i32;
            let hole = config["hole_count"].as_i64().unwrap_or(1) as i32;
            let valid = value_to_block_wrapper(&config["valid_blocks"]);
            quote! {
                ConfiguredFeature::SpringFeature(SpringFeatureFeature {
                    state: #state,
                    requires_block_below: #req,
                    rock_count: #rock,
                    hole_count: #hole,
                    valid_blocks: #valid,
                })
            }
        }
        "minecraft:block_column" => {
            let layers: Vec<TokenStream> = config["layers"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .map(|l| {
                            let h = value_to_int_provider(&l["height"]);
                            let p = value_to_block_state_provider(&l["provider"]);
                            quote! { Layer { height: #h, provider: #p } }
                        })
                        .collect()
                })
                .unwrap_or_default();
            let dir = value_to_block_direction(config["direction"].as_str().unwrap_or("up"));
            let allowed = value_to_block_predicate(&config["allowed_placement"]);
            let tip = config["prioritize_tip"].as_bool().unwrap_or(false);
            quote! {
                ConfiguredFeature::BlockColumn(BlockColumnFeature {
                    layers: vec![#(#layers),*],
                    direction: #dir,
                    allowed_placement: #allowed,
                    prioritize_tip: #tip,
                })
            }
        }
        "minecraft:fallen_tree" => {
            let trunk = value_to_block_state_provider(&config["trunk_provider"]);
            quote! {
                ConfiguredFeature::FallenTree(FallenTreeFeature {
                    trunk_provider: #trunk,
                })
            }
        }
        "minecraft:random_patch" | "minecraft:flower" | "minecraft:no_bonemeal_flower" => {
            let tries = config["tries"].as_u64().unwrap_or(128) as u8;
            let xz = config["xz_spread"].as_u64().unwrap_or(7) as u8;
            let y = config["y_spread"].as_u64().unwrap_or(3) as u8;
            let feature = value_to_inline_placed_feature(&config["feature"]);
            let variant = match type_str {
                "minecraft:flower" => quote! { ConfiguredFeature::Flower },
                "minecraft:no_bonemeal_flower" => quote! { ConfiguredFeature::NoBonemealFlower },
                _ => quote! { ConfiguredFeature::RandomPatch },
            };
            quote! {
                #variant(RandomPatchFeature {
                    tries: #tries,
                    xz_spread: #xz,
                    y_spread: #y,
                    feature: Box::new(#feature),
                })
            }
        }
        "minecraft:random_selector" => {
            let entries: Vec<TokenStream> = config["features"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .map(|e| {
                            let feat = value_to_placed_feature_wrapper(&e["feature"]);
                            let chance = e["chance"].as_f64().unwrap_or(0.1) as f32;
                            quote! { RandomFeatureEntry { feature: #feat, chance: #chance } }
                        })
                        .collect()
                })
                .unwrap_or_default();
            let default = value_to_placed_feature_wrapper(&config["default"]);
            quote! {
                ConfiguredFeature::RandomSelector(RandomFeature {
                    features: vec![#(#entries),*],
                    default: Box::new(#default),
                })
            }
        }
        "minecraft:simple_random_selector" => {
            let features: Vec<TokenStream> = config["features"]
                .as_array()
                .map(|arr| arr.iter().map(value_to_inline_placed_feature).collect())
                .unwrap_or_default();
            quote! {
                ConfiguredFeature::SimpleRandomSelector(SimpleRandomFeature {
                    features: vec![#(#features),*],
                })
            }
        }
        "minecraft:random_boolean_selector" => {
            let ft = value_to_placed_feature_wrapper(&config["feature_true"]);
            let ff = value_to_placed_feature_wrapper(&config["feature_false"]);
            quote! {
                ConfiguredFeature::RandomBooleanSelector(RandomBooleanFeature {
                    feature_true: Box::new(#ft),
                    feature_false: Box::new(#ff),
                })
            }
        }
        "minecraft:end_spike" => {
            let inv = config["crystal_invulnerable"].as_bool().unwrap_or(false);
            let spikes: Vec<TokenStream> = config["spikes"]
                .as_array()
                .map(|arr| {
                    arr.iter().map(|s| {
                    let cx = s["centerX"].as_i64().unwrap_or(0) as i32;
                    let cz = s["centerZ"].as_i64().unwrap_or(0) as i32;
                    let r = s["radius"].as_i64().unwrap_or(0) as i32;
                    let h = s["height"].as_i64().unwrap_or(0) as i32;
                    let g = s["guarded"].as_bool().unwrap_or(false);
                    quote! {
                        Spike { center_x: #cx, center_z: #cz, radius: #r, height: #h, guarded: #g }
                    }
                }).collect()
                })
                .unwrap_or_default();
            quote! {
                ConfiguredFeature::EndSpike(EndSpikeFeature {
                    crystal_invulnerable: #inv,
                    spikes: vec![#(#spikes),*],
                })
            }
        }
        "minecraft:tree" => {
            let tree = value_to_tree_feature(config);
            quote! { ConfiguredFeature::Tree(Box::new(#tree)) }
        }
        "minecraft:pointed_dripstone" | "minecraft:speleothem" => {
            let taller = config["chance_of_taller_dripstone"].as_f64().unwrap_or(0.2) as f32;
            let dir_spread = config["chance_of_directional_spread"]
                .as_f64()
                .unwrap_or(0.7) as f32;
            let r2 = config["chance_of_spread_radius2"].as_f64().unwrap_or(0.5) as f32;
            let r3 = config["chance_of_spread_radius3"].as_f64().unwrap_or(0.5) as f32;
            quote! {
                ConfiguredFeature::PointedDripstone(SmallDripstoneFeature {
                    taller_dripstone: #taller,
                    directional_spread: #dir_spread,
                    spread_radius2: #r2,
                    spread_radius3: #r3,
                })
            }
        }
        "minecraft:geode" => {
            let blocks = &config["blocks"];
            let filling_provider = value_to_block_state_provider(&blocks["filling_provider"]);
            let inner_layer_provider =
                value_to_block_state_provider(&blocks["inner_layer_provider"]);
            let alternate_inner_layer_provider =
                value_to_block_state_provider(&blocks["alternate_inner_layer_provider"]);
            let middle_layer_provider =
                value_to_block_state_provider(&blocks["middle_layer_provider"]);
            let outer_layer_provider =
                value_to_block_state_provider(&blocks["outer_layer_provider"]);
            let inner_placements: Vec<TokenStream> = blocks["inner_placements"]
                .as_array()
                .map(|arr| arr.iter().map(value_to_block_state_codec).collect())
                .unwrap_or_default();
            let cannot_replace = value_to_block_wrapper(&blocks["cannot_replace"]);
            let invalid_blocks = value_to_block_wrapper(&blocks["invalid_blocks"]);

            let layers = &config["layers"];
            let filling = layers["filling"].as_f64().unwrap_or(1.7);
            let inner_layer = layers["inner_layer"].as_f64().unwrap_or(2.2);
            let middle_layer = layers["middle_layer"].as_f64().unwrap_or(3.2);
            let outer_layer = layers["outer_layer"].as_f64().unwrap_or(4.2);

            let crack = &config["crack"];
            let generate_crack_chance = crack["generate_crack_chance"].as_f64().unwrap_or(1.0);
            let base_crack_size = crack["base_crack_size"].as_f64().unwrap_or(2.0);
            let crack_point_offset = crack["crack_point_offset"].as_i64().unwrap_or(2) as i32;

            let use_potential_placements_chance = config["use_potential_placements_chance"]
                .as_f64()
                .unwrap_or(0.35);
            let use_alternate_layer0_chance = config["use_alternate_layer0_chance"]
                .as_f64()
                .unwrap_or(0.0);
            let placements_require_layer0_alternate = config["placements_require_layer0_alternate"]
                .as_bool()
                .unwrap_or(true);
            let outer_wall_distance = value_to_int_provider(&config["outer_wall_distance"]);
            let distribution_points = value_to_int_provider(&config["distribution_points"]);
            let point_offset = value_to_int_provider(&config["point_offset"]);
            let min_gen_offset = config["min_gen_offset"].as_i64().unwrap_or(-16) as i32;
            let max_gen_offset = config["max_gen_offset"].as_i64().unwrap_or(16) as i32;
            let noise_multiplier = config["noise_multiplier"].as_f64().unwrap_or(0.05);
            let invalid_blocks_threshold =
                config["invalid_blocks_threshold"].as_i64().unwrap_or(0) as i32;

            quote! {
                ConfiguredFeature::Geode(Box::new(GeodeFeature {
                    filling_provider: #filling_provider,
                    inner_layer_provider: #inner_layer_provider,
                    alternate_inner_layer_provider: #alternate_inner_layer_provider,
                    middle_layer_provider: #middle_layer_provider,
                    outer_layer_provider: #outer_layer_provider,
                    inner_placements: vec![#(#inner_placements),*],
                    cannot_replace: #cannot_replace,
                    invalid_blocks: #invalid_blocks,
                    filling: #filling,
                    inner_layer: #inner_layer,
                    middle_layer: #middle_layer,
                    outer_layer: #outer_layer,
                    generate_crack_chance: #generate_crack_chance,
                    base_crack_size: #base_crack_size,
                    crack_point_offset: #crack_point_offset,
                    use_potential_placements_chance: #use_potential_placements_chance,
                    use_alternate_layer0_chance: #use_alternate_layer0_chance,
                    placements_require_layer0_alternate: #placements_require_layer0_alternate,
                    outer_wall_distance: #outer_wall_distance,
                    distribution_points: #distribution_points,
                    point_offset: #point_offset,
                    min_gen_offset: #min_gen_offset,
                    max_gen_offset: #max_gen_offset,
                    noise_multiplier: #noise_multiplier,
                    invalid_blocks_threshold: #invalid_blocks_threshold,
                }))
            }
        }
        "minecraft:monster_room" => {
            quote! { ConfiguredFeature::MonsterRoom(crate::generation::feature::features::monster_room::DungeonFeature {}) }
        }
        "minecraft:underwater_magma" => {
            let floor_range = config["floor_search_range"].as_i64().unwrap_or(0) as i32;
            let placement_radius = config["placement_radius_around_floor"]
                .as_i64()
                .unwrap_or(0) as i32;
            let placement_prob = config["placement_probability_per_valid_position"]
                .as_f64()
                .unwrap_or(0.0) as f32;
            quote! {
                ConfiguredFeature::UnderwaterMagma(
                    crate::generation::feature::features::underwater_magma::UnderwaterMagmaFeature {
                        floor_search_range: #floor_range,
                        placement_radius: #placement_radius,
                        placement_probability: #placement_prob,
                    }
                )
            }
        }
        "minecraft:vegetation_patch" | "minecraft:waterlogged_vegetation_patch" => {
            let replaceable = value_to_block_predicate(&config["replaceable"]);
            let ground_state = value_to_block_state_provider(&config["ground_state"]);
            let vegetation_feature = value_to_inline_placed_feature(&config["vegetation_feature"]);
            let surface = match config["surface"].as_str().unwrap_or("floor") {
                "ceiling" => {
                    quote! { pumpkin_util::math::vertical_surface_type::VerticalSurfaceType::Ceiling }
                }
                _ => {
                    quote! { pumpkin_util::math::vertical_surface_type::VerticalSurfaceType::Floor }
                }
            };
            let depth = value_to_int_provider(&config["depth"]);
            let extra_bottom = config["extra_bottom_block_chance"].as_f64().unwrap_or(0.0) as f32;
            let vert_range = config["vertical_range"].as_i64().unwrap_or(0) as i32;
            let veg_chance = config["vegetation_chance"].as_f64().unwrap_or(0.0) as f32;
            let xz = value_to_int_provider(&config["xz_radius"]);
            let extra_edge = config["extra_edge_column_chance"].as_f64().unwrap_or(0.0) as f32;

            if type_str == "minecraft:vegetation_patch" {
                quote! {
                    ConfiguredFeature::VegetationPatch(vegetation_patch::VegetationPatchFeature {
                        replaceable: #replaceable,
                        ground_state: #ground_state,
                        vegetation_feature: Box::new(#vegetation_feature),
                        surface: #surface,
                        depth: #depth,
                        extra_bottom_block_chance: #extra_bottom,
                        vertical_range: #vert_range,
                        vegetation_chance: #veg_chance,
                        xz_radius: #xz,
                        extra_edge_column_chance: #extra_edge,
                    })
                }
            } else {
                quote! {
                    ConfiguredFeature::WaterloggedVegetationPatch(waterlogged_vegetation_patch::WaterloggedVegetationPatchFeature {
                        base: vegetation_patch::VegetationPatchFeature {
                            replaceable: #replaceable,
                            ground_state: #ground_state,
                            vegetation_feature: Box::new(#vegetation_feature),
                            surface: #surface,
                            depth: #depth,
                            extra_bottom_block_chance: #extra_bottom,
                            vertical_range: #vert_range,
                            vegetation_chance: #veg_chance,
                            xz_radius: #xz,
                            extra_edge_column_chance: #extra_edge,
                        }
                    })
                }
            }
        }
        "minecraft:glowstone_blob" => {
            quote! { ConfiguredFeature::GlowstoneBlob(crate::generation::feature::features::glowstone_blob::GlowstoneBlobFeature {}) }
        }
        "minecraft:disk" => {
            let state_provider = value_to_block_state_provider(&config["state_provider"]);
            let target = value_to_block_predicate(&config["target"]);
            let radius = value_to_int_provider(&config["radius"]);
            let half_height = config["half_height"].as_i64().unwrap_or(1) as i32;
            quote! {
                ConfiguredFeature::Disk(crate::generation::feature::features::disk::DiskFeature {
                    state_provider: #state_provider,
                    target: #target,
                    radius: #radius,
                    half_height: #half_height,
                })
            }
        }
        "minecraft:basalt_columns" => {
            let height = value_to_int_provider(&config["height"]);
            let reach = value_to_int_provider(&config["reach"]);
            quote! {
                ConfiguredFeature::BasaltColumns(crate::generation::feature::features::basalt_columns::BasaltColumnsFeature {
                    height: #height,
                    reach: #reach,
                })
            }
        }
        "minecraft:basalt_pillar" => {
            quote! { ConfiguredFeature::BasaltPillar(crate::generation::feature::features::basalt_pillar::BasaltPillarFeature {}) }
        }
        "minecraft:block_blob" => {
            let state = value_to_block_state(&config["state"]);
            quote! {
                ConfiguredFeature::ForestRock(crate::generation::feature::features::forest_rock::ForestRockFeature {
                    state: #state,
                })
            }
        }
        "minecraft:freeze_top_layer" => {
            quote! { ConfiguredFeature::FreezeTopLayer(crate::generation::feature::features::freeze_top_layer::FreezeTopLayerFeature {}) }
        }
        "minecraft:ice_spike" => {
            quote! { ConfiguredFeature::IceSpike(crate::generation::feature::features::ice_spike::IceSpikeFeature {}) }
        }
        "minecraft:spike" => {
            quote! { ConfiguredFeature::IceSpike(crate::generation::feature::features::ice_spike::IceSpikeFeature {}) }
        }
        "minecraft:iceberg" => {
            let state = value_to_block_state_codec(&config["state"]);
            quote! { ConfiguredFeature::Iceberg(crate::generation::feature::features::iceberg::IcebergFeature { main_block: #state }) }
        }
        "minecraft:chorus_plant" => {
            quote! { ConfiguredFeature::ChorusPlant(crate::generation::feature::features::chorus_plant::ChorusPlantFeature {}) }
        }
        "minecraft:end_platform" => {
            quote! { ConfiguredFeature::EndPlatform(crate::generation::feature::features::end_platform::EndPlatformFeature) }
        }
        "minecraft:end_island" => {
            quote! { ConfiguredFeature::EndIsland(crate::generation::feature::features::end_island::EndIslandFeature {}) }
        }
        "minecraft:kelp" => {
            quote! { ConfiguredFeature::Kelp(crate::generation::feature::features::kelp::KelpFeature {}) }
        }

        "minecraft:fossil" => {
            let fossil_structures = config["fossil_structures"]
                .as_array()
                .expect("fossil_structures must be an array")
                .iter()
                .map(|value| {
                    let name = value
                        .as_str()
                        .expect("fossil structure names must be strings");
                    quote! { #name }
                });
            let overlay_structures = config["overlay_structures"]
                .as_array()
                .expect("overlay_structures must be an array")
                .iter()
                .map(|value| {
                    let name = value
                        .as_str()
                        .expect("fossil overlay structure names must be strings");
                    quote! { #name }
                });
            let fossil_processor = value_to_fossil_processor(&config["fossil_processors"]);
            let overlay_processor = value_to_fossil_processor(&config["overlay_processors"]);
            let max_empty_corners_allowed = config["max_empty_corners_allowed"]
                .as_u64()
                .expect("max_empty_corners_allowed must be an unsigned integer")
                as u8;

            quote! {
                ConfiguredFeature::Fossil(
                    crate::generation::feature::features::fossil::FossilFeature {
                        fossil_structures: vec![#(#fossil_structures),*],
                        overlay_structures: vec![#(#overlay_structures),*],
                        fossil_processor: #fossil_processor,
                        overlay_processor: #overlay_processor,
                        max_empty_corners_allowed: #max_empty_corners_allowed,
                    }
                )
            }
        }
        "minecraft:lake" => {
            let fluid = value_to_block_state_provider(&config["fluid"]);
            let barrier = value_to_block_state_provider(&config["barrier"]);
            quote! {
                ConfiguredFeature::Lake(crate::generation::feature::features::lake::LakeFeature {
                    fluid: #fluid,
                    barrier: #barrier,
                })
            }
        }
        "minecraft:huge_brown_mushroom" => {
            quote! { ConfiguredFeature::HugeBrownMushroom(crate::generation::feature::features::huge_brown_mushroom::HugeBrownMushroomFeature {}) }
        }
        "minecraft:huge_red_mushroom" => {
            quote! { ConfiguredFeature::HugeRedMushroom(crate::generation::feature::features::huge_red_mushroom::HugeRedMushroomFeature {}) }
        }
        "minecraft:vines" => {
            quote! { ConfiguredFeature::Vines(crate::generation::feature::features::vines::VinesFeature) }
        }
        "minecraft:root_system" => {
            let feature = value_to_inline_placed_feature(&config["feature"]);
            let required_vertical_space_for_tree = config["required_vertical_space_for_tree"]
                .as_i64()
                .unwrap_or(0) as i32;
            let root_radius = config["root_radius"].as_i64().unwrap_or(0) as i32;
            let root_replaceable = value_to_block_predicate(&config["root_replaceable"]);
            let root_state_provider = value_to_block_state_provider(&config["root_state_provider"]);
            let root_placement_attempts =
                config["root_placement_attempts"].as_i64().unwrap_or(0) as i32;
            let root_column_max_height =
                config["root_column_max_height"].as_i64().unwrap_or(0) as i32;
            let hanging_root_radius = config["hanging_root_radius"].as_i64().unwrap_or(0) as i32;
            let hanging_roots_vertical_span = config["hanging_roots_vertical_span"]
                .as_i64()
                .or(config["hanging_root_vertical_span"].as_i64())
                .unwrap_or(0) as i32;
            let hanging_root_state_provider =
                value_to_block_state_provider(&config["hanging_root_state_provider"]);
            let hanging_root_placement_attempts = config["hanging_root_placement_attempts"]
                .as_i64()
                .unwrap_or(0) as i32;
            let allowed_vertical_water_for_tree = config["allowed_vertical_water_for_tree"]
                .as_i64()
                .unwrap_or(0) as i32;
            let allowed_tree_position = value_to_block_predicate(&config["allowed_tree_position"]);

            quote! {
                ConfiguredFeature::RootSystem(crate::generation::feature::features::root_system::RootSystemFeature {
                    feature: Box::new(#feature),
                    required_vertical_space_for_tree: #required_vertical_space_for_tree,
                    root_radius: #root_radius,
                    root_replaceable: #root_replaceable,
                    root_state_provider: #root_state_provider,
                    root_placement_attempts: #root_placement_attempts,
                    root_column_max_height: #root_column_max_height,
                    hanging_root_radius: #hanging_root_radius,
                    hanging_roots_vertical_span: #hanging_roots_vertical_span,
                    hanging_root_state_provider: #hanging_root_state_provider,
                    hanging_root_placement_attempts: #hanging_root_placement_attempts,
                    allowed_vertical_water_for_tree: #allowed_vertical_water_for_tree,
                    allowed_tree_position: #allowed_tree_position,
                })
            }
        }
        "minecraft:multiface_growth" => {
            quote! { ConfiguredFeature::MultifaceGrowth(crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature {}) }
        }
        "minecraft:blue_ice" => {
            quote! { ConfiguredFeature::BlueIce(crate::generation::feature::features::blue_ice::BlueIceFeature {}) }
        }
        "minecraft:end_gateway" => {
            quote! { ConfiguredFeature::EndGateway(crate::generation::feature::features::end_gateway::EndGatewayFeature {}) }
        }
        "minecraft:coral_tree" => {
            quote! { ConfiguredFeature::CoralTree(crate::generation::feature::features::coral::coral_tree::CoralTreeFeature) }
        }
        "minecraft:coral_mushroom" => {
            quote! { ConfiguredFeature::CoralMushroom(crate::generation::feature::features::coral::coral_mushroom::CoralMushroomFeature) }
        }
        "minecraft:coral_claw" => {
            quote! { ConfiguredFeature::CoralClaw(crate::generation::feature::features::coral::coral_claw::CoralClawFeature) }
        }
        "minecraft:huge_fungus" => {
            quote! { ConfiguredFeature::HugeFungus(crate::generation::feature::features::huge_fungus::HugeFungusFeature {}) }
        }
        "minecraft:weeping_vines" => {
            quote! { ConfiguredFeature::WeepingVines(crate::generation::feature::features::weeping_vines::WeepingVinesFeature {}) }
        }
        "minecraft:twisting_vines" => {
            let spread_width = config["spread_width"].as_i64().unwrap_or(0) as i32;
            let spread_height = config["spread_height"].as_i64().unwrap_or(0) as i32;
            let max_height = config["max_height"].as_i64().unwrap_or(0) as i32;
            quote! {
                ConfiguredFeature::TwistingVines(crate::generation::feature::features::twisting_vines::TwistingVinesFeature {
                    spread_width: #spread_width,
                    spread_height: #spread_height,
                    max_height: #max_height,
                })
            }
        }
        "minecraft:delta_feature" => {
            quote! { ConfiguredFeature::DeltaFeature(crate::generation::feature::features::delta_feature::DeltaFeatureFeature {}) }
        }
        "minecraft:fill_layer" => {
            quote! { ConfiguredFeature::FillLayer(crate::generation::feature::features::fill_layer::FillLayerFeature {}) }
        }
        "minecraft:bonus_chest" => {
            quote! { ConfiguredFeature::BonusChest(crate::generation::feature::features::bonus_chest::BonusChestFeature {}) }
        }
        "minecraft:dripstone_cluster" | "minecraft:speleothem_cluster" => {
            quote! { ConfiguredFeature::DripstoneCluster(crate::generation::feature::features::drip_stone::cluster::DripstoneClusterFeature {}) }
        }
        "minecraft:sequence" | "minecraft:weighted_random_selector" => {
            quote! { ConfiguredFeature::NoOp }
        }
        "minecraft:large_dripstone" => {
            quote! { ConfiguredFeature::LargeDripstone(crate::generation::feature::features::drip_stone::large::LargeDripstoneFeature {}) }
        }
        "minecraft:sculk_patch" => {
            let charge_count = config["charge_count"].as_u64().unwrap() as i32;
            let amount_per_charge = config["amount_per_charge"].as_u64().unwrap() as i32;
            let spread_attempts = config["spread_attempts"].as_u64().unwrap() as i32;
            let growth_rounds = config["growth_rounds"].as_u64().unwrap() as i32;
            let spread_rounds = config["spread_rounds"].as_u64().unwrap() as i32;
            let extra_rare_growths = value_to_int_provider(&config["extra_rare_growths"]);
            let catalyst_chance = config["catalyst_chance"].as_f64().unwrap() as f32;
            quote! {
                ConfiguredFeature::SculkPatch(crate::generation::feature::features::sculk_patch::SculkPatchFeature {
                    charge_count: #charge_count,
                    amount_per_charge: #amount_per_charge,
                    spread_attempts: #spread_attempts,
                    growth_rounds: #growth_rounds,
                    spread_rounds: #spread_rounds,
                    extra_rare_growths: #extra_rare_growths,
                    catalyst_chance: #catalyst_chance,
                })
            }
        }
        "minecraft:block_pile" => {
            quote! { ConfiguredFeature::BlockPile(crate::generation::feature::features::block_pile::BlockPileFeature {}) }
        }
        "minecraft:replace_single_block" => {
            quote! { ConfiguredFeature::ReplaceSingleBlock(crate::generation::feature::features::replace_single_block::ReplaceSingleBlockFeature {}) }
        }
        "minecraft:void_start_platform" => {
            quote! { ConfiguredFeature::VoidStartPlatform(crate::generation::feature::features::void_start_platform::VoidStartPlatformFeature {}) }
        }
        "minecraft:desert_well" => {
            quote! { ConfiguredFeature::DesertWell(crate::generation::feature::features::desert_well::DesertWellFeature) }
        }
        other => {
            let msg = format!("unknown configured feature type: {other}");
            quote! { compile_error!(#msg) }
        }
    }
}

/// Converts a block-state-provider JSON object into its `BlockStateProvider` token stream.
///
/// # Arguments
/// – `v` – the JSON value for the provider, expected to contain a `"type"` field.
///
/// # Returns
/// A `TokenStream` for the appropriate `BlockStateProvider` variant; defaults to `BlockStateProvider::Simple` with air if the type is unrecognised.
fn value_to_block_state_provider(v: &Value) -> TokenStream {
    let type_str = v["type"].as_str().unwrap_or("");
    match type_str {
        "minecraft:simple_state_provider" => {
            let state = value_to_block_state(&v["state"]);
            quote! { BlockStateProvider::Simple(SimpleStateProvider { state: #state }) }
        }
        "minecraft:weighted_state_provider" => {
            let entries: Vec<TokenStream> = v["entries"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .map(|e| {
                            let data = value_to_block_state(&e["data"]);
                            let weight = e["weight"].as_i64().unwrap_or(1) as i32;
                            quote! { Weighted { data: #data, weight: #weight } }
                        })
                        .collect()
                })
                .unwrap_or_default();
            quote! {
                BlockStateProvider::Weighted(WeightedBlockStateProvider {
                    entries: vec![#(#entries),*],
                })
            }
        }
        "minecraft:rotated_block_provider" => {
            let state = value_to_block_state(&v["state"]);
            quote! { BlockStateProvider::Pillar(PillarBlockStateProvider { state: #state }) }
        }
        "minecraft:noise_provider" => {
            let base = value_to_noise_base(v);
            let states: Vec<TokenStream> = v["states"]
                .as_array()
                .map(|arr| arr.iter().map(value_to_block_state).collect())
                .unwrap_or_default();
            quote! {
                BlockStateProvider::NoiseProvider(NoiseBlockStateProvider {
                    base: #base,
                    states: vec![#(#states),*],
                })
            }
        }
        "minecraft:dual_noise_provider" => {
            let base_provider = value_to_noise_base(v);
            let states: Vec<TokenStream> = v["states"]
                .as_array()
                .map(|arr| arr.iter().map(value_to_block_state).collect())
                .unwrap_or_default();
            let base_noise_provider = quote! {
                NoiseBlockStateProvider { base: #base_provider, states: vec![#(#states),*] }
            };
            let v0 = v["variety"][0].as_u64().unwrap_or(2) as u32;
            let v1 = v["variety"][1].as_u64().unwrap_or(4) as u32;
            let slow_noise = value_to_dpnp(&v["slow_noise"]);
            let slow_scale = v["slow_scale"].as_f64().unwrap_or(1.0);
            quote! {
                BlockStateProvider::DualNoise(DualNoiseBlockStateProvider {
                    base: #base_noise_provider,
                    variety: [#v0, #v1],
                    slow_noise: #slow_noise,
                    slow_scale: #slow_scale,
                })
            }
        }
        "minecraft:noise_threshold_provider" => {
            let base = value_to_noise_base(v);
            let threshold = v["threshold"].as_f64().unwrap_or(0.0) as f32;
            let high_chance = v["high_chance"].as_f64().unwrap_or(0.0) as f32;
            let default = value_to_block_state(&v["default_state"]);
            let low: Vec<TokenStream> = v["low_states"]
                .as_array()
                .map(|a| a.iter().map(value_to_block_state).collect())
                .unwrap_or_default();
            let high: Vec<TokenStream> = v["high_states"]
                .as_array()
                .map(|a| a.iter().map(value_to_block_state).collect())
                .unwrap_or_default();
            quote! {
                BlockStateProvider::NoiseThreshold(NoiseThresholdBlockStateProvider {
                    base: #base,
                    threshold: #threshold,
                    high_chance: #high_chance,
                    default_state: #default,
                    low_states: vec![#(#low),*],
                    high_states: vec![#(#high),*],
                })
            }
        }
        "minecraft:randomized_int_state_provider" => {
            let src = value_to_block_state_provider(&v["source"]);
            let prop = v["property"].as_str().unwrap_or("");
            let vals = value_to_int_provider(&v["values"]);
            quote! {
                BlockStateProvider::RandomizedInt(RandomizedIntBlockStateProvider {
                    source: Box::new(#src),
                    property: #prop.to_string(),
                    values: #vals,
                })
            }
        }
        "minecraft:rule_based_state_provider" => {
            let fallback = if !v["fallback"].is_null() {
                let provider = value_to_block_state_provider(&v["fallback"]);
                quote! { Some(Box::new(#provider))}
            } else {
                quote! { None }
            };

            let rules: Vec<TokenStream> = v["rules"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .map(|rule| {
                            let if_true = value_to_block_predicate(&rule["if_true"]);
                            let then = value_to_block_state_provider(&rule["then"]);
                            quote! { BlockStateRule { if_true: #if_true, then: #then } }
                        })
                        .collect()
                })
                .unwrap_or_default();
            quote! {
                BlockStateProvider::Rule(RuleBasedBlockStateProvider {
                    fallback: #fallback,
                    rules: vec![#(#rules),*],
                })
            }
        }
        _ => {
            // Default to air
            quote! {
                BlockStateProvider::Simple(SimpleStateProvider {
                    state: pumpkin_data::Block::AIR.default_state,
                })
            }
        }
    }
}

/// Converts a noise-provider JSON object into a `NoiseBlockStateProviderBase` token stream.
///
/// # Arguments
/// – `v` – the JSON object containing `"seed"`, `"noise"`, and `"scale"` fields.
fn value_to_noise_base(v: &Value) -> TokenStream {
    let seed = v["seed"].as_i64().unwrap_or(0);
    let noise = value_to_dpnp(&v["noise"]);
    let scale = v["scale"].as_f64().unwrap_or(1.0) as f32;
    quote! {
        NoiseBlockStateProviderBase { seed: #seed, noise: #noise, scale: #scale }
    }
}

/// Converts a double-perlin-noise parameters JSON object into a `DoublePerlinNoiseParametersCodec` token stream.
///
/// # Arguments
/// – `v` – the JSON object containing `"firstOctave"` and `"amplitudes"` fields.
fn value_to_dpnp(v: &Value) -> TokenStream {
    let first_octave = v["firstOctave"].as_i64().unwrap_or(-7) as i32;
    let amplitudes: Vec<f64> = v["amplitudes"]
        .as_array()
        .map(|a| a.iter().filter_map(|x| x.as_f64()).collect())
        .unwrap_or_default();
    let mut min_octave = i32::MAX;
    let mut max_octave = i32::MIN;

    for (index, amp) in amplitudes.iter().enumerate() {
        if *amp != 0.0 {
            min_octave = i32::min(min_octave, index as i32);
            max_octave = i32::max(max_octave, index as i32);
        }
    }

    let octaves = max_octave - min_octave;
    let create_amp_val = 0.1f64 * (1.0f64 + 1.0f64 / (octaves + 1) as f64);
    let final_amplitude = 0.16666666666666666f64 / create_amp_val;
    quote! {
        DoublePerlinNoiseParametersCodec {
            first_octave: #first_octave,
            amplitudes: vec![#(#amplitudes),*],
            amplitude: #final_amplitude,
        }
    }
}

/// Converts a rule-test JSON object into its `RuleTest` enum variant token stream.
///
/// # Arguments
/// – `v` – the JSON object for the rule test, expected to contain a `"predicate_type"` field.
///
/// # Returns
/// A `TokenStream` for the appropriate `RuleTest` variant; emits a `compile_error!` for unrecognised types.
fn value_to_rule_test(v: &Value) -> TokenStream {
    let type_str = v["predicate_type"].as_str().unwrap_or("");
    match type_str {
        "minecraft:always_true" | "" => quote! { RuleTest::AlwaysTrue },
        "minecraft:block_match" => {
            let block = v["block"].as_str().unwrap_or("minecraft:stone");
            let name_stripped = block.strip_prefix("minecraft:").unwrap_or(block);
            let block_ident =
                quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_"));
            quote! { RuleTest::BlockMatch(BlockMatchRuleTest { block: pumpkin_data::BlockId::#block_ident }) }
        }
        "minecraft:blockstate_match" => {
            let state = value_to_block_state(&v["block_state"]);
            quote! { RuleTest::BlockStateMatch(BlockStateMatchRuleTest { block_state: #state.id }) }
        }
        "minecraft:tag_match" => {
            let tag = v["tag"].as_str().unwrap_or("");
            let tag_ident = quote::format_ident!("{}", tag.to_uppercase().replace([':', '-'], "_"));
            quote! { RuleTest::TagMatch(TagMatchRuleTest { tag: pumpkin_data::tag::Block::#tag_ident }) }
        }
        "minecraft:random_block_match" => {
            let block = v["block"].as_str().unwrap_or("minecraft:stone");
            let prob = v["probability"].as_f64().unwrap_or(0.5) as f32;
            let block_ident =
                quote::format_ident!("{}", block.to_uppercase().replace([':', '-'], "_"));
            quote! { RuleTest::RandomBlockMatch(RandomBlockMatchRuleTest { block: pumpkin_data::BlockId::#block_ident, probability: #prob }) }
        }
        "minecraft:random_blockstate_match" => {
            let state = value_to_block_state(&v["block_state"]);
            let prob = v["probability"].as_f64().unwrap_or(0.5) as f32;
            quote! { RuleTest::RandomBlockStateMatch(RandomBlockStateMatchRuleTest { block_state: #state.id, probability: #prob }) }
        }
        other => {
            let msg = format!("unknown rule test: {other}");
            quote! { compile_error!(#msg) }
        }
    }
}

/// Converts a block-list JSON value (string or array) into a `BlockWrapper` token stream.
///
/// # Arguments
/// – `v` – a JSON string (single block) or array of strings (multiple blocks).
///
/// # Returns
/// `BlockWrapper::Single` for a string value or `BlockWrapper::Multi` for an array; defaults to `BlockWrapper::Single("")` for other types.
fn value_to_block_wrapper(v: &Value) -> TokenStream {
    match v {
        Value::String(s) => quote! { BlockWrapper::Single(#s.to_string()) },
        Value::Array(arr) => {
            let items: Vec<TokenStream> = arr
                .iter()
                .filter_map(|s| s.as_str().map(|s| quote! { #s.to_string() }))
                .collect();
            quote! { BlockWrapper::Multi(vec![#(#items),*]) }
        }
        _ => quote! { BlockWrapper::Single(String::new()) },
    }
}

/// Converts a tree-feature config JSON object into a `TreeFeature` token stream.
///
/// # Arguments
/// – `config` – the `"config"` sub-object of a `minecraft:tree` configured feature JSON entry.
fn value_to_tree_feature(config: &Value) -> TokenStream {
    let trunk = value_to_block_state_provider(&config["trunk_provider"]);
    let trunk_placer = value_to_trunk_placer(&config["trunk_placer"]);
    let foliage = value_to_block_state_provider(&config["foliage_provider"]);
    let foliage_placer = value_to_foliage_placer(&config["foliage_placer"]);
    let min_size = value_to_feature_size(&config["minimum_size"]);
    let ignore_vines = config["ignore_vines"].as_bool().unwrap_or(true);
    let below_trunk_provider = value_to_block_state_provider(&config["below_trunk_provider"]);
    let decorators: Vec<TokenStream> = config["decorators"]
        .as_array()
        .map(|arr| arr.iter().map(value_to_tree_decorator).collect())
        .unwrap_or_default();
    let root_placer = match config.get("root_placer") {
        Some(v) if !v.is_null() => {
            let inner = value_to_root_placer(v);
            quote! { Some(#inner) }
        }
        _ => quote! { None },
    };
    quote! {
        TreeFeature {
            trunk_provider: #trunk,
            trunk_placer: #trunk_placer,
            foliage_provider: #foliage,
            foliage_placer: #foliage_placer,
            minimum_size: #min_size,
            ignore_vines: #ignore_vines,
            below_trunk_provider: #below_trunk_provider,
            decorators: vec![#(#decorators),*],
            root_placer: #root_placer,
        }
    }
}

fn value_to_root_placer(v: &Value) -> TokenStream {
    let type_str = v["type"].as_str().unwrap_or("");
    match type_str {
        "minecraft:mangrove_root_placer" => {
            let trunk_offset_y = value_to_int_provider(&v["trunk_offset_y"]);
            let root_provider = value_to_block_state_provider(&v["root_provider"]);
            let above = match v.get("above_root_placement") {
                Some(av) if !av.is_null() => {
                    let provider = value_to_block_state_provider(&av["above_root_provider"]);
                    let chance = av["above_root_placement_chance"].as_f64().unwrap_or(0.0) as f32;
                    quote! {
                        Some(AboveRootPlacement {
                            above_root_provider: #provider,
                            above_root_placement_chance: #chance,
                        })
                    }
                }
                _ => quote! { None },
            };
            let mrp = &v["mangrove_root_placement"];
            let can_grow_through = value_to_block_list(&mrp["can_grow_through"]);
            let muddy_roots_in = value_to_block_list(&mrp["muddy_roots_in"]);
            let muddy_roots_provider = value_to_block_state_provider(&mrp["muddy_roots_provider"]);
            let max_root_width = mrp["max_root_width"].as_i64().unwrap_or(8) as i32;
            let max_root_length = mrp["max_root_length"].as_i64().unwrap_or(15) as i32;
            let random_skew_chance = mrp["random_skew_chance"].as_f64().unwrap_or(0.0) as f32;
            quote! {
                RootPlacer::Mangrove(MangroveRootPlacer {
                    trunk_offset_y: #trunk_offset_y,
                    root_provider: #root_provider,
                    above_root_placement: #above,
                    mangrove_root_placement: MangroveRootPlacement {
                        can_grow_through: #can_grow_through,
                        muddy_roots_in: const { #muddy_roots_in },
                        muddy_roots_provider: #muddy_roots_provider,
                        max_root_width: #max_root_width,
                        max_root_length: #max_root_length,
                        random_skew_chance: #random_skew_chance,
                    },
                })
            }
        }
        other => {
            let msg = format!("Unknown root placer type: {other}");
            quote! { compile_error!(#msg) }
        }
    }
}

fn value_to_block_list(v: &Value) -> TokenStream {
    if let Some(tag) = v.as_str().and_then(|s| s.strip_prefix('#')) {
        let name = format!(
            "MINECRAFT_{}",
            tag.strip_prefix("minecraft:").unwrap_or(tag).to_uppercase()
        );
        let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
        return quote! { &pumpkin_data::tag::Block::#ident.1 };
    }
    let mut blocks = Vec::new();
    if let Some(arr) = v.as_array() {
        for b in arr {
            if let Some(s) = b.as_str() {
                let name = s.strip_prefix("minecraft:").unwrap_or(s).to_uppercase();
                let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
                blocks.push(quote! { pumpkin_data::BlockId::#ident.as_u16() });
            }
        }
    }
    quote! { &[#(#blocks),*] }
}

/// Converts a trunk-placer JSON object into a `TrunkPlacer` token stream.
///
/// # Arguments
/// – `v` – the `"trunk_placer"` sub-object from a tree config, containing `"type"`, `"base_height"`, `"height_rand_a"`, and `"height_rand_b"` fields.
fn value_to_trunk_placer(v: &Value) -> TokenStream {
    let base = v["base_height"].as_u64().unwrap_or(5) as u8;
    let rand_a = v["height_rand_a"].as_u64().unwrap_or(0) as u8;
    let rand_b = v["height_rand_b"].as_u64().unwrap_or(0) as u8;
    let type_str = v["type"].as_str().unwrap_or("");
    let trunk_type = match type_str {
        "minecraft:straight_trunk_placer" => quote! { TrunkType::Straight(StraightTrunkPlacer) },
        "minecraft:forking_trunk_placer" => quote! { TrunkType::Forking(ForkingTrunkPlacer) },
        "minecraft:giant_trunk_placer" => quote! { TrunkType::Giant(GiantTrunkPlacer) },
        "minecraft:mega_jungle_trunk_placer" => {
            quote! { TrunkType::MegaJungle(MegaJungleTrunkPlacer) }
        }
        "minecraft:dark_oak_trunk_placer" => quote! { TrunkType::DarkOak(DarkOakTrunkPlacer) },
        "minecraft:fancy_trunk_placer" => quote! { TrunkType::Fancy(FancyTrunkPlacer) },
        "minecraft:bending_trunk_placer" => {
            let min_h = v["min_height_for_leaves"].as_u64().unwrap_or(1) as u32;
            let bend = value_to_int_provider(&v["bend_length"]);
            quote! {
                TrunkType::Bending(BendingTrunkPlacer {
                    min_height_for_leaves: #min_h,
                    bend_length: #bend,
                })
            }
        }
        "minecraft:upwards_branching_trunk_placer" => {
            let extra_branch_steps = value_to_int_provider(&v["extra_branch_steps"]);
            let place_branch_per_log_probability = v["place_branch_per_log_probability"]
                .as_f64()
                .unwrap_or(0.0) as f32;
            let extra_branch_length = value_to_int_provider(&v["extra_branch_length"]);
            let can_grow_through = value_to_block_list(&v["can_grow_through"]);
            quote! {
                TrunkType::UpwardsBranching(UpwardsBranchingTrunkPlacer {
                    extra_branch_steps: #extra_branch_steps,
                    place_branch_per_log_probability: #place_branch_per_log_probability,
                    extra_branch_length: #extra_branch_length,
                    can_grow_through: #can_grow_through,
                })
            }
        }
        "minecraft:cherry_trunk_placer" => {
            let branch_count = value_to_int_provider(&v["branch_count"]);
            let branch_horizontal_length = value_to_int_provider(&v["branch_horizontal_length"]);
            let branch_start_offset_v = &v["branch_start_offset_from_top"];
            let min = branch_start_offset_v["min_inclusive"].as_i64().unwrap_or(0) as i32;
            let max = branch_start_offset_v["max_inclusive"].as_i64().unwrap_or(0) as i32;
            let branch_end_offset_from_top =
                value_to_int_provider(&v["branch_end_offset_from_top"]);
            quote! {
                TrunkType::Cherry(CherryTrunkPlacer {
                    count: #branch_count,
                    horizontal_length: #branch_horizontal_length,
                    start_offset_from_top: UniformIntProvider { min_inclusive: #min, max_inclusive: #max },
                    end_offset_from_top: #branch_end_offset_from_top,
                })
            }
        }
        _ => quote! { TrunkType::Straight(StraightTrunkPlacer) },
    };
    quote! {
        TrunkPlacer {
            base_height: #base,
            height_rand_a: #rand_a,
            height_rand_b: #rand_b,
            r#type: #trunk_type,
        }
    }
}

/// Converts a foliage-placer JSON object into a `FoliagePlacer` token stream.
///
/// # Arguments
/// – `v` – the `"foliage_placer"` sub-object from a tree config, containing `"type"`, `"radius"`, and `"offset"` fields.
fn value_to_foliage_placer(v: &Value) -> TokenStream {
    let radius = value_to_int_provider(&v["radius"]);
    let offset = value_to_int_provider(&v["offset"]);
    let type_str = v["type"].as_str().unwrap_or("");
    let foliage_type = match type_str {
        "minecraft:blob_foliage_placer" => {
            let h = v["height"].as_i64().unwrap_or(3) as i32;
            quote! { FoliageType::Blob(BlobFoliagePlacer { height: #h }) }
        }
        "minecraft:spruce_foliage_placer" => {
            let th = value_to_int_provider(&v["trunk_height"]);
            quote! { FoliageType::Spruce(SpruceFoliagePlacer { trunk_height: #th }) }
        }
        "minecraft:pine_foliage_placer" => {
            let h = value_to_int_provider(&v["height"]);
            quote! { FoliageType::Pine(PineFoliagePlacer { height: #h }) }
        }
        "minecraft:acacia_foliage_placer" => {
            quote! { FoliageType::Acacia(AcaciaFoliagePlacer) }
        }
        "minecraft:bush_foliage_placer" => {
            let h = v["height"].as_i64().unwrap_or(2) as i32;
            quote! { FoliageType::Bush(BushFoliagePlacer { height: #h }) }
        }
        "minecraft:fancy_foliage_placer" => {
            let h = v["height"].as_i64().unwrap_or(4) as i32;
            quote! { FoliageType::Fancy(LargeOakFoliagePlacer { height: #h }) }
        }
        "minecraft:jungle_foliage_placer" => {
            let h = v["height"].as_i64().unwrap_or(2) as i32;
            quote! { FoliageType::Jungle(JungleFoliagePlacer { height: #h }) }
        }
        "minecraft:mega_pine_foliage_placer" => {
            let ch = value_to_int_provider(&v["crown_height"]);
            quote! { FoliageType::MegaPine(MegaPineFoliagePlacer { crown_height: #ch }) }
        }
        "minecraft:dark_oak_foliage_placer" => {
            quote! { FoliageType::DarkOak(DarkOakFoliagePlacer) }
        }
        "minecraft:random_spread_foliage_placer" => {
            let fh = value_to_int_provider(&v["foliage_height"]);
            let lpa = v["leaf_placement_attempts"].as_i64().unwrap_or(128) as i32;
            quote! {
                FoliageType::RandomSpread(RandomSpreadFoliagePlacer {
                    foliage_height: #fh,
                    leaf_placement_attempts: #lpa,
                })
            }
        }
        "minecraft:cherry_foliage_placer" => {
            let h = value_to_int_provider(&v["height"]);
            let wblh = v["wide_bottom_layer_hole_chance"].as_f64().unwrap_or(0.0) as f32;
            let ch = v["corner_hole_chance"].as_f64().unwrap_or(0.0) as f32;
            let hlc = v["hanging_leaves_chance"].as_f64().unwrap_or(0.0) as f32;
            let hlec = v["hanging_leaves_extension_chance"].as_f64().unwrap_or(0.0) as f32;
            quote! {
                FoliageType::Cherry(CherryFoliagePlacer {
                    height: #h,
                    wide_bottom_layer_hole_chance: #wblh,
                    corner_hole_chance: #ch,
                    hanging_leaves_chance: #hlc,
                    hanging_leaves_extension_chance: #hlec,
                })
            }
        }
        _ => {
            let h = v["height"].as_i64().unwrap_or(3) as i32;
            quote! { FoliageType::Blob(BlobFoliagePlacer { height: #h }) }
        }
    };
    quote! {
        FoliagePlacer { radius: #radius, offset: #offset, r#type: #foliage_type }
    }
}

/// Converts a feature-size JSON object into a `FeatureSize` token stream.
///
/// # Arguments
/// – `v` – the `"minimum_size"` sub-object from a tree config, containing a `"type"` field and size parameters.
fn value_to_feature_size(v: &Value) -> TokenStream {
    let min_clipped = if v["min_clipped_height"].is_number() {
        let val = v["min_clipped_height"].as_u64().unwrap_or(0) as u8;
        quote! { Some(#val) }
    } else {
        quote! { None }
    };
    let type_str = v["type"].as_str().unwrap_or("");
    let size_type = match type_str {
        "minecraft:two_layers_feature_size" => {
            let limit = v["limit"].as_u64().unwrap_or(1) as u8;
            let lower = v["lower_size"].as_u64().unwrap_or(0) as u8;
            let upper = v["upper_size"].as_u64().unwrap_or(1) as u8;
            quote! {
                FeatureSizeType::TwoLayersFeatureSize(TwoLayersFeatureSize {
                    limit: #limit,
                    lower_size: #lower,
                    upper_size: #upper,
                })
            }
        }
        "minecraft:three_layers_feature_size" => {
            let limit = v["limit"].as_u64().unwrap_or(1) as u8;
            let upper_limit = v["upper_limit"].as_u64().unwrap_or(1) as u8;
            let lower = v["lower_size"].as_u64().unwrap_or(0) as u8;
            let middle = v["middle_size"].as_u64().unwrap_or(1) as u8;
            let upper = v["upper_size"].as_u64().unwrap_or(1) as u8;
            quote! {
                FeatureSizeType::ThreeLayersFeatureSize(ThreeLayersFeatureSize {
                    limit: #limit,
                    upper_limit: #upper_limit,
                    lower_size: #lower,
                    middle_size: #middle,
                    upper_size: #upper,
                })
            }
        }
        _ => {
            quote! {
                FeatureSizeType::TwoLayersFeatureSize(TwoLayersFeatureSize {
                    limit: 1,
                    lower_size: 0,
                    upper_size: 1,
                })
            }
        }
    };
    quote! {
        FeatureSize { min_clipped_height: #min_clipped, r#type: #size_type }
    }
}

/// Converts a tree-decorator JSON object into its `TreeDecorator` enum variant token stream.
///
/// # Arguments
/// – `v` – a JSON object for one decorator entry, expected to contain a `"type"` field.
///
/// # Returns
/// A `TokenStream` for the appropriate `TreeDecorator` variant; emits a `compile_error!` for unrecognised types.
fn value_to_tree_decorator(v: &Value) -> TokenStream {
    let type_str = v["type"].as_str().unwrap_or("");
    match type_str {
        "minecraft:trunk_vine" => quote! { TreeDecorator::TrunkVine(TrunkVineTreeDecorator) },
        "minecraft:leave_vine" => {
            let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
            quote! { TreeDecorator::LeaveVine(LeavesVineTreeDecorator { probability: #prob }) }
        }
        "minecraft:cocoa" => quote! { TreeDecorator::Cocoa(CocoaTreeDecorator {}) },
        "minecraft:beehive" => {
            let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
            quote! { TreeDecorator::Beehive(BeehiveTreeDecorator { probability: #prob }) }
        }
        "minecraft:alter_ground" => {
            quote! { TreeDecorator::AlterGround(AlterGroundTreeDecorator {}) }
        }
        "minecraft:attached_to_logs" => {
            let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
            let bp = value_to_block_state_provider(&v["block_provider"]);
            let dirs: Vec<TokenStream> = v["directions"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .filter_map(|d| d.as_str().map(value_to_block_direction))
                        .collect()
                })
                .unwrap_or_default();
            quote! {
                TreeDecorator::AttachedToLogs(AttachedToLogsTreeDecorator {
                    probability: #prob,
                    block_provider: #bp,
                    directions: vec![#(#dirs),*],
                })
            }
        }
        "minecraft:attached_to_leaves" => {
            quote! { TreeDecorator::AttachedToLeaves(AttachedToLeavesTreeDecorator {}) }
        }
        "minecraft:place_on_ground" => {
            let tries = v["tries"].as_i64().unwrap_or(1) as i32;
            let radius = v["radius"].as_i64().unwrap_or(1) as i32;
            let height = v["height"].as_i64().unwrap_or(1) as i32;
            let bsp = value_to_block_state_provider(&v["block_state_provider"]);
            quote! {
                TreeDecorator::PlaceOnGround(PlaceOnGroundTreeDecorator {
                    tries: #tries,
                    radius: #radius,
                    height: #height,
                    block_state_provider: #bsp,
                })
            }
        }
        "minecraft:creaking_heart" => {
            quote! { TreeDecorator::CreakingHeart(CreakingHeartTreeDecorator {}) }
        }
        "minecraft:pale_moss" => {
            quote! { TreeDecorator::PaleMoss(PaleMossTreeDecorator {}) }
        }
        other => {
            let msg = format!("unknown tree decorator: {other}");
            quote! { compile_error!(#msg) }
        }
    }
}

/// Converts an inline placed-feature JSON object into a `PlacedFeature` token stream.
///
/// # Arguments
/// – `v` – the JSON object containing `"feature"` and `"placement"` fields.
fn value_to_inline_placed_feature(v: &Value) -> TokenStream {
    let feature = value_to_feature_ref(&v["feature"]);
    let placement_arr = v["placement"]
        .as_array()
        .map(|a| a.as_slice())
        .unwrap_or(&[]);
    let placement: Vec<TokenStream> = placement_arr
        .iter()
        .map(value_to_placement_modifier_cf)
        .collect();
    quote! {
        PlacedFeature {
            feature: #feature,
            placement: vec![#(#placement),*],
        }
    }
}

/// Converts a placed-feature reference JSON value into a `PlacedFeatureWrapper` token stream.
///
/// # Arguments
/// – `v` – a JSON string (named reference) or object (inline placed feature).
///
/// # Returns
/// `PlacedFeatureWrapper::Named` for a string value or `PlacedFeatureWrapper::Direct` for an inline object; defaults to `PlacedFeatureWrapper::Named(pumpkin_data::placed_feature::PlacedFeature::Acacia)` for other types.
fn value_to_placed_feature_wrapper(v: &Value) -> TokenStream {
    match v {
        Value::String(s) => {
            let name = s.strip_prefix("minecraft:").unwrap_or(s);
            let variant_name = format_ident!("{}", name.to_pascal_case());
            quote! { PlacedFeatureWrapper::Named(pumpkin_data::placed_feature::PlacedFeature::#variant_name) }
        }
        Value::Object(_) => {
            // It might be a PlacedFeature object
            let pf = value_to_inline_placed_feature(v);
            quote! { PlacedFeatureWrapper::Direct(#pf) }
        }
        _ => {
            quote! { PlacedFeatureWrapper::Named(pumpkin_data::placed_feature::PlacedFeature::Acacia) }
        }
    }
}

/// Converts a feature reference JSON value into a `Feature` enum token stream.
///
/// # Arguments
/// – `v` – a JSON string (named reference) or object (inline configured feature).
///
/// # Returns
/// `Feature::Named` for a string value or `Feature::Inlined` for an object; defaults to `Feature::Named(pumpkin_data::configured_feature::ConfiguredFeature::NoOp)` for other types.
fn value_to_feature_ref(v: &Value) -> TokenStream {
    match v {
        Value::String(s) => {
            let name = s.strip_prefix("minecraft:").unwrap_or(s);
            let variant_name = format_ident!("{}", name.to_pascal_case());
            quote! { Feature::Named(pumpkin_data::configured_feature::ConfiguredFeature::#variant_name) }
        }
        Value::Object(_) => {
            let cf = value_to_configured_feature(v);
            quote! { Feature::Inlined(Box::new(#cf)) }
        }
        _ => quote! { Feature::Named(pumpkin_data::configured_feature::ConfiguredFeature::NoOp) },
    }
}

// Placement modifier in context of configured_features (same as placed_feature but re-imported)
/// Converts a placement-modifier JSON object into its `PlacementModifier` token stream within the configured-feature context.
///
/// # Arguments
/// – `v` – a JSON object for one modifier entry, expected to contain a `"type"` field.
///
/// # Returns
/// A `TokenStream` for the appropriate `PlacementModifier` variant; defaults to `PlacementModifier::Biome` for unrecognised types.
fn value_to_placement_modifier_cf(v: &Value) -> TokenStream {
    let type_str = v["type"].as_str().unwrap_or("");
    match type_str {
        "minecraft:biome" => quote! { PlacementModifier::Biome(BiomePlacementModifier) },
        "minecraft:in_square" => quote! { PlacementModifier::InSquare(SquarePlacementModifier) },
        "minecraft:fixed_placement" => {
            let positions = v["positions"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .map(|p| {
                            let coords = p.as_array().unwrap();
                            let x = coords[0].as_i64().unwrap_or(0) as i32;
                            let y = coords[1].as_i64().unwrap_or(0) as i32;
                            let z = coords[2].as_i64().unwrap_or(0) as i32;
                            quote! { BlockPos::new(#x, #y, #z) }
                        })
                        .collect::<Vec<_>>()
                })
                .unwrap_or_default();
            quote! { PlacementModifier::FixedPlacement(vec![#(#positions),*]) }
        }
        "minecraft:heightmap" => {
            let hm = crate::placed_feature::value_to_height_map(
                v["heightmap"].as_str().unwrap_or("MOTION_BLOCKING"),
            );
            quote! { PlacementModifier::Heightmap(HeightmapPlacementModifier { heightmap: #hm }) }
        }
        "minecraft:height_range" => {
            let h = value_to_height_provider(&v["height"]);
            quote! { PlacementModifier::HeightRange(HeightRangePlacementModifier { height: #h }) }
        }
        "minecraft:count" => {
            let c = value_to_int_provider(&v["count"]);
            quote! { PlacementModifier::Count(CountPlacementModifier { count: #c }) }
        }
        "minecraft:count_on_every_layer" => {
            let c = value_to_int_provider(&v["count"]);
            quote! { PlacementModifier::CountOnEveryLayer(CountOnEveryLayerPlacementModifier { count: #c }) }
        }
        "minecraft:rarity_filter" => {
            let ch = v["chance"].as_u64().unwrap_or(1) as u32;
            quote! { PlacementModifier::RarityFilter(RarityFilterPlacementModifier { chance: #ch }) }
        }
        "minecraft:block_predicate_filter" => {
            let pred = value_to_block_predicate(&v["predicate"]);
            quote! { PlacementModifier::BlockPredicateFilter(BlockFilterPlacementModifier { predicate: #pred }) }
        }
        "minecraft:surface_relative_threshold_filter" => {
            let hm = crate::placed_feature::value_to_height_map(
                v["heightmap"].as_str().unwrap_or("MOTION_BLOCKING"),
            );
            let mn = if v["min_inclusive"].is_number() {
                let x = v["min_inclusive"].as_i64().unwrap() as i32;
                quote! {Some(#x)}
            } else {
                quote! {None}
            };
            let mx = if v["max_inclusive"].is_number() {
                let x = v["max_inclusive"].as_i64().unwrap() as i32;
                quote! {Some(#x)}
            } else {
                quote! {None}
            };
            quote! { PlacementModifier::SurfaceRelativeThresholdFilter(SurfaceThresholdFilterPlacementModifier { heightmap: #hm, min_inclusive: #mn, max_inclusive: #mx }) }
        }
        "minecraft:surface_water_depth_filter" => {
            let d = v["max_water_depth"].as_i64().unwrap_or(0) as i32;
            quote! { PlacementModifier::SurfaceWaterDepthFilter(SurfaceWaterDepthFilterPlacementModifier { max_water_depth: #d }) }
        }
        "minecraft:random_offset" => {
            let xz = value_to_int_provider(&v["xz_spread"]);
            let y = value_to_int_provider(&v["y_spread"]);
            quote! { PlacementModifier::RandomOffset(RandomOffsetPlacementModifier { xz_spread: #xz, y_spread: #y }) }
        }
        "minecraft:noise_based_count" => {
            let r = v["noise_to_count_ratio"].as_i64().unwrap_or(0) as i32;
            let f = v["noise_factor"].as_f64().unwrap_or(1.0);
            let o = v["noise_offset"].as_f64().unwrap_or(0.0);
            quote! { PlacementModifier::NoiseBasedCount(NoiseBasedCountPlacementModifier { to_count_ratio: #r, factor: #f, offset: #o }) }
        }
        "minecraft:noise_threshold_count" => {
            let l = v["noise_level"].as_f64().unwrap_or(0.0);
            let b = v["below_noise"].as_i64().unwrap_or(0) as i32;
            let a = v["above_noise"].as_i64().unwrap_or(0) as i32;
            quote! { PlacementModifier::NoiseThresholdCount(NoiseThresholdCountPlacementModifier { noise_level: #l, below_noise: #b, above_noise: #a }) }
        }
        "minecraft:environment_scan" => {
            let dir = value_to_block_direction(v["direction_of_search"].as_str().unwrap_or("down"));
            let tc = value_to_block_predicate(&v["target_condition"]);
            let asc = if v["allowed_search_condition"].is_object() {
                let p = value_to_block_predicate(&v["allowed_search_condition"]);
                quote! { Some(#p) }
            } else {
                quote! { None }
            };
            let steps = v["max_steps"].as_i64().unwrap_or(1) as i32;
            quote! { PlacementModifier::EnvironmentScan(EnvironmentScanPlacementModifier { direction_of_search: #dir, target_condition: #tc, allowed_search_condition: #asc, max_steps: #steps }) }
        }
        _ => quote! { PlacementModifier::Biome(BiomePlacementModifier) },
    }
}
