package zenith.zov.utility.game.player;

import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.IntPredicate;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import lombok.Generated;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.FoodComponent;
import net.minecraft.component.type.PotionContentsComponent;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectCategory;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.EnderChestInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket;
import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket;
import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Hand;
import zenith.zov.Zenith;
import zenith.zov.utility.game.player.rotation.Rotation;
import zenith.zov.utility.interfaces.IClient;
import zenith.zov.utility.math.MathUtil;

public final class PlayerInventoryUtil implements IClient {
    public static final List<KeyBinding> moveKeys = List.of(
        mc.options.forwardKey, mc.options.backKey, mc.options.leftKey, mc.options.rightKey, mc.options.jumpKey
    );

    public static void updateSlots() {
        ScreenHandler screenHandler = mc.player.currentScreenHandler;
        ItemStack stack = ((Item)Registries.ITEM.get((int)MathUtil.getRandom(0.0, 100.0))).getDefaultStack();
        mc.player
            .networkHandler
            .sendPacket(
                new ClickSlotC2SPacket(
                    screenHandler.syncId, screenHandler.getRevision(), 0, 0, SlotActionType.PICKUP_ALL, stack, Int2ObjectMaps.singleton(0, stack)
                )
            );
    }

    public static void closeScreen(boolean packet) {
        if (packet) {
            mc.player.networkHandler.sendPacket(new CloseHandledScreenC2SPacket(mc.player.currentScreenHandler.syncId));
        } else {
            mc.player.closeHandledScreen();
        }
    }

    public static void swapHand(Slot slot, Hand hand, boolean updateInventory) {
        if (slot != null
            && slot.id != -1
            && (!hand.equals(Hand.OFF_HAND) || slot.inventory instanceof PlayerInventory || slot.inventory instanceof EnderChestInventory)) {
            int button = hand.equals(Hand.MAIN_HAND) ? mc.player.getInventory().selectedSlot : 40;
            swapHand(slot, button, updateInventory);
        }
    }

    public static void swapHand(Slot slot, int button, boolean updateInventory) {
        clickSlot(slot, button, SlotActionType.SWAP, false);
        if (updateInventory) {
            updateSlots();
        }
    }

    public static void swapHand(Slot slot, int button) {
        clickSlot(slot, button, SlotActionType.SWAP, false);
    }

    public static void clickSlot(Slot slot, int button, SlotActionType clickType, boolean silent) {
        if (slot != null) {
            clickSlot(slot.id, button, clickType, silent);
        }
    }

    public static void clickSlot(int slotId, int buttonId, SlotActionType clickType, boolean silent) {
        clickSlot(mc.player.currentScreenHandler.syncId, slotId, buttonId, clickType, silent);
    }

    public static void clickSlot(int windowId, int slotId, int buttonId, SlotActionType clickType, boolean silent) {
        mc.interactionManager.clickSlot(windowId, slotId, buttonId, clickType, mc.player);
        if (silent) {
            mc.player.currentScreenHandler.onSlotClick(slotId, buttonId, clickType, mc.player);
        }
    }

    public static Slot getSlot(Item item) {
        return getSlot(item, s -> true);
    }

    public static Slot getSlot(Item item, Predicate<Slot> filter) {
        return getSlot(item, Comparator.comparingInt(s -> 0), filter);
    }

    public static Slot getSlot(Predicate<Slot> filter) {
        return slots().filter(filter).findFirst().orElse(null);
    }

    public static Slot getSlot(Predicate<Slot> filter, Comparator<Slot> comparator) {
        return slots().filter(filter).max(comparator).orElse(null);
    }

    public static Slot getSlot(Item item, Comparator<Slot> comparator, Predicate<Slot> filter) {
        return slots().filter(s -> s.getStack().getItem().equals(item)).filter(filter).max(comparator).orElse(null);
    }

    public static Slot getFoodMaxSaturationSlot() {
        return slots()
            .filter(
                s -> s.getStack().get(DataComponentTypes.FOOD) != null
                        && !((FoodComponent)s.getStack().get(DataComponentTypes.FOOD)).canAlwaysEat()
            )
            .max(Comparator.comparingDouble(s -> (double)((FoodComponent)s.getStack().get(DataComponentTypes.FOOD)).saturation()))
            .orElse(null);
    }

    public static Slot getSlot(List<Item> item) {
        return slots().filter(s -> item.contains(s.getStack().getItem())).findFirst().orElse(null);
    }

    public static Slot getPotion(RegistryEntry<StatusEffect> effect) {
        return slots()
            .filter(
                s -> {
                    PotionContentsComponent component = (PotionContentsComponent)s.getStack().get(DataComponentTypes.POTION_CONTENTS);
                    return component == null
                        ? false
                        : StreamSupport.<StatusEffectInstance>stream(component.getEffects().spliterator(), false)
                            .anyMatch(e -> e.getEffectType().equals(effect));
                }
            )
            .findFirst()
            .orElse(null);
    }

    public static Slot getPotionFromCategory(StatusEffectCategory category) {
        return slots()
            .filter(
                s -> {
                    ItemStack stack = s.getStack();
                    PotionContentsComponent component = (PotionContentsComponent)stack.get(DataComponentTypes.POTION_CONTENTS);
                    if (stack.getItem().equals(Items.SPLASH_POTION) && component != null) {
                        StatusEffectCategory category2 = category.equals(StatusEffectCategory.BENEFICIAL)
                            ? StatusEffectCategory.HARMFUL
                            : StatusEffectCategory.BENEFICIAL;
                        long effects = StreamSupport.<StatusEffectInstance>stream(component.getEffects().spliterator(), false)
                            .filter(e -> ((StatusEffect)e.getEffectType().value()).getCategory().equals(category))
                            .count();
                        long effects2 = StreamSupport.<StatusEffectInstance>stream(component.getEffects().spliterator(), false)
                            .filter(e -> ((StatusEffect)e.getEffectType().value()).getCategory().equals(category2))
                            .count();
                        return effects >= effects2;
                    } else {
                        return false;
                    }
                }
            )
            .findFirst()
            .orElse(null);
    }

    public static int getInventoryCount(Item item) {
        return IntStream.range(0, 45)
            .filter(i -> Objects.requireNonNull(mc.player).getInventory().getStack(i).getItem().equals(item))
            .map(i -> mc.player.getInventory().getStack(i).getCount())
            .sum();
    }

    public static int getHotbarItems(List<Item> items) {
        return IntStream.range(0, 9).filter(i -> items.contains(mc.player.getInventory().getStack(i).getItem())).findFirst().orElse(-1);
    }

    public static int getHotbarSlotId(IntPredicate filter) {
        return IntStream.range(0, 9).filter(filter).findFirst().orElse(-1);
    }

    public static int getCount(Predicate<Slot> filter) {
        return slots().filter(filter).mapToInt(s -> s.getStack().getCount()).sum();
    }

    public static Slot mainHandSlot() {
        long count = slots().count();
        int i = count == 46L ? 10 : 9;
        return slots().toList().get(Math.toIntExact(count - (long)i + (long)mc.player.getInventory().selectedSlot));
    }

    public static boolean isServerScreen() {
        return slots().toList().size() != 46;
    }

    public static Stream<Slot> slots() {
        return mc.player.currentScreenHandler.slots.stream();
    }

    public static void swapAndUse(Item item) {
        swapAndUse(item, Zenith.getInstance().getRotationManager().getCurrentRotation());
    }

    public static void swapAndUse(Item item, Rotation angle) {
        float cooldownProgress = mc.player.getItemCooldownManager().getCooldownProgress(item.getDefaultStack(), 0.0F);
        if (cooldownProgress > 0.0F) {
            String time = MathUtil.round((double)cooldownProgress, 0.1) + "s";
            Zenith.getInstance()
                .getNotifyManager()
                .addNotification(
                    "N",
                    Text.of(
                        item.getName()
                            .copy()
                            .setStyle(Style.EMPTY.withColor(zenith.getThemeManager().getCurrentTheme().getColor().getRGB()))
                            .append(
                                Text.of("is in cooldown")
                                    .copy()
                                    .setStyle(Style.EMPTY.withColor(zenith.getThemeManager().getCurrentTheme().getWhite().getRGB()))
                            )
                    )
                );
        } else {
            Slot slot = getSlot(item);
            if (slot == null) {
                Zenith.getInstance()
                    .getNotifyManager()
                    .addNotification(
                        "M",
                        Text.of(
                            item.getName()
                                .copy()
                                .setStyle(Style.EMPTY.withColor(zenith.getThemeManager().getCurrentTheme().getColor().getRGB()))
                                .append(
                                    Text.of("not found")
                                        .copy()
                                        .setStyle(Style.EMPTY.withColor(zenith.getThemeManager().getCurrentTheme().getWhite().getRGB()))
                                )
                        )
                    );
            } else {
                PlayerInventoryComponent.addTask(() -> swapAndUse(slot, angle));
            }
        }
    }

    public static void swapAndUse(Slot slot, Rotation angle) {
        swapHand(slot, Hand.MAIN_HAND, false);
        closeScreen(true);
        PlayerIntersectionUtil.useItem(Hand.MAIN_HAND, angle);
        swapHand(slot, Hand.MAIN_HAND, false);
        closeScreen(true);
    }

    public static void moveItem(Slot from, int to) {
        if (from != null) {
            moveItem(from.id, to, false, false);
        }
    }

    public static void moveItem(Slot from, int to, boolean task) {
        moveItem(from, to, task, false);
    }

    public static void moveItem(Slot from, int to, boolean task, boolean updateInventory) {
        if (from != null) {
            moveItem(from.id, to, task, updateInventory);
        }
    }

    public static void moveItem(int from, int to, boolean task, boolean updateInventory) {
        if (from != to && from != -1) {
            int count = Math.toIntExact(slots().count()) - 9;
            if (from >= count && count == 36) {
                if (task) {
                    PlayerInventoryComponent.addTask(() -> clickSlot(to, from - count, SlotActionType.SWAP, false));
                } else {
                    clickSlot(to, from - count, SlotActionType.SWAP, false);
                    closeScreen(true);
                }
            } else {
                if (task) {
                    PlayerInventoryComponent.addTask(() -> moveItem(from, to, updateInventory));
                } else {
                    moveItem(from, to, updateInventory);
                    closeScreen(true);
                }
            }
        }
    }

    public static void moveItem(int from, int to, boolean updateInventory) {
        clickSlot(from, 0, SlotActionType.SWAP, false);
        clickSlot(to, 0, SlotActionType.SWAP, false);
        clickSlot(from, 0, SlotActionType.SWAP, false);
        if (updateInventory) {
            updateSlots();
        }
    }

    @Generated
    private PlayerInventoryUtil() {
        throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
    }
}
