package net.halbear.aotg.custom.blocks.AlloyFurnace;

import net.halbear.aotg.registries.ModBlocks;
import net.halbear.aotg.registries.ModMenus;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.inventory.ContainerLevelAccess;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.transfer.IndexModifier;
import net.neoforged.neoforge.transfer.ResourceHandler;
import net.neoforged.neoforge.transfer.StacksResourceHandler;
import net.neoforged.neoforge.transfer.access.ItemAccess;
import net.neoforged.neoforge.transfer.item.*;
import net.neoforged.neoforge.transfer.transaction.Transaction;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

public class AlloyFurnaceGUI extends AbstractContainerMenu implements ModMenus.MenuAccessor {
    public final Map<String, Object> menuState = new HashMap<>() {
        @Override
        public Object put(String key, Object value) {
            if (!this.containsKey(key) && this.size() >= 5)
                return null;
            return super.put(key, value);
        }
    };
    public final Level world;
    public final Player entity;
    public int x, y, z;
    private ContainerLevelAccess access = ContainerLevelAccess.NULL;
    private ResourceHandler<ItemResource> internal;
    private final Map<Integer, Slot> customSlots = new HashMap<>();
    private boolean bound = false;
    private Supplier<Boolean> boundItemMatcher = null;
    private BlockEntity boundBlockEntity = null;
    private final ContainerData data;



    public AlloyFurnaceGUI(int containerId, Inventory playerInv, FriendlyByteBuf buffer) {
        super(ModMenus.ALLOY_FURNACE_GUI.get(), containerId);
        this.entity = playerInv.player;
        this.world = playerInv.player.level();
        this.internal = new ItemStacksResourceHandler(4);

        BlockPos pos = null;
        if (buffer != null) {
            pos = buffer.readBlockPos();
            this.x = pos.getX();
            this.y = pos.getY();
            this.z = pos.getZ();
            access = ContainerLevelAccess.create(world, pos);
        }

        if (pos != null) {
            boundBlockEntity = this.world.getBlockEntity(pos);
            if (boundBlockEntity instanceof BaseContainerBlockEntity baseContainerBlockEntity) {
                this.internal = VanillaContainerWrapper.of(baseContainerBlockEntity);
                this.bound = true;
            }
        }
        this.customSlots.put(0, this.addSlot(new ResourceHandlerSlot(internal, this::setItemInSlot, 0, 37, 16) {
            private final int slot = 0;
            private int x = this.x;
            private int y = this.y;
        }));
        this.customSlots.put(1, this.addSlot(new ResourceHandlerSlot(internal, this::setItemInSlot, 1, 57, 16) {
            private final int slot = 1;
            private int x = this.x;
            private int y = this.y;
        }));
        this.customSlots.put(2, this.addSlot(new ResourceHandlerSlot(internal, this::setItemInSlot, 2, 47, 35) {
            private final int slot = 2;
            private int x = this.x;
            private int y = this.y;
            private final int maxLimit = 1;

            @Override
            public boolean mayPlace(ItemStack stack) {
                return Blocks.DECORATED_POT.asItem() == stack.getItem();
            }

            @Override
            public int getMaxStackSize() {
                return this.maxLimit;
            }

            @Override
            public int getMaxStackSize(ItemStack stack) {
                return Math.min(this.maxLimit, super.getMaxStackSize(stack));
            }

        }));
        this.customSlots.put(3, this.addSlot(new ResourceHandlerSlot(internal, this::setItemInSlot, 3, 64, 58) {
            private final int slot = 3;
            private int x = this.x;
            private int y = this.y;
        }));
        this.customSlots.put(4, this.addSlot(new ResourceHandlerSlot(internal, this::setItemInSlot, 4, 130, 37) {
            private final int slot = 4;
            private int x = this.x;
            private int y = this.y;

            @Override
            public boolean mayPlace(ItemStack stack) {
                return false;
            }
        }));


        for (int row = 0; row < 3; ++row) {
            for (int col = 0; col < 9; ++col) {
                this.addSlot(new Slot(playerInv, col + row * 9 + 9, 8 + col * 18, 84 + row * 18));
            }
        }

        for (int col = 0; col < 9; ++col) {
            this.addSlot(new Slot(playerInv, col, 8 + col * 18, 142));
        }
        if (boundBlockEntity instanceof AlloyFurnaceBlockEntity alloyFurnaceBlockEntity) {
            this.data = new ContainerData() {
                @Override
                public int get(int index) {
                    int returnVal = 0;
                    switch (index) {
                        case 0:
                            returnVal = alloyFurnaceBlockEntity.GetCookProgress();
                            break;
                        case 1:
                            returnVal = alloyFurnaceBlockEntity.GetFuel();
                            break;
                        case 2:
                            returnVal = alloyFurnaceBlockEntity.GetTemperatureC();
                            break;
                        case 3:
                            returnVal = alloyFurnaceBlockEntity.GetBellowPush();
                            break;
                        case 4:
                            returnVal = alloyFurnaceBlockEntity.GetCurrentFuelTime();
                            break;
                    }
                    return returnVal;
                }

                @Override
                public void set(int index, int value) {
                    switch(index) {
                        case 0:
                            alloyFurnaceBlockEntity.SetCookProgress(value);
                            break;
                        case 1:
                            alloyFurnaceBlockEntity.SetFuel(value);
                            break;
                        case 2:
                            alloyFurnaceBlockEntity.SetTemperatureC(value);
                            break;
                        case 3:
                            alloyFurnaceBlockEntity.SetBellowPush(value);
                            break;
                        case 4:
                             alloyFurnaceBlockEntity.SetCurrentFuelTime(value);
                            break;
                    }
                }

                @Override
                public int getCount() {
                    return 5;
                }
            };

            this.addDataSlots(this.data);
        } else this.data = null;
    }

    public int getSyncedVariable(int index) {
        return this.data.get(index);
    }

    private void setItemInSlot(int index, ItemResource resource, int amount) {
        if (internal instanceof ItemStacksResourceHandler handler) {
            handler.set(index, resource, amount);
        } else if (boundBlockEntity instanceof Container container) {
            container.setItem(index, resource.toStack(Math.max(0, amount)));
        } else {
            try (var tx = Transaction.openRoot()) {
                if (!internal.getResource(index).isEmpty())
                    internal.extract(index, internal.getResource(index), internal.getAmountAsInt(index), tx);
                if (!resource.isEmpty() && amount > 0)
                    internal.insert(index, resource, amount, tx);
                tx.commit();
            }
        }
    }




    @Override
    public boolean stillValid(Player player) {
        if (this.bound) {
            if (this.boundItemMatcher != null)
                return this.boundItemMatcher.get();
            else if (this.boundBlockEntity != null)
                return AbstractContainerMenu.stillValid(this.access, player, this.boundBlockEntity.getBlockState().getBlock());
        }
        return true;
    }

    @Override
    public ItemStack quickMoveStack(Player playerIn, int index) {
        ItemStack itemstack = ItemStack.EMPTY;
        Slot slot = this.slots.get(index);
        if (slot != null && slot.hasItem()) {
            ItemStack itemstack1 = slot.getItem();
            itemstack = itemstack1.copy();
            if (index < 4) {
                if (!this.moveItemStackTo(itemstack1, 4, this.slots.size(), true))
                    return ItemStack.EMPTY;
                slot.onQuickCraft(itemstack1, itemstack);
            } else if (!this.moveItemStackTo(itemstack1, 0, 4, false)) {
                if (index < 4 + 27) {
                    if (!this.moveItemStackTo(itemstack1, 4 + 27, this.slots.size(), true))
                        return ItemStack.EMPTY;
                } else {
                    if (!this.moveItemStackTo(itemstack1, 4, 4 + 27, false))
                        return ItemStack.EMPTY;
                }
                return ItemStack.EMPTY;
            }
            if (itemstack1.isEmpty()) {
                slot.setByPlayer(ItemStack.EMPTY);
            } else {
                slot.setChanged();
            }
            if (itemstack1.getCount() == itemstack.getCount()) {
                return ItemStack.EMPTY;
            }
            slot.onTake(playerIn, itemstack1);
        }
        return itemstack;
    }

    @Override
    protected boolean moveItemStackTo(ItemStack itemStack, int startSlot, int endSlot, boolean backwards) {
        boolean anythingChanged = false;
        int destSlot = startSlot;
        if (backwards) {
            destSlot = endSlot - 1;
        }
        if (itemStack.isStackable()) {
            while (!itemStack.isEmpty() && (backwards ? destSlot >= startSlot : destSlot < endSlot)) {
                Slot slot = this.slots.get(destSlot);
                ItemStack target = slot.getItem();
                if (slot.mayPlace(target) && !target.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, target)) {
                    int totalStack = target.getCount() + itemStack.getCount();
                    int maxStackSize = slot.getMaxStackSize(target);
                    if (totalStack <= maxStackSize) {
                        itemStack.setCount(0);
                        target.setCount(totalStack);
                        slot.set(target);
                        anythingChanged = true;
                    } else if (target.getCount() < maxStackSize) {
                        itemStack.shrink(maxStackSize - target.getCount());
                        target.setCount(maxStackSize);
                        slot.set(target);
                        anythingChanged = true;
                    }
                }
                if (backwards) {
                    destSlot--;
                } else {
                    destSlot++;
                }
            }
        }
        if (!itemStack.isEmpty()) {
            if (backwards) {
                destSlot = endSlot - 1;
            } else {
                destSlot = startSlot;
            }
            while (backwards ? destSlot >= startSlot : destSlot < endSlot) {
                Slot slotx = this.slots.get(destSlot);
                ItemStack targetx = slotx.getItem();
                if (targetx.isEmpty() && slotx.mayPlace(itemStack)) {
                    int maxStackSize = slotx.getMaxStackSize(itemStack);
                    slotx.setByPlayer(itemStack.split(Math.min(itemStack.getCount(), maxStackSize)));
                    slotx.setChanged();
                    anythingChanged = true;
                    break;
                }
                if (backwards) {
                    destSlot--;
                } else {
                    destSlot++;
                }
            }
        }
        return anythingChanged;
    }

    @Override
    public void removed(Player playerIn) {
        super.removed(playerIn);
        if (!bound && playerIn instanceof ServerPlayer serverPlayer) {
            if (!serverPlayer.isAlive() || serverPlayer.hasDisconnected()) {
                for (int j = 0; j < internal.size(); ++j) {
                    playerIn.drop(ItemUtil.getStack(internal, j), false);
                    setItemInSlot(j, ItemResource.EMPTY, 0);
                }
            } else {
                for (int i = 0; i < internal.size(); ++i) {
                    playerIn.getInventory().placeItemBackInInventory(ItemUtil.getStack(internal, i));
                    setItemInSlot(i, ItemResource.EMPTY, 0);
                }
            }
        }
    }

    @Override
    public Map<Integer, Slot> getSlots() {
        return Collections.unmodifiableMap(customSlots);
    }

    @Override
    public Map<String, Object> getMenuState() {
        return menuState;
    }

}
