package zenith.zov.client.modules.impl.render;

import com.darkmagician6.eventapi.EventManager;
import com.darkmagician6.eventapi.EventTarget;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.PillarBlock;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.passive.TraderLlamaEntity;
import net.minecraft.entity.passive.WanderingTraderEntity;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.BlockPos.Mutable;
import net.minecraft.util.math.Direction.Axis;
import net.minecraft.world.chunk.WorldChunk;
import zenith.zov.base.events.impl.player.EventUpdate;
import zenith.zov.base.events.impl.render.EventRender3D;
import zenith.zov.base.notify.NotifyManager;
import zenith.zov.client.modules.api.Category;
import zenith.zov.client.modules.api.Module;
import zenith.zov.client.modules.api.ModuleAnnotation;
import zenith.zov.client.modules.api.setting.impl.BooleanSetting;
import zenith.zov.client.modules.api.setting.impl.NumberSetting;
import zenith.zov.utility.interfaces.IMinecraft;
import zenith.zov.utility.render.level.Render3DUtil;

@ModuleAnnotation(
    name = "ChunkFinder",
    category = Category.RENDER,
    description = "Finds Sus Chunks, Holes, and Traders"
)
public final class ChunkFinder extends Module implements IMinecraft {
    public static final ChunkFinder INSTANCE = new ChunkFinder();
    private final BooleanSetting tracers = new BooleanSetting("Tracers", true);
    private final BooleanSetting fill = new BooleanSetting("Fill", true);
    private final BooleanSetting outline = new BooleanSetting("Outline", true);
    private final BooleanSetting highlightBlocks = new BooleanSetting("HighlightBlocks", true);
    private final BooleanSetting detectTraders = new BooleanSetting("BetterChunk", true);
    private final NumberSetting traderChunkColorRed = new NumberSetting("BetterChunkColor Red", 255.0F, 0.0F, 255.0F, 1.0F);
    private final NumberSetting traderChunkColorGreen = new NumberSetting("BetterChunkColor Green", 165.0F, 0.0F, 255.0F, 1.0F);
    private final NumberSetting traderChunkColorBlue = new NumberSetting("BetterChunkColor Blue", 0.0F, 0.0F, 255.0F, 1.0F);
    private final NumberSetting traderChunkColorAlpha = new NumberSetting("BetterChunkColor Alpha", 120.0F, 0.0F, 255.0F, 1.0F);
    private final NumberSetting fillAlpha = new NumberSetting("FillAlpha", 120.0F, 0.0F, 255.0F, 5.0F);
    private final BooleanSetting holeESP = new BooleanSetting("HoleESP", true);
    private final BooleanSetting detect1x1Holes = new BooleanSetting("Detect1x1Holes", true);
    private final BooleanSetting detect3x1Holes = new BooleanSetting("Detect3x1Holes", true);
    private final NumberSetting minHoleDepth = new NumberSetting("MinHoleDepth", 4.0F, 1.0F, 20.0F, 1.0F);
    private final BooleanSetting holeOutline = new BooleanSetting("HoleOutline", true);
    private final BooleanSetting holeFill = new BooleanSetting("HoleFill", true);
    private static final int MIN_Y = 8;
    private static final int MAX_UNDERGROUND_Y = 60;
    private static final int HIGHLIGHT_Y = 60;
    private static final boolean FIND_ROTATED_DEEPSLATE = true;
    private final Set<ChunkPos> flaggedChunks = ConcurrentHashMap.newKeySet();
    private final Set<ChunkPos> scannedChunks = ConcurrentHashMap.newKeySet();
    private final Set<ChunkPos> notifiedChunks = ConcurrentHashMap.newKeySet();
    private final ConcurrentMap<ChunkPos, Set<BlockPos>> flaggedBlocks = new ConcurrentHashMap<>();
    private final Set<ChunkPos> traderChunks = ConcurrentHashMap.newKeySet();
    private final Set<ChunkPos> notifiedTraderChunks = ConcurrentHashMap.newKeySet();
    private Set<Integer> notifiedTraders = new HashSet<>();
    private final Set<Box> holes1x1 = Collections.newSetFromMap(new ConcurrentHashMap<>());
    private final Set<Box> holes3x1 = Collections.newSetFromMap(new ConcurrentHashMap<>());
    private final Set<ChunkPos> holeScannedChunks = ConcurrentHashMap.newKeySet();
    private ExecutorService scannerThread;
    private Future<?> currentScanTask;
    private Future<?> currentHoleScanTask;
    private Future<?> currentTraderScanTask;
    private volatile boolean shouldStop = false;
    private long lastScanTime = 0L;
    private long lastCleanupTime = 0L;
    private long lastHoleScanTime = 0L;
    private long lastTraderScanTime = 0L;
    private static final long SCAN_COOLDOWN = 200L;
    private static final long CLEANUP_INTERVAL = 5000L;
    private static final long HOLE_SCAN_COOLDOWN = 300L;
    private static final long TRADER_SCAN_COOLDOWN = 1000L;
    private int previousSusCount = 0;
    private int previousTraderCount = 0;

    private ChunkFinder() {
        EventManager.register(this);
    }

    @Override
    public void onEnable() {
        this.flaggedChunks.clear();
        this.scannedChunks.clear();
        this.notifiedChunks.clear();
        this.flaggedBlocks.clear();
        this.traderChunks.clear();
        this.notifiedTraderChunks.clear();
        this.holes1x1.clear();
        this.holes3x1.clear();
        this.holeScannedChunks.clear();
        this.shouldStop = false;
        this.scannerThread = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "ChunkScanner");
            t.setDaemon(true);
            t.setPriority(1);
            return t;
        });
        this.scheduleChunkScan();
        if (this.holeESP.isEnabled()) {
            this.scheduleHoleScan();
        }

        if (this.detectTraders.isEnabled()) {
            this.scheduleTraderScan();
        }
    }

    @Override
    public void onDisable() {
        this.shouldStop = true;
        if (this.currentScanTask != null && !this.currentScanTask.isDone()) {
            this.currentScanTask.cancel(true);
        }

        if (this.currentHoleScanTask != null && !this.currentHoleScanTask.isDone()) {
            this.currentHoleScanTask.cancel(true);
        }

        if (this.currentTraderScanTask != null && !this.currentTraderScanTask.isDone()) {
            this.currentTraderScanTask.cancel(true);
        }

        if (this.scannerThread != null && !this.scannerThread.isShutdown()) {
            this.scannerThread.shutdownNow();
        }

        this.flaggedChunks.clear();
        this.scannedChunks.clear();
        this.notifiedChunks.clear();
        this.flaggedBlocks.clear();
        this.traderChunks.clear();
        this.notifiedTraderChunks.clear();
        this.holes1x1.clear();
        this.holes3x1.clear();
        this.holeScannedChunks.clear();
    }

    @EventTarget
    public void onUpdate(EventUpdate event) {
        if (mc.world != null && mc.player != null) {
            long currentTime = System.currentTimeMillis();
            if (currentTime - this.lastScanTime > 200L) {
                this.scheduleChunkScan();
                this.lastScanTime = currentTime;
            }

            if (this.holeESP.isEnabled() && currentTime - this.lastHoleScanTime > 300L) {
                this.scheduleHoleScan();
                this.lastHoleScanTime = currentTime;
            }

            if (this.detectTraders.isEnabled() && currentTime - this.lastTraderScanTime > 1000L) {
                this.scheduleTraderScan();
                this.lastTraderScanTime = currentTime;
            }

            if (currentTime - this.lastCleanupTime > 5000L) {
                this.cleanupDistantChunks();
                this.cleanupDistantHoles();
                this.cleanupDistantTraderChunks();
                this.lastCleanupTime = currentTime;
            }
        }
    }

    private void notifyChunkFound(ChunkPos chunkPos) {
        mc.execute(() -> {
            try {
                int worldX = chunkPos.x * 16;
                int worldZ = chunkPos.z * 16;
                String message = "Sus Chunk Found: X: " + worldX + " Z: " + worldZ;
                NotifyManager.getInstance().addNotification("CF", Text.literal(message));
                if (mc.player != null) {
                    mc.world.playSound(mc.player, mc.player.getBlockPos(), SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.PLAYERS, 1.0F, 1.5F);
                }
            } catch (Exception var4) {
            }
        });
    }

    private void notifyTraderChunkFound(ChunkPos chunkPos) {
        mc.execute(() -> {
            try {
                int worldX = chunkPos.x * 16;
                int worldZ = chunkPos.z * 16;
                String message = "Trader Chunk Found: X: " + worldX + " Z: " + worldZ;
                NotifyManager.getInstance().addNotification("CF", Text.literal(message));
                if (mc.player != null) {
                    mc.world.playSound(mc.player, mc.player.getBlockPos(), SoundEvents.ENTITY_VILLAGER_YES, SoundCategory.PLAYERS, 1.0F, 1.0F);
                }
            } catch (Exception var4) {
            }
        });
    }

    private void scheduleChunkScan() {
        if (!this.shouldStop && this.scannerThread != null && !this.scannerThread.isShutdown()) {
            if (this.currentScanTask != null && !this.currentScanTask.isDone()) {
                this.currentScanTask.cancel(false);
            }

            this.currentScanTask = this.scannerThread.submit(this::scanChunksBackground);
        }
    }

    private void scheduleHoleScan() {
        if (!this.shouldStop && this.scannerThread != null && !this.scannerThread.isShutdown()) {
            if (this.currentHoleScanTask != null && !this.currentHoleScanTask.isDone()) {
                this.currentHoleScanTask.cancel(false);
            }

            this.currentHoleScanTask = this.scannerThread.submit(this::scanHolesBackground);
        }
    }

    private void scheduleTraderScan() {
        if (!this.shouldStop && this.scannerThread != null && !this.scannerThread.isShutdown()) {
            if (this.currentTraderScanTask != null && !this.currentTraderScanTask.isDone()) {
                this.currentTraderScanTask.cancel(false);
            }

            this.currentTraderScanTask = this.scannerThread.submit(this::scanTradersBackground);
        }
    }

    private void scanChunksBackground() {
        if (!this.shouldStop && mc.world != null && mc.player != null) {
            try {
                for (WorldChunk chunk : this.getLoadedChunks()) {
                    if (!this.shouldStop && chunk != null && !chunk.isEmpty()) {
                        ChunkPos chunkPos = chunk.getPos();
                        if (!this.scannedChunks.contains(chunkPos)) {
                            boolean wasAlreadyFlagged = this.flaggedChunks.contains(chunkPos);
                            Set<BlockPos> chunkFlaggedBlocks = this.scanChunkForCoveredOres(chunk);
                            boolean shouldFlag = !chunkFlaggedBlocks.isEmpty();
                            if (shouldFlag) {
                                this.flaggedChunks.add(chunkPos);
                                this.flaggedBlocks.put(chunkPos, chunkFlaggedBlocks);
                                if (!wasAlreadyFlagged && !this.notifiedChunks.contains(chunkPos)) {
                                    this.notifyChunkFound(chunkPos);
                                    this.notifiedChunks.add(chunkPos);
                                }
                            } else {
                                this.flaggedChunks.remove(chunkPos);
                                this.notifiedChunks.remove(chunkPos);
                                this.flaggedBlocks.remove(chunkPos);
                            }

                            this.scannedChunks.add(chunkPos);
                            Thread.sleep(1L);
                        }
                    }
                }
            } catch (InterruptedException var8) {
                Thread.currentThread().interrupt();
            } catch (Exception var9) {
                System.err.println("Error in ChunkFinder background scanning: " + var9.getMessage());
            }
        }
    }

    private void scanTradersBackground() {
        if (!this.shouldStop && mc.world != null && mc.player != null) {
            try {
                int entityCount = 0;

                for (Entity entity : mc.world.getEntities()) {
                    if (!this.shouldStop && entity != null) {
                        entityCount++;
                        if (entity instanceof WanderingTraderEntity || entity instanceof TraderLlamaEntity) {
                            Vec3d pos = entity.getPos();
                            ChunkPos chunkPos = new ChunkPos((int)Math.floor(pos.x / 16.0), (int)Math.floor(pos.z / 16.0));
                            int entityId = entity.getId();
                            boolean hasNotifiedThisTrader = this.notifiedTraders.contains(entityId);
                            boolean wasAlreadyFlagged = this.traderChunks.contains(chunkPos);
                            this.traderChunks.add(chunkPos);
                            if (!hasNotifiedThisTrader) {
                                this.notifyTraderChunkFound(chunkPos);
                                this.notifiedTraders.add(entityId);
                                this.notifiedTraderChunks.add(chunkPos);
                            }
                        }

                        if (entityCount > 0 && entityCount % 100 == 0) {
                            Thread.sleep(5L);
                        }
                    }
                }

                Set<ChunkPos> chunksToRemove = new HashSet<>();
                Set<Integer> tradersToRemove = new HashSet<>();

                for (ChunkPos chunkPos : this.traderChunks) {
                    if (!this.hasTraderInChunk(chunkPos)) {
                        chunksToRemove.add(chunkPos);
                    }
                }

                for (Integer traderId : this.notifiedTraders) {
                    boolean traderExists = false;

                    for (Entity entityx : mc.world.getEntities()) {
                        if (entityx != null
                            && entityx.getId() == traderId
                            && (entityx instanceof WanderingTraderEntity || entityx instanceof TraderLlamaEntity)) {
                            traderExists = true;
                            break;
                        }
                    }

                    if (!traderExists) {
                        tradersToRemove.add(traderId);
                    }
                }

                for (ChunkPos chunkPosx : chunksToRemove) {
                    this.traderChunks.remove(chunkPosx);
                    this.notifiedTraderChunks.remove(chunkPosx);
                }

                for (Integer traderId : tradersToRemove) {
                    this.notifiedTraders.remove(traderId);
                }
            } catch (InterruptedException var9) {
                Thread.currentThread().interrupt();
            } catch (Exception var10) {
                System.err.println("Error in ChunkFinder trader scanning: " + var10.getMessage());
            }
        }
    }

    private boolean hasTraderInChunk(ChunkPos chunkPos) {
        if (!this.shouldStop && mc.world != null) {
            try {
                int startX = chunkPos.x * 16;
                int startZ = chunkPos.z * 16;
                int endX = startX + 16;
                int endZ = startZ + 16;

                for (Entity entity : mc.world.getEntities()) {
                    if (entity != null) {
                        Vec3d pos = entity.getPos();
                        int entityX = (int)Math.floor(pos.x);
                        int entityZ = (int)Math.floor(pos.z);
                        if (entityX >= startX
                            && entityX < endX
                            && entityZ >= startZ
                            && entityZ < endZ
                            && (entity instanceof WanderingTraderEntity || entity instanceof TraderLlamaEntity)) {
                            return true;
                        }
                    }
                }
            } catch (Exception var11) {
            }

            return false;
        } else {
            return false;
        }
    }

    private void scanHolesBackground() {
        if (!this.shouldStop && mc.world != null && mc.player != null) {
            try {
                for (WorldChunk chunk : this.getLoadedChunks()) {
                    if (!this.shouldStop && chunk != null && !chunk.isEmpty()) {
                        ChunkPos chunkPos = chunk.getPos();
                        if (!this.holeScannedChunks.contains(chunkPos)) {
                            this.scanChunkForHoles(chunk);
                            this.holeScannedChunks.add(chunkPos);
                            Thread.sleep(2L);
                        }
                    }
                }
            } catch (InterruptedException var5) {
                Thread.currentThread().interrupt();
            } catch (Exception var6) {
                System.err.println("Error in ChunkFinder hole scanning: " + var6.getMessage());
            }
        }
    }

    private void scanChunkForHoles(WorldChunk chunk) {
        if (!this.shouldStop && chunk != null && !chunk.isEmpty()) {
            ChunkPos chunkPos = chunk.getPos();
            int xStart = chunkPos.getStartX();
            int zStart = chunkPos.getStartZ();
            int yMin = Math.max(chunk.getBottomY(), 8);
            int yMax = Math.min(chunk.getBottomY() + chunk.getHeight(), 60);

            for (int x = xStart; x < xStart + 16; x++) {
                for (int z = zStart; z < zStart + 16; z++) {
                    for (int y = yMin; y < yMax; y++) {
                        if (this.shouldStop) {
                            return;
                        }

                        BlockPos pos = new BlockPos(x, y, z);

                        try {
                            if (this.detect1x1Holes.isEnabled()) {
                                this.checkHole1x1(pos);
                            }

                            if (this.detect3x1Holes.isEnabled()) {
                                this.checkHole3x1(pos);
                            }
                        } catch (Exception var12) {
                        }
                    }
                }
            }
        }
    }

    private void checkHole1x1(BlockPos pos) {
        if (this.isValidHoleSection(pos)) {
            Mutable currentPos = pos.mutableCopy();

            while (this.isValidHoleSection(currentPos)) {
                currentPos.move(Direction.UP);
            }

            if ((float)(currentPos.getY() - pos.getY()) >= this.minHoleDepth.getCurrent()) {
                Box holeBox = new Box(
                    (double)pos.getX(),
                    (double)pos.getY(),
                    (double)pos.getZ(),
                    (double)(pos.getX() + 1),
                    (double)currentPos.getY(),
                    (double)(pos.getZ() + 1)
                );
                if (!this.holes1x1.contains(holeBox) && this.holes1x1.stream().noneMatch(existingHole -> existingHole.intersects(holeBox))) {
                    this.holes1x1.add(holeBox);
                }
            }
        }
    }

    private void checkHole3x1(BlockPos pos) {
        if (this.isValid3x1HoleSectionX(pos)) {
            Mutable currentPos = pos.mutableCopy();

            while (this.isValid3x1HoleSectionX(currentPos)) {
                currentPos.move(Direction.UP);
            }

            if ((float)(currentPos.getY() - pos.getY()) >= this.minHoleDepth.getCurrent()) {
                Box holeBox = new Box(
                    (double)pos.getX(),
                    (double)pos.getY(),
                    (double)pos.getZ(),
                    (double)(pos.getX() + 3),
                    (double)currentPos.getY(),
                    (double)(pos.getZ() + 1)
                );
                if (!this.holes3x1.contains(holeBox) && this.holes3x1.stream().noneMatch(existingHole -> existingHole.intersects(holeBox))) {
                    this.holes3x1.add(holeBox);
                }
            }
        }

        if (this.isValid3x1HoleSectionZ(pos)) {
            Mutable currentPos = pos.mutableCopy();

            while (this.isValid3x1HoleSectionZ(currentPos)) {
                currentPos.move(Direction.UP);
            }

            if ((float)(currentPos.getY() - pos.getY()) >= this.minHoleDepth.getCurrent()) {
                Box holeBox = new Box(
                    (double)pos.getX(),
                    (double)pos.getY(),
                    (double)pos.getZ(),
                    (double)(pos.getX() + 1),
                    (double)currentPos.getY(),
                    (double)(pos.getZ() + 3)
                );
                if (!this.holes3x1.contains(holeBox) && this.holes3x1.stream().noneMatch(existingHole -> existingHole.intersects(holeBox))) {
                    this.holes3x1.add(holeBox);
                }
            }
        }
    }

    private boolean isValidHoleSection(BlockPos pos) {
        return this.isAirBlock(pos)
            && !this.isAirBlock(pos.north())
            && !this.isAirBlock(pos.south())
            && !this.isAirBlock(pos.east())
            && !this.isAirBlock(pos.west());
    }

    private boolean isValid3x1HoleSectionX(BlockPos pos) {
        return this.isAirBlock(pos)
            && this.isAirBlock(pos.east())
            && this.isAirBlock(pos.east(2))
            && !this.isAirBlock(pos.north())
            && !this.isAirBlock(pos.south())
            && !this.isAirBlock(pos.east(3))
            && !this.isAirBlock(pos.west())
            && !this.isAirBlock(pos.east().north())
            && !this.isAirBlock(pos.east().south())
            && !this.isAirBlock(pos.east(2).north())
            && !this.isAirBlock(pos.east(2).south());
    }

    private boolean isValid3x1HoleSectionZ(BlockPos pos) {
        return this.isAirBlock(pos)
            && this.isAirBlock(pos.south())
            && this.isAirBlock(pos.south(2))
            && !this.isAirBlock(pos.east())
            && !this.isAirBlock(pos.west())
            && !this.isAirBlock(pos.south(3))
            && !this.isAirBlock(pos.north())
            && !this.isAirBlock(pos.south().east())
            && !this.isAirBlock(pos.south().west())
            && !this.isAirBlock(pos.south(2).east())
            && !this.isAirBlock(pos.south(2).west());
    }

    private boolean isAirBlock(BlockPos pos) {
        return mc.world == null ? false : mc.world.getBlockState(pos).isAir();
    }

    private Set<BlockPos> scanChunkForCoveredOres(WorldChunk chunk) {
        Set<BlockPos> foundBlocks = ConcurrentHashMap.newKeySet();
        if (!this.shouldStop && chunk != null && !chunk.isEmpty()) {
            ChunkPos chunkPos = chunk.getPos();
            int xStart = chunkPos.getStartX();
            int zStart = chunkPos.getStartZ();
            int yMin = Math.max(chunk.getBottomY(), 8);
            int yMax = Math.min(chunk.getBottomY() + chunk.getHeight(), 60);

            for (int x = xStart; x < xStart + 16; x++) {
                for (int z = zStart; z < zStart + 16; z++) {
                    for (int y = yMin; y < yMax; y++) {
                        if (this.shouldStop) {
                            return foundBlocks;
                        }

                        BlockPos pos = new BlockPos(x, y, z);

                        try {
                            BlockState state = chunk.getBlockState(pos);
                            Block block = state.getBlock();
                            if ((block == Blocks.TUFF || block == Blocks.DEEPSLATE)
                                && y > 8
                                && y < 60
                                && this.isBlockCovered(chunk, pos)
                                && this.isPositionUnderground(pos)) {
                                foundBlocks.add(pos);
                            }

                            if (this.isTargetBlock(state) && y >= 8 && y <= 60 && this.isBlockCovered(chunk, pos) && this.isPositionUnderground(pos)) {
                                foundBlocks.add(pos);
                            }

                            if (this.isRotatedDeepslate(state) && this.isBlockCovered(chunk, pos) && this.isPositionUnderground(pos)) {
                                foundBlocks.add(pos);
                            }
                        } catch (Exception var14) {
                        }
                    }
                }
            }

            return foundBlocks;
        } else {
            return foundBlocks;
        }
    }

    private boolean isTargetBlock(BlockState state) {
        if (state != null && !state.isAir()) {
            Block block = state.getBlock();
            return block == Blocks.DEEPSLATE ? !this.isRotatedDeepslate(state) : block == Blocks.TUFF;
        } else {
            return false;
        }
    }

    private boolean isRotatedDeepslate(BlockState state) {
        if (state != null && !state.isAir()) {
            Block block = state.getBlock();
            if (block == Blocks.DEEPSLATE && state.contains(PillarBlock.AXIS)) {
                Axis axis = (Axis)state.get(PillarBlock.AXIS);
                return axis == Axis.X || axis == Axis.Z;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private boolean isBlockCovered(WorldChunk chunk, BlockPos pos) {
        Direction[] directions = new Direction[]{
            Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST
        };

        for (Direction dir : directions) {
            BlockPos adjacentPos = pos.offset(dir);

            try {
                BlockState adjacentState = null;
                if (mc.world == null) {
                    return false;
                }

                adjacentState = mc.world.getBlockState(adjacentPos);
                if (adjacentState.isAir() || this.isTransparentBlock(adjacentState)) {
                    return false;
                }

                if (!adjacentState.isSolidBlock(mc.world, adjacentPos)) {
                    return false;
                }
            } catch (Exception var10) {
                return false;
            }
        }

        return true;
    }

    private boolean isTransparentBlock(BlockState state) {
        Block block = state.getBlock();
        if (block == Blocks.GLASS
            || block == Blocks.WATER
            || block == Blocks.LAVA
            || block == Blocks.ICE
            || block == Blocks.PACKED_ICE
            || block == Blocks.BLUE_ICE) {
            return true;
        } else {
            return block != Blocks.WHITE_STAINED_GLASS
                    && block != Blocks.ORANGE_STAINED_GLASS
                    && block != Blocks.MAGENTA_STAINED_GLASS
                    && block != Blocks.LIGHT_BLUE_STAINED_GLASS
                    && block != Blocks.YELLOW_STAINED_GLASS
                    && block != Blocks.LIME_STAINED_GLASS
                    && block != Blocks.PINK_STAINED_GLASS
                    && block != Blocks.GRAY_STAINED_GLASS
                    && block != Blocks.LIGHT_GRAY_STAINED_GLASS
                    && block != Blocks.CYAN_STAINED_GLASS
                    && block != Blocks.PURPLE_STAINED_GLASS
                    && block != Blocks.BLUE_STAINED_GLASS
                    && block != Blocks.BROWN_STAINED_GLASS
                    && block != Blocks.GREEN_STAINED_GLASS
                    && block != Blocks.RED_STAINED_GLASS
                    && block != Blocks.BLACK_STAINED_GLASS
                    && block != Blocks.TINTED_GLASS
                ? block == Blocks.OAK_LEAVES
                    || block == Blocks.SPRUCE_LEAVES
                    || block == Blocks.BIRCH_LEAVES
                    || block == Blocks.JUNGLE_LEAVES
                    || block == Blocks.ACACIA_LEAVES
                    || block == Blocks.DARK_OAK_LEAVES
                    || block == Blocks.MANGROVE_LEAVES
                    || block == Blocks.CHERRY_LEAVES
                : true;
        }
    }

    private boolean isPositionUnderground(BlockPos pos) {
        if (mc.world == null) {
            return false;
        } else {
            int checkHeight = Math.min(pos.getY() + 50, 80);
            int solidBlocksAbove = 0;

            for (int y = pos.getY() + 1; y <= checkHeight; y++) {
                BlockPos checkPos = new BlockPos(pos.getX(), y, pos.getZ());
                BlockState state = mc.world.getBlockState(checkPos);
                if (!state.isAir() && state.isSolidBlock(mc.world, checkPos)) {
                    solidBlocksAbove++;
                }
            }

            return solidBlocksAbove >= 3;
        }
    }

    private void cleanupDistantChunks() {
        if (mc.player != null) {
            int viewDist = (Integer)mc.options.getViewDistance().getValue();
            int playerChunkX = (int)mc.player.getX() / 16;
            int playerChunkZ = (int)mc.player.getZ() / 16;
            this.flaggedChunks.removeIf(chunkPos -> {
                int dx = Math.abs(chunkPos.x - playerChunkX);
                int dz = Math.abs(chunkPos.z - playerChunkZ);
                boolean shouldRemove = dx > viewDist || dz > viewDist;
                if (shouldRemove) {
                    this.flaggedBlocks.remove(chunkPos);
                }

                return shouldRemove;
            });
            this.scannedChunks.removeIf(chunkPos -> {
                int dx = Math.abs(chunkPos.x - playerChunkX);
                int dz = Math.abs(chunkPos.z - playerChunkZ);
                return dx > viewDist || dz > viewDist;
            });
            this.notifiedChunks.removeIf(chunkPos -> {
                int dx = Math.abs(chunkPos.x - playerChunkX);
                int dz = Math.abs(chunkPos.z - playerChunkZ);
                return dx > viewDist || dz > viewDist;
            });
        }
    }

    private void cleanupDistantHoles() {
        if (mc.player != null) {
            int viewDist = (Integer)mc.options.getViewDistance().getValue();
            int playerChunkX = (int)mc.player.getX() / 16;
            int playerChunkZ = (int)mc.player.getZ() / 16;
            this.holes1x1.removeIf(holeBox -> {
                int holeChunkX = (int)Math.floor(holeBox.getCenter().getX()) / 16;
                int holeChunkZ = (int)Math.floor(holeBox.getCenter().getZ()) / 16;
                int dx = Math.abs(holeChunkX - playerChunkX);
                int dz = Math.abs(holeChunkZ - playerChunkZ);
                return dx > viewDist || dz > viewDist;
            });
            this.holes3x1.removeIf(holeBox -> {
                int holeChunkX = (int)Math.floor(holeBox.getCenter().getX()) / 16;
                int holeChunkZ = (int)Math.floor(holeBox.getCenter().getZ()) / 16;
                int dx = Math.abs(holeChunkX - playerChunkX);
                int dz = Math.abs(holeChunkZ - playerChunkZ);
                return dx > viewDist || dz > viewDist;
            });
            this.holeScannedChunks.removeIf(chunkPos -> {
                int dx = Math.abs(chunkPos.x - playerChunkX);
                int dz = Math.abs(chunkPos.z - playerChunkZ);
                return dx > viewDist || dz > viewDist;
            });
        }
    }

    private void cleanupDistantTraderChunks() {
        if (mc.player != null) {
            int viewDist = (Integer)mc.options.getViewDistance().getValue();
            int playerChunkX = (int)mc.player.getX() / 16;
            int playerChunkZ = (int)mc.player.getZ() / 16;
            this.traderChunks.removeIf(chunkPos -> {
                int dx = Math.abs(chunkPos.x - playerChunkX);
                int dz = Math.abs(chunkPos.z - playerChunkZ);
                return dx > viewDist || dz > viewDist;
            });
            this.notifiedTraderChunks.removeIf(chunkPos -> {
                int dx = Math.abs(chunkPos.x - playerChunkX);
                int dz = Math.abs(chunkPos.z - playerChunkZ);
                return dx > viewDist || dz > viewDist;
            });
        }
    }

    private List<WorldChunk> getLoadedChunks() {
        List<WorldChunk> chunks = new ArrayList<>();
        int viewDist = (Integer)mc.options.getViewDistance().getValue();

        for (int x = -viewDist; x <= viewDist; x++) {
            for (int z = -viewDist; z <= viewDist; z++) {
                WorldChunk chunk = mc.world
                    .getChunkManager()
                    .getWorldChunk((int)mc.player.getX() / 16 + x, (int)mc.player.getZ() / 16 + z);
                if (chunk != null) {
                    chunks.add(chunk);
                }
            }
        }

        return chunks;
    }

    @EventTarget
    public void onRender3D(EventRender3D event) {
        if (mc.player != null && mc.world != null) {
            MatrixStack matrices = event.getMatrix();
            if (!this.flaggedChunks.isEmpty()) {
                for (ChunkPos chunkPos : this.flaggedChunks) {
                    int a = (int)this.fillAlpha.getCurrent();
                    this.renderChunkHighlight(matrices, chunkPos, new Color(0, 255, 0, a));
                    if (this.highlightBlocks.isEnabled()) {
                        this.renderFlaggedBlocks(matrices, chunkPos);
                    }
                }
            }

            if (this.detectTraders.isEnabled() && !this.traderChunks.isEmpty()) {
                for (ChunkPos chunkPosx : this.traderChunks) {
                    int a2 = (int)this.fillAlpha.getCurrent();
                    Color base = new Color(
                        (int)this.traderChunkColorRed.getCurrent(),
                        (int)this.traderChunkColorGreen.getCurrent(),
                        (int)this.traderChunkColorBlue.getCurrent(),
                        (int)this.traderChunkColorAlpha.getCurrent()
                    );
                    this.renderChunkHighlight(matrices, chunkPosx, new Color(base.getRed(), base.getGreen(), base.getBlue(), a2));
                }
            }

            if (this.holeESP.isEnabled()) {
                if (this.detect1x1Holes.isEnabled() && !this.holes1x1.isEmpty()) {
                    this.renderHoles1x1(matrices);
                }

                if (this.detect3x1Holes.isEnabled() && !this.holes3x1.isEmpty()) {
                    this.renderHoles3x1(matrices);
                }
            }
        }
    }

    private void renderHoles1x1(MatrixStack stack) {
        Color color = new Color(255, 255, 255, 100);

        for (Box hole : this.holes1x1) {
            if (this.holeFill.isEnabled() || this.holeOutline.isEnabled()) {
                Render3DUtil.drawBox(hole, color.getRGB(), 1.0F);
            }
        }
    }

    private void renderHoles3x1(MatrixStack stack) {
        Color color = new Color(255, 255, 255, 100);

        for (Box hole : this.holes3x1) {
            if (this.holeFill.isEnabled() || this.holeOutline.isEnabled()) {
                Render3DUtil.drawBox(hole, color.getRGB(), 1.0F);
            }
        }
    }

    private void renderChunkHighlight(MatrixStack stack, ChunkPos chunkPos, Color renderColor) {
        int startX = chunkPos.x * 16;
        int startZ = chunkPos.z * 16;
        int endX = startX + 16;
        int endZ = startZ + 16;
        double y = 60.0;
        double height = 0.1F;
        Box chunkBox = new Box((double)startX, y, (double)startZ, (double)endX, y + height, (double)endZ);
        if (this.fill.isEnabled() || this.outline.isEnabled()) {
            Render3DUtil.drawBox(chunkBox, renderColor.getRGB(), 1.0F);
        }

        if (this.tracers.isEnabled()) {
            Vec3d crosshairPos = mc.player.getEyePos().add(mc.player.getRotationVector().multiply(2.0));
            Vec3d chunkCenter = new Vec3d((double)(startX + 8), y + height / 2.0, (double)(startZ + 8));
            Render3DUtil.drawLine(crosshairPos, chunkCenter, renderColor.getRGB(), 1.0F, false);
        }
    }

    private void renderFlaggedBlocks(MatrixStack stack, ChunkPos chunkPos) {
        Set<BlockPos> blocks = this.flaggedBlocks.get(chunkPos);
        if (blocks != null && !blocks.isEmpty()) {
            Color blockColor = new Color(255, 0, 0, 120);

            for (BlockPos blockPos : blocks) {
                Box blockBox = new Box(blockPos);
                Render3DUtil.drawBox(blockBox, blockColor.getRGB(), 1.0F);
            }
        }
    }

    public Set<ChunkPos> getFlaggedChunks() {
        return new HashSet<>(this.flaggedChunks);
    }

    public Set<ChunkPos> getTraderChunks() {
        return new HashSet<>(this.traderChunks);
    }

    public Set<Box> getHoles1x1() {
        return new HashSet<>(this.holes1x1);
    }

    public Set<Box> getHoles3x1() {
        return new HashSet<>(this.holes3x1);
    }
}
