use crate::block::blocks::amethyst::AmethystBlock;
use crate::block::blocks::anvil::AnvilBlock;
use crate::block::blocks::banners::BannerBlock;
use crate::block::blocks::barrel::BarrelBlock;
use crate::block::blocks::barrier::BarrierBlock;
use crate::block::blocks::beacon::BeaconBlock;
use crate::block::blocks::bed::BedBlock;
use crate::block::blocks::beehive::BeehiveBlock;
use crate::block::blocks::brewing_stand::BrewingStandBlock;
use crate::block::blocks::bubble_column::BubbleColumnBlock;
use crate::block::blocks::cake::CakeBlock;
use crate::block::blocks::campfire::CampfireBlock;
use crate::block::blocks::candle_cakes::CandleCakeBlock;
use crate::block::blocks::candles::CandleBlock;
use crate::block::blocks::carpet::{CarpetBlock, MossCarpetBlock, PaleMossCarpetBlock};
use crate::block::blocks::carved_pumpkin::CarvedPumpkinBlock;
use crate::block::blocks::cauldron::CauldronBlock;
use crate::block::blocks::chests::{ChestBlock, CopperChestBlock, TrappedChestBlock};
use crate::block::blocks::chiseled_bookshelf::ChiseledBookshelfBlock;
use crate::block::blocks::command::CommandBlock;
use crate::block::blocks::composter::ComposterBlock;
use crate::block::blocks::conduit::ConduitBlock;
use crate::block::blocks::coral::coral_block::CoralBlock;
use crate::block::blocks::coral::coral_fan::CoralFanBlock;
use crate::block::blocks::coral::coral_plant::CoralPlantBlock;
use crate::block::blocks::creaking_heart::CreakingHeartBlock;
use crate::block::blocks::decorated_pot::DecoratedPotBlock;
use crate::block::blocks::dirt_path::DirtPathBlock;
use crate::block::blocks::doors::DoorBlock;
use crate::block::blocks::dripstone::DripstoneBlock;
use crate::block::blocks::end_portal::EndPortalBlock;
use crate::block::blocks::end_portal_frame::EndPortalFrameBlock;
use crate::block::blocks::falling::FallingBlock;
use crate::block::blocks::farmland::FarmlandBlock;
use crate::block::blocks::fence_gates::FenceGateBlock;
use crate::block::blocks::fences::FenceBlock;
use crate::block::blocks::fire::fire::FireBlock;
use crate::block::blocks::fire::soul_fire::SoulFireBlock;
use crate::block::blocks::flower_pots::FlowerPotBlock;
use crate::block::blocks::furnace::FurnaceBlock;
use crate::block::blocks::glass_panes::GlassPaneBlock;
use crate::block::blocks::glazed_terracotta::GlazedTerracottaBlock;
use crate::block::blocks::grass_block::GrassBlock;
use crate::block::blocks::grindstone::GrindstoneBlock;
use crate::block::blocks::hay::HayBlock;
use crate::block::blocks::infested::InfestedBlock;
use crate::block::blocks::iron_bars::IronBarsBlock;
use crate::block::blocks::jigsaw::JigsawBlock;
use crate::block::blocks::logs::LogBlock;
use crate::block::blocks::magma::MagmaBlock;
use crate::block::blocks::mangrove_roots::MangroveRootsBlock;
use crate::block::blocks::nether_portal::NetherPortalBlock;
use crate::block::blocks::note::NoteBlock;
use crate::block::blocks::piston::piston::PistonBlock;
use crate::block::blocks::piston::piston_extension::PistonExtensionBlock;
use crate::block::blocks::piston::piston_head::PistonHeadBlock;
use crate::block::blocks::plant::bamboo::BambooBlock;
use crate::block::blocks::plant::bamboo_sapling::BambooSaplingBlock;
use crate::block::blocks::plant::big_dripleaf::BigDripleafBlock;
use crate::block::blocks::plant::big_dripleaf_stem::BigDripleafStemBlock;
use crate::block::blocks::plant::bush::BushBlock;
use crate::block::blocks::plant::cactus::CactusBlock;
use crate::block::blocks::plant::cactus_flower::CactusFlowerBlock;
use crate::block::blocks::plant::chorus_flower::ChorusFlowerBlock;
use crate::block::blocks::plant::chorus_plant::ChorusPlantBlock;
use crate::block::blocks::plant::crop::beetroot::BeetrootBlock;
use crate::block::blocks::plant::crop::carrot::CarrotBlock;
use crate::block::blocks::plant::crop::nether_wart::NetherWartBlock;
use crate::block::blocks::plant::crop::potatoes::PotatoBlock;
use crate::block::blocks::plant::crop::sweet_berry_bush::SweetBerryBushBlock;
use crate::block::blocks::plant::crop::torch_flower::TorchFlowerBlock;
use crate::block::blocks::plant::crop::wheat::WheatBlock;
use crate::block::blocks::plant::dry_vegetation::DryVegetationBlock;
use crate::block::blocks::plant::flower::FlowerBlock;
use crate::block::blocks::plant::flowerbed::FlowerbedBlock;
use crate::block::blocks::plant::fungus::FungusBlock;
use crate::block::blocks::plant::kelp::KelpBlock;
use crate::block::blocks::plant::leaf_litter::LeafLitterBlock;
use crate::block::blocks::plant::lily_pad::LilyPadBlock;
use crate::block::blocks::plant::mushroom_plant::MushroomPlantBlock;
use crate::block::blocks::plant::nether_sprouts::NetherSproutsBlock;
use crate::block::blocks::plant::roots::RootsBlock;
use crate::block::blocks::plant::sapling::SaplingBlock;
use crate::block::blocks::plant::sea_pickles::SeaPickleBlock;
use crate::block::blocks::plant::seagrass::SeaGrassBlock;
use crate::block::blocks::plant::short_plant::ShortPlantBlock;
use crate::block::blocks::plant::small_dripleaf::SmallDripleafBlock;
use crate::block::blocks::plant::spore_blossom::SporeBlossomBlock;
use crate::block::blocks::plant::sugar_cane::SugarCaneBlock;
use crate::block::blocks::plant::tall_plant::TallPlantBlock;
use crate::block::blocks::plant::tall_seagrass::TallSeaGrassBlock;
use crate::block::blocks::plant::twisting_vines::TwistingVinesBlock;
use crate::block::blocks::plant::weeping_vines::WeepingVinesBlock;
use crate::block::blocks::plant::wither_rose::WitherRoseBlock;
use crate::block::blocks::powder_snow::PowderSnowBlock;
use crate::block::blocks::pumpkin::PumpkinBlock;
use crate::block::blocks::redstone::bell::BellBlock;
use crate::block::blocks::redstone::buttons::ButtonBlock;
use crate::block::blocks::redstone::comparator::ComparatorBlock;
use crate::block::blocks::redstone::copper_bulb::CopperBulbBlock;
use crate::block::blocks::redstone::crafter::CrafterBlock;
use crate::block::blocks::redstone::daylight_detector::DaylightDetectorBlock;
use crate::block::blocks::redstone::dispenser::DispenserBlock;
use crate::block::blocks::redstone::dropper::DropperBlock;
use crate::block::blocks::redstone::lever::LeverBlock;
use crate::block::blocks::redstone::lightning_rod::LightningRodBlock;
use crate::block::blocks::redstone::observer::ObserverBlock;
use crate::block::blocks::redstone::pressure_plate::plate::PressurePlateBlock;
use crate::block::blocks::redstone::pressure_plate::weighted::WeightedPressurePlateBlock;
use crate::block::blocks::redstone::rails::activator_rail::ActivatorRailBlock;
use crate::block::blocks::redstone::rails::detector_rail::DetectorRailBlock;
use crate::block::blocks::redstone::rails::powered_rail::PoweredRailBlock;
use crate::block::blocks::redstone::rails::rail::RailBlock;
use crate::block::blocks::redstone::redstone_block::RedstoneBlock;
use crate::block::blocks::redstone::redstone_lamp::RedstoneLamp;
use crate::block::blocks::redstone::redstone_ore::RedstoneOreBlock;
use crate::block::blocks::redstone::redstone_torch::RedstoneTorchBlock;
use crate::block::blocks::redstone::redstone_wire::RedstoneWireBlock;
use crate::block::blocks::redstone::repeater::RepeaterBlock;
use crate::block::blocks::redstone::sculk_sensor::SculkSensorBlock;
use crate::block::blocks::redstone::target_block::TargetBlock;
use crate::block::blocks::redstone::tripwire::TripwireBlock;
use crate::block::blocks::redstone::tripwire_hook::TripwireHookBlock;
use crate::block::blocks::sculk::sculk_catalyst::SculkCatalystBlock;
use crate::block::blocks::sculk::sculk_shrieker::SculkShriekerBlock;
use crate::block::blocks::sculk::sculk_vein::SculkVeinBlock;
use crate::block::blocks::shelf::ShelfBlock;
use crate::block::blocks::signs::SignBlock;
use crate::block::blocks::slabs::SlabBlock;
use crate::block::blocks::slime::SlimeBlock;
use crate::block::blocks::snow::LayeredSnowBlock;
use crate::block::blocks::sniffer_egg::SnifferEggBlock;
use crate::block::blocks::spawner::SpawnerBlock;
use crate::block::blocks::sponge::{SpongeBlock, WetSpongeBlock};
use crate::block::blocks::stairs::StairBlock;
use crate::block::blocks::tnt::TNTBlock;
use crate::block::blocks::torches::TorchBlock;
use crate::block::blocks::trapdoor::TrapDoorBlock;
use crate::block::blocks::trial_spawner::TrialSpawnerBlock;
use crate::block::blocks::vault::VaultBlock;
use crate::block::blocks::vine::VineBlock;
use crate::block::blocks::walls::WallBlock;
use crate::block::blocks::wither_skull::WitherSkeletonSkullBlock;
use crate::block::fluid::lava::FlowingLava;
use crate::block::fluid::water::FlowingWater;
use crate::block::{
    BlockBehaviour, BlockHitResult, BlockMetadata, FluidMetadata, GetInsideCollisionShapeArgs,
    OnEntityCollisionArgs, OnLandedUponArgs, UpdateEntityMovementAfterFallOnArgs,
    stop_vertical_movement_after_fall,
};
use crate::entity::EntityBase;
use crate::entity::player::Player;
use crate::server::Server;
use crate::world::World;
use pumpkin_data::BlockStateId;
use pumpkin_data::block_rotation::{Mirror, Rotation};
use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::fluid::Fluid;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::{Block, BlockDirection, BlockId, BlockState};
use pumpkin_protocol::java::server::play::SUseItemOn;
use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::{BlockAccessor, BlockFlags};
use rustc_hash::FxHashMap;
use std::sync::Arc;

use super::BlockIsReplacing;
use super::blocks::plant::crop::gourds::attached_stem::AttachedStemBlock;
use super::blocks::plant::crop::gourds::stem::StemBlock;
use super::fluid::FluidBehaviour;
use super::{
    BrokenArgs, CanPlaceAtArgs, CanUpdateAtArgs, EmitsRedstonePowerArgs, ExplodeArgs,
    GetRedstonePowerArgs, GetStateForNeighborUpdateArgs, NormalUseArgs, OnNeighborUpdateArgs,
    OnPlaceArgs, OnStateReplacedArgs, OnSyncedBlockEventArgs, PlacedArgs, PlayerPlacedArgs,
    PrepareArgs, UseWithItemArgs,
};
use crate::block::OnEntityStepArgs;
use crate::block::blocks::blast_furnace::BlastFurnaceBlock;
use crate::block::blocks::chain::ChainBlock;
use crate::block::blocks::cobweb::CobwebBlock;
use crate::block::blocks::crafting_table::CraftingTableBlock;
use crate::block::blocks::dragon_egg::DragonEggBlock;
use crate::block::blocks::enchanting_table::EnchantingTableBlock;
use crate::block::blocks::end_rod::EndRodBlock;
use crate::block::blocks::ender_chest::EnderChestBlock;
use crate::block::blocks::hopper::HopperBlock;
use crate::block::blocks::jukebox::JukeboxBlock;
use crate::block::blocks::ladder::LadderBlock;
use crate::block::blocks::lanterns::LanternBlock;
use crate::block::blocks::lectern::LecternBlock;
use crate::block::blocks::respawn_anchor::RespawnAnchorBlock;
use crate::block::blocks::shulker_box::ShulkerBoxBlock;
use crate::block::blocks::skull_block::SkullBlock;
use crate::block::blocks::smoker::SmokerBlock;
use crate::block::blocks::stonecutter::StonecutterBlock;

#[must_use]
#[expect(clippy::too_many_lines)]
pub fn default_registry() -> Arc<BlockRegistry> {
    let mut manager = BlockRegistry::default();

    // Blocks
    manager.register(AnvilBlock);
    manager.register(BeaconBlock);
    manager.register(BedBlock);
    manager.register(SaplingBlock);
    manager.register(CactusBlock);
    manager.register(ChorusFlowerBlock);
    manager.register(ChorusPlantBlock);
    manager.register(CarpetBlock);
    manager.register(CarvedPumpkinBlock);
    manager.register(WitherSkeletonSkullBlock);
    manager.register(CampfireBlock);
    manager.register(MossCarpetBlock);
    manager.register(PaleMossCarpetBlock);
    manager.register(ChestBlock);
    manager.register(TrappedChestBlock);
    manager.register(CopperChestBlock);
    manager.register(EnderChestBlock);
    manager.register(CraftingTableBlock);
    manager.register(EnchantingTableBlock);
    manager.register(DirtPathBlock);
    manager.register(DoorBlock);
    manager.register(FarmlandBlock);
    manager.register(FenceGateBlock);
    manager.register(FenceBlock);
    manager.register(FlowerPotBlock);
    manager.register(FurnaceBlock);
    manager.register(BrewingStandBlock);
    manager.register(BlastFurnaceBlock);
    manager.register(SmokerBlock);
    manager.register(GlassPaneBlock);
    manager.register(GlazedTerracottaBlock);
    manager.register(HayBlock);
    manager.register(GrindstoneBlock);
    manager.register(IronBarsBlock);
    manager.register(InfestedBlock);
    manager.register(JukeboxBlock);
    manager.register(LogBlock);
    manager.register(BambooBlock);
    manager.register(BambooSaplingBlock);
    manager.register(BannerBlock);
    manager.register(SignBlock);
    manager.register(SlabBlock);
    manager.register(SlimeBlock);
    manager.register(StairBlock);
    manager.register(StonecutterBlock);
    manager.register(ShortPlantBlock);
    manager.register(DryVegetationBlock);
    manager.register(LilyPadBlock);
    manager.register(SugarCaneBlock);
    manager.register(VineBlock);
    manager.register(TNTBlock);
    manager.register(TrialSpawnerBlock);
    manager.register(VaultBlock);
    manager.register(DecoratedPotBlock);
    manager.register(CreakingHeartBlock);
    manager.register(SnifferEggBlock);
    manager.register(BushBlock);
    manager.register(FlowerBlock);
    manager.register(PotatoBlock);
    manager.register(BeetrootBlock);
    manager.register(TorchFlowerBlock);
    manager.register(CarrotBlock);
    manager.register(SweetBerryBushBlock);
    manager.register(SeaGrassBlock);
    manager.register(TallSeaGrassBlock);
    manager.register(KelpBlock);
    manager.register(NetherWartBlock);
    manager.register(WheatBlock);
    manager.register(TorchBlock);
    manager.register(TrapDoorBlock);
    manager.register(MushroomPlantBlock);
    manager.register(FlowerbedBlock);
    manager.register(LeafLitterBlock);
    manager.register(WallBlock);
    manager.register(RootsBlock);
    manager.register(NetherPortalBlock);
    manager.register(TallPlantBlock);
    manager.register(NoteBlock);
    manager.register(PowderSnowBlock);
    manager.register(SpongeBlock);
    manager.register(PumpkinBlock);
    manager.register(WetSpongeBlock);
    manager.register(CommandBlock);
    manager.register(JigsawBlock);
    manager.register(ComposterBlock);
    manager.register(CauldronBlock);
    manager.register(BeehiveBlock);
    manager.register(PressurePlateBlock);
    manager.register(WeightedPressurePlateBlock);
    manager.register(EndPortalBlock);
    manager.register(SpawnerBlock);
    manager.register(EndPortalFrameBlock);
    manager.register(RespawnAnchorBlock);
    manager.register(CandleBlock);
    manager.register(SeaPickleBlock);
    manager.register(CakeBlock);
    manager.register(CandleCakeBlock);
    manager.register(SkullBlock);
    manager.register(ChiseledBookshelfBlock);
    manager.register(ShelfBlock);
    manager.register(LecternBlock);
    manager.register(DragonEggBlock);
    manager.register(StemBlock);
    manager.register(AttachedStemBlock);
    manager.register(ChainBlock);
    manager.register(LanternBlock);
    manager.register(EndRodBlock);
    manager.register(BarrierBlock);
    manager.register(MangroveRootsBlock);
    manager.register(LayeredSnowBlock);
    manager.register(CobwebBlock);
    manager.register(WitherRoseBlock);
    manager.register(FungusBlock);
    manager.register(NetherSproutsBlock);
    manager.register(SporeBlossomBlock);
    manager.register(ConduitBlock);
    manager.register(DripstoneBlock);
    manager.register(TwistingVinesBlock);
    manager.register(WeepingVinesBlock);
    manager.register(CactusFlowerBlock);
    manager.register(SmallDripleafBlock);
    manager.register(BigDripleafStemBlock);
    manager.register(BigDripleafBlock);
    manager.register(CoralFanBlock);
    manager.register(CoralPlantBlock);
    manager.register(CoralBlock);
    manager.register(AmethystBlock);
    manager.register(GrassBlock);
    manager.register(BubbleColumnBlock);

    manager.register(FallingBlock);

    // Fire
    manager.register(SoulFireBlock);
    manager.register(FireBlock);
    manager.register(MagmaBlock);

    // Redstone
    manager.register(ButtonBlock);
    manager.register(BellBlock);
    manager.register(LeverBlock);
    manager.register(LightningRodBlock);
    manager.register(SculkSensorBlock);
    manager.register(SculkVeinBlock);
    manager.register(SculkCatalystBlock);
    manager.register(SculkShriekerBlock);
    manager.register(ObserverBlock);
    manager.register(TripwireBlock);
    manager.register(TripwireHookBlock);

    // Piston
    manager.register(PistonBlock);
    manager.register(PistonExtensionBlock);
    manager.register(PistonHeadBlock);

    manager.register(RedstoneBlock);
    manager.register(RedstoneLamp);
    manager.register(RedstoneOreBlock);
    manager.register(CopperBulbBlock);
    manager.register(RedstoneTorchBlock);
    manager.register(RedstoneWireBlock);
    manager.register(RepeaterBlock);
    manager.register(ComparatorBlock);
    manager.register(CrafterBlock);
    manager.register(TargetBlock);
    manager.register(BarrelBlock);
    manager.register(HopperBlock);
    manager.register(ShulkerBoxBlock);
    manager.register(DropperBlock);
    manager.register(DispenserBlock);
    manager.register(LadderBlock);
    manager.register(DaylightDetectorBlock);

    // Rails
    manager.register(RailBlock);
    manager.register(ActivatorRailBlock);
    manager.register(DetectorRailBlock);
    manager.register(PoweredRailBlock);

    // Fluids
    manager.register_fluid(FlowingWater);
    manager.register_fluid(FlowingLava);
    Arc::new(manager)
}

// ActionResult.java
#[derive(PartialEq, Eq)]
pub enum BlockActionResult {
    /// Action was successful | Same as SUCCESS in vanilla
    Success,
    /// Action was successful and we should swing the hand for the server | Same as `SUCCESS_SERVER` in vanilla
    SuccessServer,
    /// Block other actions from being executed | Same as CONSUME in vanilla
    Consume,
    /// Allow other actions to be executed, but indicate it failed | Same as FAIL in vanilla
    Fail,
    /// Allow other actions to be executed | Same as PASS in vanilla
    Pass,
    /// Use default action for the block: `normal_use` | Same as `PASS_TO_DEFAULT_BLOCK_ACTION` in vanilla
    PassToDefaultBlockAction,
}

impl BlockActionResult {
    #[must_use]
    pub const fn consumes_action(&self) -> bool {
        matches!(self, Self::Consume | Self::Success | Self::SuccessServer)
    }
}

#[derive(Default)]
pub struct BlockRegistry {
    blocks: FxHashMap<BlockId, Arc<dyn BlockBehaviour>>,
    fluids: FxHashMap<u16, Arc<dyn FluidBehaviour>>,
}

#[derive(Debug)]
pub enum BlockPlacingError {
    InvalidGamemode,
    BlockOutOfWorld,
}

impl BlockRegistry {
    fn entity_blocks_block_placement(entity: &dyn EntityBase) -> bool {
        let base_entity = entity.get_entity();
        if base_entity.is_removed()
            || base_entity
                .no_clip
                .load(std::sync::atomic::Ordering::Relaxed)
            || entity.is_spectator()
        {
            return false;
        }

        if entity.get_living_entity().is_some() {
            return true;
        }

        let entity_type = base_entity.entity_type;
        let resource_name = entity_type.resource_name;
        entity_type == &pumpkin_data::entity::EntityType::END_CRYSTAL
            || entity_type == &pumpkin_data::entity::EntityType::FALLING_BLOCK
            || entity_type == &pumpkin_data::entity::EntityType::TNT
            || resource_name.ends_with("_minecart")
            || resource_name.ends_with("_boat")
            || resource_name.ends_with("_raft")
    }

    fn has_blocking_entity_in_box(world: &World, placed_box: &BoundingBox) -> bool {
        let players = world.players.load();
        if players.iter().any(|player| {
            Self::entity_blocks_block_placement(player.as_ref())
                && player
                    .get_entity()
                    .bounding_box
                    .load()
                    .intersects(placed_box)
        }) {
            return true;
        }

        world.entities.load().iter().any(|entity| {
            Self::entity_blocks_block_placement(entity.as_ref())
                && entity
                    .get_entity()
                    .bounding_box
                    .load()
                    .intersects(placed_box)
        })
    }

    #[expect(clippy::too_many_lines)]
    pub async fn place_block(
        &self,
        player: &Arc<Player>,
        placed_block: &'static Block,
        server: &Arc<Server>,
        use_item_on: &SUseItemOn,
        location: BlockPos,
        face: BlockDirection,
    ) -> Result<Option<(BlockPos, BlockStateId)>, BlockPlacingError> {
        let entity = &player.get_entity();

        match player.gamemode.load() {
            pumpkin_util::GameMode::Spectator | pumpkin_util::GameMode::Adventure => {
                return Err(BlockPlacingError::InvalidGamemode);
            }
            _ => {}
        }

        let clicked_block_pos = BlockPos(location.0);
        let world = entity.world.load_full();

        if location.0.y + face.to_offset().y < world.get_bottom_y() {
            return Err(BlockPlacingError::BlockOutOfWorld);
        }

        if location.0.y + face.to_offset().y > world.get_top_y() {
            player
                .send_system_message_raw(
                    &pumpkin_util::text::TextComponent::translate_cross(
                        pumpkin_data::translation::java::BUILD_TOOHIGH,
                        pumpkin_data::translation::bedrock::BUILD_TOOHIGH,
                        vec![pumpkin_util::text::TextComponent::text(
                            (world.get_top_y()).to_string(),
                        )],
                    )
                    .color_named(pumpkin_util::text::color::NamedColor::Red),
                    true,
                )
                .await;
            return Err(BlockPlacingError::BlockOutOfWorld);
        }

        let (clicked_block, clicked_block_state) = world.get_block_and_state(&clicked_block_pos);

        let replace_clicked_block = if clicked_block == placed_block {
            self.can_update_at(
                &world,
                clicked_block,
                clicked_block_state.id,
                &clicked_block_pos,
                face,
                use_item_on,
                player,
            )
            .then_some(BlockIsReplacing::Itself(clicked_block_state.id))
        } else if clicked_block_state.replaceable() {
            if clicked_block == &Block::WATER {
                use pumpkin_data::block_properties::{BlockProperties, WaterLikeProperties};
                let water_props =
                    WaterLikeProperties::from_state_id(clicked_block_state.id, clicked_block);
                Some(BlockIsReplacing::Water(water_props.level))
            } else {
                Some(BlockIsReplacing::Other)
            }
        } else {
            None
        };

        let (final_block_pos, final_face, replacing) =
            if let Some(replacing) = replace_clicked_block {
                (clicked_block_pos, face.opposite(), replacing)
            } else {
                let block_pos = BlockPos(location.0 + face.to_offset());
                let (previous_block, previous_block_state) = world.get_block_and_state(&block_pos);

                let replace_previous_block = if previous_block == placed_block {
                    self.can_update_at(
                        &world,
                        previous_block,
                        previous_block_state.id,
                        &block_pos,
                        face.opposite(),
                        use_item_on,
                        player,
                    )
                    .then_some(BlockIsReplacing::Itself(previous_block_state.id))
                } else {
                    previous_block_state.replaceable().then(|| {
                        if previous_block == &Block::WATER {
                            use pumpkin_data::block_properties::{
                                BlockProperties, WaterLikeProperties,
                            };
                            let water_props = WaterLikeProperties::from_state_id(
                                previous_block_state.id,
                                previous_block,
                            );
                            BlockIsReplacing::Water(water_props.level)
                        } else {
                            BlockIsReplacing::None
                        }
                    })
                };

                match replace_previous_block {
                    Some(replacing) => (block_pos, face.opposite(), replacing),
                    None => {
                        return Ok(None);
                    }
                }
            };

        if !self.can_place_at(
            Some(server),
            Some(&*world),
            &*world,
            Some(player),
            placed_block,
            placed_block.default_state,
            &final_block_pos,
            Some(final_face),
            Some(use_item_on),
        ) {
            return Ok(None);
        }

        let new_state = self
            .on_place(
                server,
                &world,
                player,
                placed_block,
                &final_block_pos,
                final_face,
                replacing,
                use_item_on,
            )
            .await;

        // Mirror vanilla obstruction checks: only entities that block building should prevent
        // placement. (e.g. arrows/xp orbs/displays/markers should not)
        let state = BlockState::from_id(new_state);
        let mut buildable = true;
        for shape in state.get_block_collision_shapes() {
            let placed_box = shape.at_pos(final_block_pos);

            if Self::has_blocking_entity_in_box(world.as_ref(), &placed_box) {
                buildable = false;
                break;
            }
        }

        let mut can_build_event = crate::plugin::block::block_can_build::BlockCanBuildEvent {
            block_to_build: placed_block,
            buildable,
            player: player.clone(),
            block: clicked_block,
            cancelled: false,
        };
        server
            .plugin_manager
            .fire::<crate::plugin::block::block_can_build::BlockCanBuildEvent>(
                server,
                &mut can_build_event,
            )
            .await;
        if can_build_event.cancelled || !can_build_event.buildable {
            return Ok(None);
        }

        let mut event = crate::plugin::block::block_place::BlockPlaceEvent::new(
            player.clone(),
            placed_block,
            clicked_block,
            final_block_pos,
            true,
        );
        server
            .plugin_manager
            .fire::<crate::plugin::block::block_place::BlockPlaceEvent>(server, &mut event)
            .await;
        if event.cancelled {
            return Ok(None);
        }

        let _replaced_id = world
            .set_block_state(&final_block_pos, new_state, BlockFlags::NOTIFY_ALL)
            .await;

        self.player_placed(
            &world,
            placed_block,
            new_state,
            &final_block_pos,
            face,
            player,
        )
        .await;

        player
            .trigger_advancement(
                crate::entity::player::advancement::trigger::AdvancementTrigger::PlacedBlock {
                    block_id: format!("minecraft:{}", placed_block.name),
                },
            )
            .await;

        Ok(Some((final_block_pos, new_state)))
    }
    pub fn register<T: BlockBehaviour + BlockMetadata + 'static>(&mut self, block: T) {
        let ids = T::ids();
        let val = Arc::new(block);
        self.blocks.reserve(ids.len());
        for i in ids {
            self.blocks.insert(i, val.clone());
        }
    }

    pub fn register_fluid<T: FluidBehaviour + FluidMetadata + 'static>(&mut self, fluid: T) {
        let ids = T::ids();
        let val = Arc::new(fluid);
        self.fluids.reserve(ids.len());
        for i in ids {
            self.fluids.insert(i, val.clone());
        }
    }

    pub async fn on_synced_block_event(
        &self,
        block: &Block,
        world: &Arc<World>,
        position: &BlockPos,
        r#type: u8,
        data: u8,
    ) -> bool {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .on_synced_block_event(OnSyncedBlockEventArgs {
                    world,
                    block,
                    position,
                    r#type,
                    data,
                })
                .await;
        }
        false
    }

    pub async fn on_entity_collision(
        &self,
        block: &Block,
        world: &Arc<World>,
        entity: &dyn EntityBase,
        position: &BlockPos,
        state: &BlockState,
        server: &Server,
    ) {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .on_entity_collision(OnEntityCollisionArgs {
                    server,
                    world,
                    block,
                    state,
                    position,
                    entity,
                })
                .await;
        }
    }

    pub async fn on_entity_step(
        &self,
        block: &Block,
        world: &Arc<World>,
        entity: &dyn EntityBase,
        position: &BlockPos,
        state: &BlockState,
        below_supporting_block: bool,
    ) {
        if let Some(pumpkin_block) = self.get_pumpkin_block(block.id) {
            pumpkin_block
                .on_entity_step(OnEntityStepArgs {
                    world,
                    block,
                    state,
                    position,
                    entity,
                    below_supporting_block,
                })
                .await;
        }
    }

    pub async fn on_entity_collision_fluid(&self, fluid: &Fluid, entity: &dyn EntityBase) {
        let pumpkin_fluid = self.get_pumpkin_fluid(fluid.id);
        if let Some(pumpkin_fluid) = pumpkin_fluid {
            pumpkin_fluid.on_entity_collision(entity).await;
        }
    }

    pub async fn on_use(
        &self,
        block: &Block,
        player: &Arc<Player>,
        position: &BlockPos,
        hit: &BlockHitResult<'_>,
        server: &Server,
        world: &Arc<World>,
    ) -> BlockActionResult {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .normal_use(NormalUseArgs {
                    server,
                    world,
                    block,
                    position,
                    player,
                    hit,
                })
                .await;
        }
        BlockActionResult::Pass
    }

    pub async fn explode(&self, block: &Block, world: &Arc<World>, position: &BlockPos) {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .explode(ExplodeArgs {
                    world,
                    block,
                    position,
                })
                .await;
        }
    }

    #[expect(clippy::too_many_arguments)]
    pub async fn use_with_item(
        &self,
        block: &Block,
        player: &Arc<Player>,
        position: &BlockPos,
        hit: &BlockHitResult<'_>,
        item_stack: &mut ItemStack,
        equipment_slot: &EquipmentSlot,
        server: &Server,
        world: &Arc<World>,
    ) -> BlockActionResult {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .use_with_item(UseWithItemArgs {
                    server,
                    world,
                    block,
                    position,
                    player,
                    hit,
                    item_stack,
                    equipment_slot,
                })
                .await;
        }
        BlockActionResult::Pass
    }

    pub async fn use_with_item_fluid(
        &self,
        fluid: &Fluid,
        player: &Arc<Player>,
        position: BlockPos,
        item: &Item,
        server: &Server,
        world: &Arc<World>,
    ) -> BlockActionResult {
        let pumpkin_fluid = self.get_pumpkin_fluid(fluid.id);
        if let Some(pumpkin_fluid) = pumpkin_fluid {
            return pumpkin_fluid
                .use_with_item(fluid, player, position, item, server, world)
                .await;
        }
        BlockActionResult::Pass
    }

    #[expect(clippy::too_many_arguments)]
    pub fn can_place_at(
        &self,
        server: Option<&Server>,
        world: Option<&World>,
        block_accessor: &dyn BlockAccessor,
        player: Option<&Player>,
        block: &Block,
        state: &BlockState,
        position: &BlockPos,
        direction: Option<BlockDirection>,
        use_item_on: Option<&SUseItemOn>,
    ) -> bool {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block.can_place_at(CanPlaceAtArgs {
                server,
                world,
                block_accessor,
                block,
                state,
                position,
                direction,
                player,
                use_item_on,
            });
        }
        true
    }

    #[expect(clippy::too_many_arguments)]
    pub fn can_update_at(
        &self,
        world: &World,
        block: &Block,
        state_id: BlockStateId,
        position: &BlockPos,
        direction: BlockDirection,
        use_item_on: &SUseItemOn,
        player: &Player,
    ) -> bool {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block.can_update_at(CanUpdateAtArgs {
                world,
                block,
                state_id,
                position,
                direction,
                player,
                use_item_on,
            });
        }
        false
    }

    #[expect(clippy::too_many_arguments)]
    pub async fn on_place(
        &self,
        server: &Server,
        world: &World,
        player: &Player,
        block: &Block,
        position: &BlockPos,
        direction: BlockDirection,
        replacing: BlockIsReplacing,
        use_item_on: &SUseItemOn,
    ) -> BlockStateId {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .on_place(OnPlaceArgs {
                    server,
                    world,
                    block,
                    position,
                    direction,
                    player,
                    replacing,
                    use_item_on,
                })
                .await;
        }
        block.default_state.id
    }

    pub async fn player_placed(
        &self,
        world: &Arc<World>,
        block: &Block,
        state_id: BlockStateId,
        position: &BlockPos,
        direction: BlockDirection,
        player: &Player,
    ) {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .player_placed(PlayerPlacedArgs {
                    world,
                    block,
                    state_id,
                    position,
                    direction,
                    player,
                })
                .await;
        }
    }

    pub async fn on_placed(
        &self,
        world: &Arc<World>,
        block: &Block,
        state_id: BlockStateId,
        position: &BlockPos,
        old_state_id: BlockStateId,
        notify: bool,
    ) {
        let state = world.get_block_state(position);
        if state.block_entity_type != u16::MAX
            && world.get_block_entity(position).is_none()
            && let Some(entity) =
                crate::block::entities::create_block_entity(state.block_entity_type, *position)
        {
            world.add_block_entity(entity);
        }

        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .placed(PlacedArgs {
                    world,
                    block,
                    state_id,
                    old_state_id,
                    position,
                    notify,
                })
                .await;
        }
    }

    pub async fn on_placed_fluid(
        &self,
        world: &Arc<World>,
        fluid: &Fluid,
        state_id: BlockStateId,
        position: &BlockPos,
        old_state_id: BlockStateId,
        notify: bool,
    ) {
        let pumpkin_fluid = self.get_pumpkin_fluid(fluid.id);
        if let Some(pumpkin_fluid) = pumpkin_fluid {
            pumpkin_fluid
                .placed(world, fluid, state_id, position, old_state_id, notify)
                .await;
        }
    }

    pub async fn on_landed_upon(
        &self,
        block: &Block,
        world: &Arc<World>,
        fall_distance: f32,
        entity: &dyn EntityBase,
    ) {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .on_landed_upon(OnLandedUponArgs {
                    world,
                    fall_distance,
                    entity,
                })
                .await;
        }
    }

    pub async fn update_entity_movement_after_fall_on(
        &self,
        block: &Block,
        entity: &dyn EntityBase,
    ) {
        if let Some(pumpkin_block) = self.get_pumpkin_block(block.id) {
            pumpkin_block
                .update_entity_movement_after_fall_on(UpdateEntityMovementAfterFallOnArgs {
                    entity,
                })
                .await;
        } else {
            stop_vertical_movement_after_fall(entity);
        }
    }

    pub async fn broken(
        &self,
        world: &Arc<World>,
        block: &Block,
        player: &Arc<Player>,
        position: &BlockPos,
        server: &Server,
        state: &BlockState,
    ) {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .broken(BrokenArgs {
                    block,
                    player,
                    position,
                    server,
                    world,
                    state,
                })
                .await;
        }
    }

    pub async fn on_state_replaced(
        &self,
        world: &Arc<World>,
        block: &Block,
        position: &BlockPos,
        old_state_id: BlockStateId,
        moved: bool,
    ) {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .on_state_replaced(OnStateReplacedArgs {
                    world,
                    block,
                    old_state_id,
                    position,
                    moved,
                })
                .await;
        }
    }

    /// Updates state of all neighbors of the block
    pub async fn post_process_state(
        &self,
        world: &Arc<World>,
        position: &BlockPos,
        block: &Block,
        flags: BlockFlags,
    ) {
        let state_id = world.get_block_state_id(position);
        for direction in BlockDirection::all() {
            let neighbor_pos = position.offset(direction.to_offset());
            let neighbor_state_id = world.get_block_state_id(&neighbor_pos);
            let pumpkin_block = self.get_pumpkin_block(block.id);
            if let Some(pumpkin_block) = pumpkin_block {
                let new_state = pumpkin_block
                    .get_state_for_neighbor_update(GetStateForNeighborUpdateArgs {
                        world,
                        block,
                        state_id,
                        position,
                        direction: direction.opposite(),
                        neighbor_position: &neighbor_pos,
                        neighbor_state_id,
                    })
                    .await;
                world.set_block_state(&neighbor_pos, new_state, flags).await;
            }
        }
    }

    pub async fn prepare(
        &self,
        world: &Arc<World>,
        position: &BlockPos,
        block: &Block,
        state_id: BlockStateId,
        flags: BlockFlags,
    ) {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .prepare(PrepareArgs {
                    world,
                    block,
                    state_id,
                    position,
                    flags,
                })
                .await;
        }
    }

    #[expect(clippy::too_many_arguments)]
    pub async fn get_state_for_neighbor_update(
        &self,
        world: &Arc<World>,
        block: &Block,
        state_id: BlockStateId,
        position: &BlockPos,
        direction: BlockDirection,
        neighbor_location: &BlockPos,
        neighbor_state_id: BlockStateId,
    ) -> BlockStateId {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .get_state_for_neighbor_update(GetStateForNeighborUpdateArgs {
                    world,
                    block,
                    state_id,
                    position,
                    direction,
                    neighbor_position: neighbor_location,
                    neighbor_state_id,
                })
                .await;
        }
        state_id
    }

    pub async fn update_neighbors(
        &self,
        world: &Arc<World>,
        position: &BlockPos,
        _block: &Block,
        flags: BlockFlags,
    ) {
        for direction in BlockDirection::abstract_block_update_order() {
            let pos = position.offset(direction.to_offset());

            Box::pin(world.replace_with_state_for_neighbor_update(
                &pos,
                direction.opposite(),
                flags,
            ))
            .await;
        }
    }

    pub async fn on_neighbor_update(
        &self,
        world: &Arc<World>,
        block: &Block,
        position: &BlockPos,
        source_block: &Block,
        notify: bool,
    ) {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            pumpkin_block
                .on_neighbor_update(OnNeighborUpdateArgs {
                    world,
                    block,
                    position,
                    source_block,
                    notify,
                })
                .await;
        }
    }

    #[must_use]
    pub fn get_pumpkin_block(&self, block: BlockId) -> Option<&Arc<dyn BlockBehaviour>> {
        self.blocks.get(&block)
    }

    #[must_use]
    pub fn get_pumpkin_fluid(&self, fluid_id: u16) -> Option<&Arc<dyn FluidBehaviour>> {
        self.fluids.get(&fluid_id).or_else(|| {
            // Still fluids share behavior with their flowing counterpart
            match fluid_id {
                2 => self.fluids.get(&1),
                4 => self.fluids.get(&3),
                _ => None,
            }
        })
    }

    pub async fn emits_redstone_power(
        &self,
        block: &Block,
        state: &BlockState,
        direction: BlockDirection,
    ) -> bool {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .emits_redstone_power(EmitsRedstonePowerArgs {
                    block,
                    state,
                    direction,
                })
                .await;
        }
        false
    }

    pub async fn get_weak_redstone_power(
        &self,
        block: &Block,
        world: &World,
        position: &BlockPos,
        state: &BlockState,
        direction: BlockDirection,
    ) -> u8 {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .get_weak_redstone_power(GetRedstonePowerArgs {
                    world,
                    block,
                    state,
                    position,
                    direction,
                })
                .await;
        }
        0
    }

    pub async fn get_strong_redstone_power(
        &self,
        block: &Block,
        world: &World,
        position: &BlockPos,
        state: &BlockState,
        direction: BlockDirection,
    ) -> u8 {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .get_strong_redstone_power(GetRedstonePowerArgs {
                    world,
                    block,
                    state,
                    position,
                    direction,
                })
                .await;
        }
        0
    }

    pub async fn get_inside_collision_shape(
        &self,
        block: &Block,
        world: &World,
        state: &BlockState,
        position: &BlockPos,
    ) -> BoundingBox {
        let pumpkin_block = self.get_pumpkin_block(block.id);
        if let Some(pumpkin_block) = pumpkin_block {
            return pumpkin_block
                .get_inside_collision_shape(GetInsideCollisionShapeArgs {
                    world,
                    block,
                    state,
                    position,
                })
                .await;
        }
        BoundingBox::full_block()
    }

    #[must_use]
    pub fn mirror(
        &self,
        block: &Block,
        state_id: BlockStateId,
        mirror: Mirror,
    ) -> &'static BlockState {
        self.get_pumpkin_block(block.id).map_or_else(
            || block.mirror(state_id, mirror),
            |pumpkin_block| pumpkin_block.mirror(block, state_id, mirror),
        )
    }

    #[must_use]
    pub fn rotate(
        &self,
        block: &Block,
        state_id: BlockStateId,
        rotation: Rotation,
    ) -> &'static BlockState {
        self.get_pumpkin_block(block.id).map_or_else(
            || block.rotate(state_id, rotation),
            |pumpkin_block| pumpkin_block.rotate(block, state_id, rotation),
        )
    }
}
