package dev.micle.firefly_bush_backport.block;

import dev.micle.firefly_bush_backport.config.Config;
import dev.micle.firefly_bush_backport.particle.ModParticles;
import dev.micle.firefly_bush_backport.sound.ModSounds;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;

import javax.annotation.ParametersAreNonnullByDefault;
import java.util.List;
import java.util.Optional;

public class FireflyBushBlock extends BushBlock implements BonemealableBlock {
    public FireflyBushBlock(Properties properties) {
        super(properties);
    }

    @Override
    @ParametersAreNonnullByDefault
    public void animateTick(BlockState blockState, Level level, BlockPos blockPos, RandomSource randomSource) {
        if (randomSource.nextInt(Config.Client.bushFireflyAmbientSoundChanceOneIn.get()) == 0
                && isMoonVisible(level)
                && level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockPos.getX(), blockPos.getZ()) <= blockPos.getY()) {
            level.playLocalSound(
                    blockPos,
                    ModSounds.FIREFLY_BUSH_IDLE.get(),
                    SoundSource.AMBIENT,
                    Config.Client.bushFireflyAmbientSoundVolume.get().floatValue(),
                    Config.Client.bushFireflyAmbientSoundPitch.get().floatValue(),
                    false
            );
        }

        if ((isMoonVisible(level) || level.getMaxLocalRawBrightness(blockPos) <= Config.Client.bushFireflySpawnMaxBrightnessLevel.get()) &&
                randomSource.nextDouble() <= Config.Client.bushFireflyChancePerTick.get()) {
            double d0 = blockPos.getX() + randomSource.nextDouble() * Config.Client.bushFireflyHorizontalRange.get() - Config.Client.bushFireflyVerticalRange.get();
            double d1 = blockPos.getY() + randomSource.nextDouble() * Config.Client.bushFireflyVerticalRange.get();
            double d2 = blockPos.getZ() + randomSource.nextDouble() * Config.Client.bushFireflyHorizontalRange.get() - Config.Client.bushFireflyVerticalRange.get();
            level.addParticle(ModParticles.FIREFLY.get(), d0, d1, d2, 0.0, 0.0, 0.0);
        }
    }

    public boolean isMoonVisible(Level level) {
        if (!level.dimensionType().natural()) {
            return false;
        } else {
            int i = (int) (level.getDayTime() % 24000L);
            return i >= 12600 && i <= 23400;
        }
    }

    @Override
    @ParametersAreNonnullByDefault
    public boolean isValidBonemealTarget(LevelReader level, BlockPos blockPos, BlockState blockState, boolean isClient) {
        return hasSpreadableNeighbourPos(level, blockPos, blockState);
    }

    @Override
    @ParametersAreNonnullByDefault
    public boolean isBonemealSuccess(Level level, RandomSource randomSource, BlockPos blockPos, BlockState blockState) {
        return true;
    }

    @Override
    @ParametersAreNonnullByDefault
    public void performBonemeal(ServerLevel level, RandomSource randomSource, BlockPos blockPos, BlockState blockState) {
        findSpreadableNeighbourPos(level, blockPos, blockState).ifPresent(x -> level.setBlockAndUpdate(x, this.defaultBlockState()));
    }

    static boolean hasSpreadableNeighbourPos(LevelReader levelReader, BlockPos blockPos, BlockState blockState) {
        return getSpreadableNeighbourPos(Direction.Plane.HORIZONTAL.stream().toList(), levelReader, blockPos, blockState).isPresent();
    }

    static Optional<BlockPos> findSpreadableNeighbourPos(Level level, BlockPos blockPos, BlockState blockState) {
        return getSpreadableNeighbourPos(Direction.Plane.HORIZONTAL.shuffledCopy(level.random), level, blockPos, blockState);
    }

    static Optional<BlockPos> getSpreadableNeighbourPos(List<Direction> directions, LevelReader levelReader, BlockPos blockPos, BlockState blockState) {
        for (Direction direction : directions) {
            BlockPos blockPosDirection = blockPos.relative(direction);
            if (levelReader.isEmptyBlock(blockPosDirection) && blockState.canSurvive(levelReader, blockPosDirection)) {
                return Optional.of(blockPosDirection);
            }
        }
        return Optional.empty();
    }
}
