package dev.crucifixbeta.api.utils.combat.crystal;

import dev.crucifixbeta.api.utils.Wrapper;
import dev.crucifixbeta.api.utils.math.DamageUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.fluid.FluidState;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.BlockView;

@Environment(EnvType.CLIENT)
public final class CrystalWorldSnapshot implements BlockView {
   private final Vec3d eyePos;
   private final CrystalEntitySnapshot localPlayer;
   private final List<CrystalEntitySnapshot> entities;
   private final List<CrystalEntitySnapshot> crystals;
   private final List<CrystalEntitySnapshot> livingEntities;
   private final Map<BlockPos, BlockState> blockStates;
   private final int bottomY;
   private final int height;

   private CrystalWorldSnapshot(
      Vec3d eyePos,
      CrystalEntitySnapshot localPlayer,
      List<CrystalEntitySnapshot> entities,
      List<CrystalEntitySnapshot> crystals,
      List<CrystalEntitySnapshot> livingEntities,
      Map<BlockPos, BlockState> blockStates,
      int bottomY,
      int height
   ) {
      this.eyePos = eyePos;
      this.localPlayer = localPlayer;
      this.entities = Collections.unmodifiableList(entities);
      this.crystals = Collections.unmodifiableList(crystals);
      this.livingEntities = Collections.unmodifiableList(livingEntities);
      this.blockStates = Collections.unmodifiableMap(blockStates);
      this.bottomY = bottomY;
      this.height = height;
   }

   public static CrystalWorldSnapshot capture(ClientWorld world, Iterable<Entity> source, Vec3d eyePos, double radius) {
      ArrayList var5 = new ArrayList();
      ArrayList var6 = new ArrayList();
      ArrayList var7 = new ArrayList();
      HashMap var8 = new HashMap();
      CrystalEntitySnapshot var9 = CrystalEntitySnapshot.of(Wrapper.mc.player);
      double var10 = radius * radius;

      for (Entity var13 : source) {
         if (var13 != null && !(var13.getPos().squaredDistanceTo(eyePos) > var10)) {
            CrystalEntitySnapshot var14 = CrystalEntitySnapshot.of(var13);
            var5.add(var14);
            if (var14.endCrystal()) {
               var6.add(var14);
            }

            if (var14.living()) {
               var7.add(var14);
            }
         }
      }

      int var18 = (int)Math.ceil(radius) + 2;
      BlockPos var19 = BlockPos.ofFloored(eyePos);

      for (int var20 = -var18; var20 <= var18; var20++) {
         for (int var15 = -var18; var15 <= var18; var15++) {
            for (int var16 = -var18; var16 <= var18; var16++) {
               if (var20 * var20 + var15 * var15 + var16 * var16 <= var18 * var18) {
                  BlockPos var17 = var19.add(var20, var15, var16);
                  var8.put(var17, world.getBlockState(var17));
               }
            }
         }
      }

      return new CrystalWorldSnapshot(eyePos, var9, var5, var6, var7, var8, world.getBottomY(), world.getHeight());
   }

   public Vec3d eyePos() {
      return this.eyePos;
   }

   public CrystalEntitySnapshot localPlayer() {
      return this.localPlayer;
   }

   public List<CrystalEntitySnapshot> crystals() {
      return this.crystals;
   }

   public List<CrystalEntitySnapshot> livingEntities() {
      return this.livingEntities;
   }

   public List<CrystalEntitySnapshot> entities(Box box) {
      ArrayList var2 = new ArrayList();

      for (CrystalEntitySnapshot var4 : this.entities) {
         if (var4.intersects(box)) {
            var2.add(var4);
         }
      }

      return var2;
   }

   public BlockState blockState(BlockPos pos) {
      return this.blockStates.getOrDefault(pos, Blocks.AIR.getDefaultState());
   }

   @Override
   public BlockState getBlockState(BlockPos pos) {
      return this.blockState(pos);
   }

   @Override
   public FluidState getFluidState(BlockPos pos) {
      return this.blockState(pos).getFluidState();
   }

   @Override
   public BlockEntity getBlockEntity(BlockPos pos) {
      return null;
   }

   @Override
   public int getBottomY() {
      return this.bottomY;
   }

   @Override
   public int getHeight() {
      return this.height;
   }

   public Block block(BlockPos pos) {
      return this.blockState(pos).getBlock();
   }

   public boolean isAir(BlockPos pos) {
      return this.blockState(pos).isAir();
   }

   public boolean canReplace(BlockPos pos) {
      return pos.getY() >= 320 ? false : this.blockState(pos).isReplaceable();
   }

   public boolean hasCrystal(BlockPos pos) {
      Box var2 = new Box(pos);

      for (CrystalEntitySnapshot var4 : this.crystals) {
         if (var4.intersects(var2)) {
            return true;
         }
      }

      return false;
   }

   public float explosionDamage(Vec3d explosionPos, CrystalEntitySnapshot target, boolean terrainIgnore) {
      if (target != null && target.living()) {
         double var4 = target.pos().distanceTo(explosionPos);
         if (var4 > 12.0) {
            return 0.0F;
         } else {
            double var6 = (double)this.getExposure(explosionPos, target.boundingBox(), terrainIgnore);
            double var8 = (1.0 - var4 / 12.0) * var6;
            float var10 = (float)((int)((var8 * var8 + var8) / 2.0 * 7.0 * 12.0 + 1.0));
            var10 = this.damageAfterArmor(var10, (float)target.armor(), target.armorToughness());
            if (target.resistanceLevel() > 0) {
               var10 *= 1.0F - (float)target.resistanceLevel() * 0.2F;
            }

            var10 = this.damageAfterProtection(var10, this.protectionAmount(target));
            return Math.max(var10, 0.0F);
         }
      } else {
         return 0.0F;
      }
   }

   private int protectionAmount(CrystalEntitySnapshot target) {
      int var2 = 0;

      for (ItemStack var4 : target.armorItems()) {
         var2 += DamageUtils.getProtectionAmount(var4);
      }

      return var2;
   }

   private float damageAfterArmor(float damage, float armor, float toughness) {
      float var4 = 2.0F + toughness / 4.0F;
      float var5 = MathHelper.clamp(armor - damage / var4, armor * 0.2F, 20.0F);
      return damage * (1.0F - var5 / 25.0F);
   }

   private float damageAfterProtection(float damage, int protection) {
      int var3 = MathHelper.clamp(protection, 0, 20);
      return damage * (1.0F - (float)var3 / 25.0F);
   }

   private float getExposure(Vec3d source, Box box, boolean terrainIgnore) {
      double var4 = box.maxX - box.minX;
      double var6 = box.maxY - box.minY;
      double var8 = box.maxZ - box.minZ;
      double var10 = 1.0 / (var4 * 2.0 + 1.0);
      double var12 = 1.0 / (var6 * 2.0 + 1.0);
      double var14 = 1.0 / (var8 * 2.0 + 1.0);
      if (!(var10 <= 0.0) && !(var12 <= 0.0) && !(var14 <= 0.0)) {
         int var16 = 0;
         int var17 = 0;
         double var18 = (1.0 - Math.floor(1.0 / var10) * var10) * 0.5;
         double var20 = (1.0 - Math.floor(1.0 / var14) * var14) * 0.5;
         var10 *= var4;
         var12 *= var6;
         var14 *= var8;

         for (double var22 = box.minX + var18; var22 <= box.maxX + var18; var22 += var10) {
            for (double var24 = box.minY; var24 <= box.maxY; var24 += var12) {
               for (double var26 = box.minZ + var20; var26 <= box.maxZ + var20; var26 += var14) {
                  Vec3d var28 = new Vec3d(var22, var24, var26);
                  if (!this.raycastBlocks(var28, source, terrainIgnore)) {
                     var16++;
                  }

                  var17++;
               }
            }
         }

         return var17 == 0 ? 0.0F : (float)var16 / (float)var17;
      } else {
         return 0.0F;
      }
   }

   private boolean raycastBlocks(Vec3d start, Vec3d end, boolean terrainIgnore) {
      return BlockView.raycast(start, end, null, (context, blockPos) -> {
         BlockState var6 = this.blockState(blockPos);
         if (terrainIgnore && var6.getBlock().getBlastResistance() < 600.0F) {
            return null;
         } else {
            return var6.getCollisionShape(this, blockPos).raycast(start, end, blockPos) == null ? null : Boolean.TRUE;
         }
      }, context -> Boolean.FALSE);
   }
}
