package dev.crucifixbeta.api.utils.math;

import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.utils.Wrapper;
import dev.crucifixbeta.api.utils.combat.CombatUtil;
import dev.crucifixbeta.core.impl.PlayerManager;
import dev.crucifixbeta.mod.modules.impl.client.ClientSetting;
import dev.crucifixbeta.mod.modules.impl.combat.Criticals;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
import java.util.function.BiFunction;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.AttributeModifiersComponent;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.DamageUtil;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttributeInstance;
import net.minecraft.entity.attribute.EntityAttributeModifier;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.item.MaceItem;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.registry.tag.DamageTypeTags;
import net.minecraft.registry.tag.EntityTypeTags;
import net.minecraft.util.hit.BlockHitResult;
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;
import net.minecraft.world.GameMode;
import net.minecraft.world.RaycastContext;
import net.minecraft.world.Heightmap.Type;
import net.minecraft.world.RaycastContext.FluidHandling;
import net.minecraft.world.RaycastContext.ShapeType;
import org.apache.commons.lang3.mutable.MutableInt;

@Environment(EnvType.CLIENT)
public class DamageUtils {
   public static final DamageUtils.RaycastFactory HIT_FACTORY = (context, blockPos) -> {
      BlockState var2 = Wrapper.mc.world.getBlockState(blockPos);
      return var2.getBlock().getBlastResistance() < 600.0F
         ? null
         : var2.getCollisionShape(Wrapper.mc.world, blockPos).raycast(context.start(), context.end(), blockPos);
   };

   public static float calculateDamage(BlockPos pos, LivingEntity entity) {
      return explosionDamage(entity, null, new Vec3d((double)pos.getX() + 0.5, (double)(pos.getY() + 1), (double)pos.getZ() + 0.5), 12.0F);
   }

   public static float calculateDamage(Vec3d pos, LivingEntity entity) {
      return explosionDamage(entity, null, pos, 12.0F);
   }

   public static float explosionDamage(
      LivingEntity target, Vec3d targetPos, Box targetBox, Vec3d explosionPos, float power, DamageUtils.RaycastFactory raycastFactory
   ) {
      double var6 = getDistance(targetPos.x, targetPos.y, targetPos.z, explosionPos.x, explosionPos.y, explosionPos.z);
      if (var6 > (double)power) {
         return 0.0F;
      } else {
         double var8 = (double)getExposure(explosionPos, targetBox, raycastFactory);
         double var10 = (1.0 - var6 / (double)power) * var8;
         float var12 = (float)((int)((var10 * var10 + var10) / 2.0 * 7.0 * 12.0 + 1.0));
         return calculateReductionsExplosion(var12, target, Wrapper.mc.world.getDamageSources().explosion(null));
      }
   }

   public static float anchorDamage(LivingEntity target, LivingEntity predict, Vec3d anchor) {
      return overridingExplosionDamage(target, predict, anchor, 10.0F, BlockPos.ofFloored(anchor), Blocks.AIR.getDefaultState());
   }

   public static float overridingExplosionDamage(
      LivingEntity target, LivingEntity predict, Vec3d explosionPos, float power, BlockPos overridePos, BlockState overrideState
   ) {
      return explosionDamage(target, predict, explosionPos, power, getOverridingHitFactory(overridePos, overrideState));
   }

   public static float explosionDamage(LivingEntity target, LivingEntity predict, Vec3d explosionPos, float power, DamageUtils.RaycastFactory raycastFactory) {
      if (target == null) {
         return 0.0F;
      } else {
         PlayerEntity var5;
         return target instanceof PlayerEntity && getGameMode(var5 = (PlayerEntity)target) == GameMode.CREATIVE
            ? 0.0F
            : explosionDamage(
               target,
               predict != null ? predict.getPos() : target.getPos(),
               predict != null ? predict.getBoundingBox() : target.getBoundingBox(),
               explosionPos,
               power,
               raycastFactory
            );
      }
   }

   public static float explosionDamage(LivingEntity target, LivingEntity predict, Vec3d explosionPos, float power) {
      if (target == null) {
         return 0.0F;
      } else {
         PlayerEntity var4;
         return target instanceof PlayerEntity && getGameMode(var4 = (PlayerEntity)target) == GameMode.CREATIVE
            ? 0.0F
            : explosionDamage(
               target,
               predict != null ? predict.getPos() : target.getPos(),
               predict != null ? predict.getBoundingBox() : target.getBoundingBox(),
               explosionPos,
               power,
               HIT_FACTORY
            );
      }
   }

   public static DamageUtils.RaycastFactory getOverridingHitFactory(BlockPos overridePos, BlockState overrideState) {
      return (context, blockPos) -> {
         BlockState var4;
         if (blockPos.equals(overridePos)) {
            var4 = overrideState;
         } else {
            var4 = Wrapper.mc.world.getBlockState(blockPos);
            if (var4.getBlock().getBlastResistance() < 600.0F) {
               return null;
            }
         }

         return var4.getCollisionShape(Wrapper.mc.world, blockPos).raycast(context.start(), context.end(), blockPos);
      };
   }

   public static float getAttackDamage(LivingEntity attacker, LivingEntity target) {
      float var2 = (float)attacker.getAttributeValue(EntityAttributes.GENERIC_ATTACK_DAMAGE);
      DamageSource var4;
      if (attacker instanceof PlayerEntity var3) {
         var4 = Wrapper.mc.world.getDamageSources().playerAttack(var3);
      } else {
         var4 = Wrapper.mc.world.getDamageSources().mobAttack(attacker);
      }

      StatusEffectInstance var5 = attacker.getStatusEffect(StatusEffects.STRENGTH);
      if (var5 != null) {
         var2 += 3.0F * (float)(var5.getAmplifier() + 1);
      }

      float var6 = modifyAttackDamage(attacker, target, attacker.getWeaponStack(), var4, var2);
      return calculateReductions(var6, target, var4);
   }

   public static float getAttackDamage(LivingEntity attacker, LivingEntity target, ItemStack weapon) {
      EntityAttributeInstance var3 = attacker.getAttributeInstance(EntityAttributes.GENERIC_ATTACK_DAMAGE);
      EntityAttributeInstance var4 = new EntityAttributeInstance(EntityAttributes.GENERIC_ATTACK_DAMAGE, o -> {
      });
      var4.setBaseValue(var3.getBaseValue());

      for (EntityAttributeModifier var6 : var3.getModifiers()) {
         var4.addTemporaryModifier(var6);
      }

      var4.removeModifier(Item.BASE_ATTACK_DAMAGE_MODIFIER_ID);
      AttributeModifiersComponent var10 = weapon.get(DataComponentTypes.ATTRIBUTE_MODIFIERS);
      if (var10 != null) {
         var10.applyModifiers(EquipmentSlot.MAINHAND, (entry, modifier) -> {
            if (entry == EntityAttributes.GENERIC_ATTACK_DAMAGE) {
               var4.updateModifier(modifier);
            }
         });
      }

      float var11 = (float)var4.getValue();
      DamageSource var8;
      if (attacker instanceof PlayerEntity var7) {
         var8 = Wrapper.mc.world.getDamageSources().playerAttack(var7);
      } else {
         var8 = Wrapper.mc.world.getDamageSources().mobAttack(attacker);
      }

      float var9 = modifyAttackDamage(attacker, target, weapon, var8, var11);
      return calculateReductions(var9, target, var8);
   }

   private static float modifyAttackDamage(LivingEntity attacker, LivingEntity target, ItemStack weapon, DamageSource damageSource, float damage) {
      Object2IntOpenHashMap var5 = new Object2IntOpenHashMap();
      getEnchantments(weapon, var5);
      float var6 = 0.0F;
      int var7 = getEnchantmentLevel(var5, Enchantments.SHARPNESS);
      if (var7 > 0) {
         var6 += 1.0F + 0.5F * (float)(var7 - 1);
      }

      int var8;
      if ((var8 = getEnchantmentLevel(var5, Enchantments.BANE_OF_ARTHROPODS)) > 0 && target.getType().isIn(EntityTypeTags.SENSITIVE_TO_BANE_OF_ARTHROPODS)) {
         var6 += 2.5F * (float)var8;
      }

      int var9;
      if ((var9 = getEnchantmentLevel(var5, Enchantments.IMPALING)) > 0 && target.getType().isIn(EntityTypeTags.SENSITIVE_TO_IMPALING)) {
         var6 += 2.5F * (float)var9;
      }

      int var10;
      if ((var10 = getEnchantmentLevel(var5, Enchantments.SMITE)) > 0 && target.getType().isIn(EntityTypeTags.SENSITIVE_TO_SMITE)) {
         var6 += 2.5F * (float)var10;
      }

      if (attacker instanceof PlayerEntity var11) {
         float var12 = var11.getAttackCooldownProgress(0.5F);
         damage *= 0.2F + var12 * var12 * 0.8F;
         var6 *= var12;
         Item var13 = weapon.getItem();
         MaceItem var14;
         float var15;
         if (var13 instanceof MaceItem && (var15 = (var14 = (MaceItem)var13).getBonusAttackDamage(target, damage, damageSource)) > 0.0F) {
            int var16 = getEnchantmentLevel(weapon, Enchantments.DENSITY);
            if (var16 > 0) {
               var15 += 0.5F * attacker.fallDistance;
            }

            damage += var15;
         }

         if (var12 > 0.9F
            && (
               attacker.fallDistance > 0.0F
                  || attacker == Wrapper.mc.player
                     && Criticals.INSTANCE.isOn()
                     && !Criticals.INSTANCE.mode.is(Criticals.Mode.Ground)
                     && (Wrapper.mc.player.isOnGround() || !Criticals.INSTANCE.onlyGround.getValue())
            )
            && (!attacker.isOnGround() || attacker == Wrapper.mc.player && Criticals.INSTANCE.isOn() && !Criticals.INSTANCE.mode.is(Criticals.Mode.Ground))
            && !attacker.isClimbing()
            && !attacker.isTouchingWater()
            && !attacker.hasStatusEffect(StatusEffects.BLINDNESS)
            && !attacker.hasVehicle()) {
            damage *= 1.5F;
         }
      }

      return damage + var6;
   }

   public static float fallDamage(LivingEntity entity) {
      if (entity instanceof PlayerEntity var1 && var1.getAbilities().flying) {
         return 0.0F;
      }

      if (!entity.hasStatusEffect(StatusEffects.SLOW_FALLING) && !entity.hasStatusEffect(StatusEffects.LEVITATION)) {
         int var3 = Wrapper.mc
            .world
            .getWorldChunk(entity.getBlockPos())
            .getHeightmap(Type.MOTION_BLOCKING)
            .get(entity.getBlockX() & 15, entity.getBlockZ() & 15);
         if (entity.getBlockY() >= var3) {
            return fallDamageReductions(entity, var3);
         } else {
            BlockHitResult var2 = Wrapper.mc
               .world
               .raycast(
                  new RaycastContext(
                     entity.getPos(),
                     new Vec3d(entity.getX(), (double)Wrapper.mc.world.getBottomY(), entity.getZ()),
                     ShapeType.COLLIDER,
                     FluidHandling.WATER,
                     entity
                  )
               );
            return var2.getType() == net.minecraft.util.hit.HitResult.Type.MISS ? 0.0F : fallDamageReductions(entity, var2.getBlockPos().getY());
         }
      } else {
         return 0.0F;
      }
   }

   private static float fallDamageReductions(LivingEntity entity, int surface) {
      int var2 = (int)(entity.getY() - (double)surface + (double)entity.fallDistance - 3.0);
      StatusEffectInstance var3 = entity.getStatusEffect(StatusEffects.JUMP_BOOST);
      if (var3 != null) {
         var2 -= var3.getAmplifier() + 1;
      }

      return calculateReductions((float)var2, entity, Wrapper.mc.world.getDamageSources().fall());
   }

   public static float calculateReductionsExplosion(float damage, LivingEntity entity, DamageSource damageSource) {
      if (damageSource.isScaledWithDifficulty()) {
         switch (Wrapper.mc.world.getDifficulty()) {
            case EASY:
               damage = Math.min(damage / 2.0F + 1.0F, damage);
               break;
            case HARD:
               damage *= 1.5F;
         }
      }

      damage = DamageUtil.getDamageLeft(entity, damage, damageSource, getArmor(entity), (float)getARMOR_TOUGHNESS(entity));
      damage = resistanceReduction(entity, damage);
      damage = DamageUtil.getInflictedDamage(damage, (float)getProtectionAmount(entity.getArmorItems()));
      return Math.max(damage, 0.0F);
   }

   public static float calculateReductions(float damage, LivingEntity entity, DamageSource damageSource) {
      if (damageSource.isScaledWithDifficulty()) {
         switch (Wrapper.mc.world.getDifficulty()) {
            case EASY:
               damage = Math.min(damage / 2.0F + 1.0F, damage);
               break;
            case HARD:
               damage *= 1.5F;
         }
      }

      damage = DamageUtil.getDamageLeft(entity, damage, damageSource, getArmor(entity), (float)getARMOR_TOUGHNESS(entity));
      damage = resistanceReduction(entity, damage);
      damage = protectionReduction(entity, damage, damageSource);
      return Math.max(damage, 0.0F);
   }

   public static double getARMOR_TOUGHNESS(LivingEntity entity) {
      PlayerEntity var1;
      PlayerManager.EntityAttribute var2;
      return entity instanceof PlayerEntity && (var2 = CrucifixBeta.PLAYER.map.get(var1 = (PlayerEntity)entity)) != null
         ? var2.toughness()
         : entity.getAttributeValue(EntityAttributes.GENERIC_ARMOR_TOUGHNESS);
   }

   private static float getArmor(LivingEntity entity) {
      PlayerEntity var1;
      PlayerManager.EntityAttribute var2;
      return entity instanceof PlayerEntity && (var2 = CrucifixBeta.PLAYER.map.get(var1 = (PlayerEntity)entity)) != null
         ? (float)var2.armor()
         : (float)Math.floor(entity.getAttributeValue(EntityAttributes.GENERIC_ARMOR));
   }

   private static float protectionReduction(LivingEntity player, float damage, DamageSource source) {
      if (source.isIn(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
         return damage;
      } else {
         int var3 = 0;

         for (ItemStack var5 : player.getAllArmorItems()) {
            Object2IntOpenHashMap var6 = new Object2IntOpenHashMap();
            getEnchantments(var5, var6);
            int var7 = getEnchantmentLevel(var6, Enchantments.PROTECTION);
            if (var7 > 0) {
               var3 += var7;
            }

            int var8;
            if ((var8 = getEnchantmentLevel(var6, Enchantments.FIRE_PROTECTION)) > 0 && source.isIn(DamageTypeTags.IS_FIRE)) {
               var3 += 2 * var8;
            }

            int var9;
            if ((var9 = getEnchantmentLevel(var6, Enchantments.BLAST_PROTECTION)) > 0 && source.isIn(DamageTypeTags.IS_EXPLOSION)) {
               var3 += 2 * var9;
            }

            int var10;
            if ((var10 = getEnchantmentLevel(var6, Enchantments.PROJECTILE_PROTECTION)) > 0 && source.isIn(DamageTypeTags.IS_PROJECTILE)) {
               var3 += 2 * var10;
            }

            int var11;
            if ((var11 = getEnchantmentLevel(var6, Enchantments.FEATHER_FALLING)) > 0 && source.isIn(DamageTypeTags.IS_FALL)) {
               var3 += 3 * var11;
            }
         }

         return DamageUtil.getInflictedDamage(damage, (float)var3);
      }
   }

   public static int getProtectionAmount(Iterable<ItemStack> equipment) {
      MutableInt var1 = new MutableInt();
      equipment.forEach(i -> var1.add(getProtectionAmount(i)));
      return var1.intValue();
   }

   public static int getProtectionAmount(ItemStack stack) {
      int var1 = EnchantmentHelper.getLevel(
         Wrapper.mc.world.getRegistryManager().get(Enchantments.BLAST_PROTECTION.getRegistryRef()).getEntry(Enchantments.BLAST_PROTECTION).get(), stack
      );
      int var2 = EnchantmentHelper.getLevel(
         Wrapper.mc.world.getRegistryManager().get(Enchantments.PROTECTION.getRegistryRef()).getEntry(Enchantments.PROTECTION).get(), stack
      );
      return var1 * 2 + var2;
   }

   private static float resistanceReduction(LivingEntity player, float damage) {
      StatusEffectInstance var2 = player.getStatusEffect(StatusEffects.RESISTANCE);
      if (var2 != null) {
         int var3 = var2.getAmplifier() + 1;
         damage *= 1.0F - (float)var3 * 0.2F;
      }

      return Math.max(damage, 0.0F);
   }

   private static float getExposure(Vec3d source, Box box, DamageUtils.RaycastFactory raycastFactory) {
      if (ClientSetting.INSTANCE.optimizedCalc.getValue()) {
         int var3 = 0;
         int var4 = 0;

         for (int var5 = 0; var5 <= 1; var5++) {
            for (int var6 = 0; var6 <= 1; var6++) {
               for (int var7 = 0; var7 <= 1; var7++) {
                  double var8 = MathHelper.lerp((double)var5, box.minX, box.maxX);
                  Vec3d var14 = new Vec3d(var8, MathHelper.lerp((double)var6, box.minY, box.maxY), MathHelper.lerp((double)var7, box.minZ, box.maxZ));
                  if (raycast(var14, source, CombatUtil.terrainIgnore) == net.minecraft.util.hit.HitResult.Type.MISS) {
                     var3++;
                  }

                  var4++;
               }
            }
         }

         return (float)var3 / (float)var4;
      } else {
         double var15 = box.maxX - box.minX;
         double var17 = box.maxY - box.minY;
         double var19 = box.maxZ - box.minZ;
         double var21 = 1.0 / (var15 * 2.0 + 1.0);
         double var23 = 1.0 / (var17 * 2.0 + 1.0);
         double var25 = 1.0 / (var19 * 2.0 + 1.0);
         if (var21 > 0.0 && var23 > 0.0 && var25 > 0.0) {
            int var27 = 0;
            int var28 = 0;
            double var29 = (1.0 - Math.floor(1.0 / var21) * var21) * 0.5;
            double var31 = (1.0 - Math.floor(1.0 / var25) * var25) * 0.5;
            var21 *= var15;
            var23 *= var17;
            var25 *= var19;
            double var33 = box.minX + var29;
            double var35 = box.minY;
            double var37 = box.minZ + var31;
            double var39 = box.maxX + var29;
            double var41 = box.maxY;
            double var43 = box.maxZ + var31;

            for (double var45 = var33; var45 <= var39; var45 += var21) {
               for (double var47 = var35; var47 <= var41; var47 += var23) {
                  for (double var49 = var37; var49 <= var43; var49 += var25) {
                     Vec3d var51 = new Vec3d(var45, var47, var49);
                     if (raycast(new DamageUtils.ExposureRaycastContext(var51, source), raycastFactory) == null) {
                        var27++;
                     }

                     var28++;
                  }
               }
            }

            return (float)var27 / (float)var28;
         } else {
            return 0.0F;
         }
      }
   }

   public static net.minecraft.util.hit.HitResult.Type raycast(Vec3d start, Vec3d end, boolean ignoreTerrain) {
      return BlockView.raycast(start, end, null, (innerContext, blockPos) -> {
         BlockState var5 = Wrapper.mc.world.getBlockState(blockPos);
         if (var5.getBlock().getBlastResistance() < 600.0F && ignoreTerrain) {
            return null;
         } else {
            BlockHitResult var6 = var5.getCollisionShape(Wrapper.mc.world, blockPos).raycast(start, end, blockPos);
            return var6 == null ? null : var6.getType();
         }
      }, innerContext -> net.minecraft.util.hit.HitResult.Type.MISS);
   }

   public static BlockHitResult raycast(DamageUtils.ExposureRaycastContext context, DamageUtils.RaycastFactory raycastFactory) {
      return BlockView.raycast(context.start(), context.end(), context, raycastFactory, ctx -> null);
   }

   public static int getEnchantmentLevel(ItemStack itemStack, RegistryKey<Enchantment> enchantment) {
      if (itemStack.isEmpty()) {
         return 0;
      } else {
         Object2IntArrayMap var2 = new Object2IntArrayMap();
         getEnchantments(itemStack, var2);
         return getEnchantmentLevel(var2, enchantment);
      }
   }

   public static int getEnchantmentLevel(Object2IntMap<RegistryEntry<Enchantment>> itemEnchantments, RegistryKey<Enchantment> enchantment) {
      ObjectIterator var2 = Object2IntMaps.fastIterable(itemEnchantments).iterator();

      while (var2.hasNext()) {
         Entry var3 = (Entry)var2.next();
         if (((RegistryEntry)var3.getKey()).matchesKey(enchantment)) {
            return var3.getIntValue();
         }
      }

      return 0;
   }

   public static double getDistance(double x1, double y1, double z1, double x2, double y2, double z2) {
      return Math.sqrt(squaredDistance(x1, y1, z1, x2, y2, z2));
   }

   public static GameMode getGameMode(PlayerEntity player) {
      if (player == null) {
         return null;
      } else {
         PlayerListEntry var1 = Wrapper.mc.getNetworkHandler().getPlayerListEntry(player.getUuid());
         return var1 == null ? null : var1.getGameMode();
      }
   }

   public static double squaredDistanceTo(Entity entity) {
      return squaredDistanceTo(entity.getX(), entity.getY(), entity.getZ());
   }

   public static double squaredDistanceTo(BlockPos blockPos) {
      return squaredDistanceTo((double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ());
   }

   public static double squaredDistanceTo(double x, double y, double z) {
      return squaredDistance(Wrapper.mc.player.getX(), Wrapper.mc.player.getY(), Wrapper.mc.player.getZ(), x, y, z);
   }

   public static double squaredDistance(double x1, double y1, double z1, double x2, double y2, double z2) {
      double var12 = x1 - x2;
      double var14 = y1 - y2;
      double var16 = z1 - z2;
      return org.joml.Math.fma(var12, var12, org.joml.Math.fma(var14, var14, var16 * var16));
   }

   public static void getEnchantments(ItemStack itemStack, Object2IntMap<RegistryEntry<Enchantment>> enchantments) {
      enchantments.clear();
      if (!itemStack.isEmpty()) {
         for (Entry var4 : itemStack.getItem() == Items.ENCHANTED_BOOK
            ? itemStack.get(DataComponentTypes.STORED_ENCHANTMENTS).getEnchantmentEntries()
            : itemStack.getEnchantments().getEnchantmentEntries()) {
            enchantments.put((RegistryEntry)var4.getKey(), var4.getIntValue());
         }
      }
   }

   @Environment(EnvType.CLIENT)
   public static record ExposureRaycastContext(Vec3d start, Vec3d end) {
   }

   @FunctionalInterface
   @Environment(EnvType.CLIENT)
   public interface RaycastFactory extends BiFunction<DamageUtils.ExposureRaycastContext, BlockPos, BlockHitResult> {
   }
}
