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

import com.darkmagician6.eventapi.EventTarget;
import com.mojang.blaze3d.platform.GlStateManager.DstFactor;
import com.mojang.blaze3d.platform.GlStateManager.SrcFactor;
import com.mojang.blaze3d.systems.RenderSystem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.StreamSupport;
import net.minecraft.block.Blocks;
import net.minecraft.client.gl.ShaderProgramKeys;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferRenderer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.VertexFormat.DrawMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.projectile.ArrowEntity;
import net.minecraft.entity.projectile.PersistentProjectileEntity;
import net.minecraft.entity.projectile.ProjectileEntity;
import net.minecraft.entity.projectile.TridentEntity;
import net.minecraft.entity.projectile.thrown.ThrownItemEntity;
import net.minecraft.item.CrossbowItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.hit.HitResult.Type;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext.ShapeType;
import org.lwjgl.opengl.GL11;
import zenith.zov.Zenith;
import zenith.zov.base.events.impl.render.EventRender2D;
import zenith.zov.base.events.impl.render.EventRender3D;
import zenith.zov.base.font.Font;
import zenith.zov.base.font.Fonts;
import zenith.zov.client.modules.api.Category;
import zenith.zov.client.modules.api.Module;
import zenith.zov.client.modules.api.ModuleAnnotation;
import zenith.zov.utility.game.player.PlayerIntersectionUtil;
import zenith.zov.utility.game.player.RaytracingUtil;
import zenith.zov.utility.game.player.rotation.RotationUtil;
import zenith.zov.utility.math.MathUtil;
import zenith.zov.utility.math.ProjectionUtil;
import zenith.zov.utility.render.display.base.BorderRadius;
import zenith.zov.utility.render.display.base.color.ColorRGBA;
import zenith.zov.utility.render.level.Render3DUtil;

@ModuleAnnotation(
    name = "Predictions",
    category = Category.RENDER,
    description = "Shows where item will fall"
)
public final class Predictions extends Module {
    private final List<Predictions.Point> points = new ArrayList<>();
    public static Predictions INSTANCE = new Predictions();

    private Predictions() {
    }

    @EventTarget
    public void onDraw(EventRender2D e) {
        for (Predictions.Point point : this.points) {
            Vec3d vec3d = ProjectionUtil.worldSpaceToScreenSpace(point.pos);
            int ticks = point.ticks;
            if (ProjectionUtil.canSee(point.pos)) {
                Font font = Fonts.MEDIUM.getFont(7.0F);
                double time = (double)(ticks * 50) / 1000.0;
                String text = String.format("%.1f", time) + " sec";
                float textWidth = font.width(text);
                float iconSize = 11.2F;
                float padding = 2.0F;
                float centerX = (float)vec3d.getX();
                float centerY = (float)vec3d.getY();
                float totalWidth = iconSize + padding + textWidth;
                float rectX = centerX - totalWidth / 2.0F;
                float rectY = centerY - iconSize / 2.0F;
                e.getContext()
                    .drawRoundedRect(
                        rectX - padding,
                        rectY - padding,
                        totalWidth + padding * 2.0F,
                        iconSize + padding * 2.0F,
                        BorderRadius.all(2.0F),
                        Zenith.getInstance().getThemeManager().getCurrentTheme().getForegroundColor()
                    );
                float itemY = rectY + (iconSize - iconSize) / 2.0F;
                e.getContext().pushMatrix();
                e.getContext().getMatrices().translate(rectX, itemY, 0.0F);
                e.getContext().getMatrices().scale(0.7F, 0.7F, 1.0F);
                e.getContext().drawItem(point.stack, 0, 0);
                e.getContext().popMatrix();
                float textX = rectX + iconSize + padding;
                float textY = centerY - font.height() / 2.0F;
                e.getContext().drawText(font, text, textX, textY, Zenith.getInstance().getThemeManager().getCurrentTheme().getWhite());
            }
        }
    }

    @EventTarget
    public void onWorldRender(EventRender3D e) {
        this.points.clear();
        this.drawPredictionInHand(e.getMatrix(), StreamSupport.<ItemStack>stream(mc.player.getHandItems().spliterator(), false).toList());
        this.getProjectiles()
            .forEach(
                entity -> {
                    Vec3d motion = entity.getVelocity();
                    Vec3d pos = entity.getPos();
                    int ticks = 0;

                    for (int i = 0; i < 300; i++) {
                        Vec3d prevPos = pos;
                        pos = pos.add(motion);
                        motion = this.calculateMotion(entity, prevPos, motion);
                        HitResult result = RaytracingUtil.raycast(prevPos, pos, ShapeType.COLLIDER, entity);
                        if (!result.getType().equals(Type.MISS)) {
                            pos = result.getPos();
                        }

                        Render3DUtil.drawLine(
                            prevPos,
                            pos,
                            Zenith.getInstance().getThemeManager().getClientColor(i).mulAlpha(MathHelper.clamp((float)i / 25.0F, 0.0F, 1.0F)).getRGB(),
                            2.0F,
                            false
                        );
                        Vec3d finalPos = pos;
                        boolean inEntity = PlayerIntersectionUtil.streamEntities().filter(ent -> {
                            if (ent instanceof LivingEntity living && living != mc.player && living.isAlive()) {
                                return true;
                            }

                            return false;
                        }).anyMatch(ent -> ent.getBoundingBox().expand(0.25).intersects(prevPos, finalPos));
                        if (result.getType().equals(Type.BLOCK)
                            || pos.y < -128.0
                            || inEntity
                            || result.getType().equals(Type.ENTITY)) {
                            this.BreakingBad(entity, pos, ticks);
                            break;
                        }

                        ticks++;
                    }
                }
            );
    }

    public void drawPredictionInHand(MatrixStack matrix, List<ItemStack> stacks) {
        Item activeItem = mc.player.getActiveItem().getItem();
        Iterator var4 = stacks.iterator();
        if (var4.hasNext()) {
            label65: {
                ItemStack stack = (ItemStack)var4.next();
                Object var10000;
                Objects.requireNonNull(var10000);
                Object var7 = var10000;

                stack.getItem();
            }
        }
    }

    public void renderProjectileResults(MatrixStack matrix, List<HitResult> results) {
        GL11.glEnable(2881);
        RenderSystem.enableBlend();
        RenderSystem.disableCull();
        RenderSystem.disableDepthTest();
        RenderSystem.lineWidth(1.0F);
        RenderSystem.blendFunc(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_CONSTANT_ALPHA);
        RenderSystem.setShader(ShaderProgramKeys.RENDERTYPE_LINES);
        BufferBuilder buffer = Tessellator.getInstance().begin(DrawMode.LINES, VertexFormats.LINES);

        for (HitResult result : results) {
            Direction direction = this.getDirection(result);
            Vec3d renderPos = result.getPos().subtract(mc.getEntityRenderDispatcher().camera.getPos());
            int color = result.getType().equals(Type.ENTITY)
                ? ColorRGBA.RED.getRGB()
                : Zenith.getInstance().getThemeManager().getClientColor(90).getRGB();
            double width = 0.3;
            matrix.push();
            matrix.translate(renderPos.x, renderPos.y, renderPos.z);
            if (direction.equals(Direction.WEST) || direction.equals(Direction.EAST)) {
                matrix.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(90.0F));
            } else if (direction.equals(Direction.SOUTH) || direction.equals(Direction.NORTH)) {
                matrix.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90.0F));
            }

            int i = 0;

            for (int size = 90; i <= size; i++) {
                Render3DUtil.vertexLine(matrix, buffer, MathUtil.cosSin(i, size, width), MathUtil.cosSin(i + 1, size, width), color);
            }

            Render3DUtil.vertexLine(matrix, buffer, new Vec3d(0.0, 0.0, -width), new Vec3d(0.0, 0.0, width), color);
            Render3DUtil.vertexLine(matrix, buffer, new Vec3d(-width, 0.0, 0.0), new Vec3d(width, 0.0, 0.0), color);
            matrix.pop();
        }

        BufferRenderer.drawWithGlobalProgram(buffer.end());
        RenderSystem.enableDepthTest();
        RenderSystem.enableCull();
        RenderSystem.disableBlend();
        GL11.glDisable(2881);
    }

    public List<Entity> getProjectiles() {
        return PlayerIntersectionUtil.streamEntities()
            .filter(e -> (e instanceof PersistentProjectileEntity || e instanceof ThrownItemEntity || e instanceof ItemEntity) && !this.visible(e))
            .toList();
    }

    public List<HitResult> checkTrajectory(ProjectileEntity entity, double velocity) {
        return new ArrayList<>(Collections.singleton(this.checkTrajectory(RotationUtil.getClientRotation().toVector(), entity, velocity)));
    }

    public HitResult checkTrajectory(Vec3d lookVec, ProjectileEntity entity, double velocity) {
        double distance = Math.sqrt(lookVec.x * lookVec.x + lookVec.y * lookVec.y + lookVec.z * lookVec.z);
        Vec3d motion = mc.player.getPos().subtract(mc.player.prevX, mc.player.prevY, mc.player.prevZ);
        if (entity instanceof ArrowEntity arrow && arrow.getItemStack().getItem() instanceof CrossbowItem) {
            motion = Vec3d.ZERO;
        }

        return this.traceTrajectory(
            mc.player.getEyePos().add(MathUtil.interpolate(mc.player).subtract(mc.player.getPos())),
            lookVec.multiply(velocity / distance).add(motion),
            entity
        );
    }

    public HitResult calcTrajectory(ProjectileEntity e) {
        return this.traceTrajectory(e.getPos(), e.getVelocity(), e);
    }

    public HitResult traceTrajectory(Vec3d pos, Vec3d motion, ProjectileEntity entity) {
        for (int i = 0; i < 300; i++) {
            Vec3d prevPos = pos;
            pos = pos.add(motion);
            motion = this.calculateMotion(entity, prevPos, motion);
            HitResult result = RaytracingUtil.raycast(prevPos, pos, ShapeType.COLLIDER, entity);
            if (!result.getType().equals(Type.MISS)) {
                return result;
            }

            if (PlayerIntersectionUtil.streamEntities().filter(ent -> {
                if (ent != entity.getOwner() && ent instanceof LivingEntity living && living != mc.player && living.isAlive()) {
                    return true;
                }

                return false;
            }).anyMatch(ent -> ent.getBoundingBox().expand(0.3).intersects(prevPos, pos))) {
                return new HitResult(pos) {
                    public Type getType() {
                        return Type.ENTITY;
                    }
                };
            }

            if (pos.y < -128.0) {
                break;
            }
        }

        return null;
    }

    public Vec3d calculateMotion(Entity entity, Vec3d prevPos, Vec3d motion) {
        boolean isInWater = Objects.requireNonNull(mc.world)
            .getBlockState(BlockPos.ofFloored(prevPos))
            .getFluidState()
            .isIn(FluidTags.WATER);
        Objects.requireNonNull(entity);

        float multiply = switch (entity) {
            case TridentEntity i -> 0.99F;
            case PersistentProjectileEntity ix when isInWater -> 0.6F;
            default -> isInWater ? 0.8F : 0.99F;
        };
        return motion.multiply((double)multiply).add(0.0, -entity.getFinalGravity(), 0.0);
    }

    private void BreakingBad(Entity entity, Vec3d pos, int ticks) {
        Objects.requireNonNull(entity);
        switch (entity) {
            case ItemEntity item:
                this.points.add(new Predictions.Point(item.getStack(), pos, ticks));
                break;
            case ThrownItemEntity thrown:
                this.points.add(new Predictions.Point(thrown.getStack(), pos, ticks));
                break;
            case PersistentProjectileEntity persistent:
                this.points.add(new Predictions.Point(persistent.getItemStack(), pos, ticks));
                break;
        }
    }

    private Direction getDirection(HitResult result) {
        if (result instanceof BlockHitResult blockHitResult) {
            return blockHitResult.getSide();
        } else {
            Vec3d diff = result.getPos().subtract(mc.player.getEyePos()).normalize();
            return Direction.getFacing(diff.x, diff.y, diff.z);
        }
    }

    private boolean visible(Entity entity) {
        boolean posChange = entity.getX() == entity.prevX
            && entity.getY() == entity.prevY
            && entity.getZ() == entity.prevZ;
        boolean itemEntityCheck = entity instanceof ItemEntity
            && (entity.isOnGround() || PlayerIntersectionUtil.isBoxInBlock(entity.getBoundingBox().expand(2.0), Blocks.WATER));
        return posChange || itemEntityCheck;
    }

    private static record Point(ItemStack stack, Vec3d pos, int ticks) {
    }
}
