package com.tiedup.remake.entities;

import com.tiedup.remake.core.ModMenuTypes;
import javax.annotation.Nullable;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
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.Slot;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ItemStack;

/**
 * Vanilla-style container menu for NPC inventory.
 *
 * Supports variable inventory sizes (9, 18, or 27 slots).
 * Uses standard shift-click behavior for item transfer.
 */
public class NpcInventoryMenu extends AbstractContainerMenu {

    /** The NPC entity (null on client if entity not found) */
    @Nullable
    private final EntityDamsel npc;

    /** Container wrapping the NPC's inventory */
    private final Container npcContainer;

    /** Number of NPC inventory slots */
    private final int npcSlotCount;

    /** NPC's display name (for screen title) */
    private final String npcName;

    // Slot layout constants
    private static final int SLOT_SIZE = 18;
    private static final int SLOTS_PER_ROW = 9;

    /**
     * Server-side constructor - called when player opens NPC inventory.
     *
     * @param containerId Container window ID
     * @param playerInventory Player's inventory
     * @param npc The NPC entity
     */
    public NpcInventoryMenu(
        int containerId,
        Inventory playerInventory,
        EntityDamsel npc
    ) {
        super(ModMenuTypes.NPC_INVENTORY.get(), containerId);
        this.npc = npc;
        this.npcSlotCount = npc.getNpcInventorySize();
        this.npcName = npc.getNpcName();

        // Create container wrapping NPC's inventory list
        this.npcContainer = new NpcInventoryContainer(npc);

        // Add NPC inventory slots
        addNpcSlots();

        // Add equipment slots (armor + main hand) on the right side
        addEquipmentSlots();

        // Add player inventory slots (standard layout below NPC slots)
        addPlayerInventorySlots(playerInventory);
    }

    /**
     * Client-side constructor - called when receiving menu open packet.
     * Creates a dummy container since real NPC data comes from server.
     *
     * @param containerId Container window ID
     * @param playerInventory Player's inventory
     * @param buf Network buffer with extra data
     * @return New menu instance
     */
    public static NpcInventoryMenu createClientMenu(
        int containerId,
        Inventory playerInventory,
        FriendlyByteBuf buf
    ) {
        int entityId = buf.readInt();
        int inventorySize = buf.readInt();
        String name = buf.readUtf(64);

        // Try to find entity on client
        Entity entity = playerInventory.player.level().getEntity(entityId);
        EntityDamsel npc = entity instanceof EntityDamsel d ? d : null;

        return new NpcInventoryMenu(
            containerId,
            playerInventory,
            npc,
            inventorySize,
            name
        );
    }

    /**
     * Client-side constructor with explicit parameters.
     */
    private NpcInventoryMenu(
        int containerId,
        Inventory playerInventory,
        @Nullable EntityDamsel npc,
        int inventorySize,
        String npcName
    ) {
        super(ModMenuTypes.NPC_INVENTORY.get(), containerId);
        this.npc = npc;
        this.npcSlotCount = inventorySize;
        this.npcName = npcName;

        // Use real NPC container if available, otherwise dummy
        if (npc != null) {
            this.npcContainer = new NpcInventoryContainer(npc);
        } else {
            this.npcContainer = new SimpleContainer(inventorySize);
        }

        addNpcSlots();
        addEquipmentSlots();
        addPlayerInventorySlots(playerInventory);
    }

    /**
     * Add NPC inventory slots in chest-style grid.
     */
    private void addNpcSlots() {
        int rows = (npcSlotCount + SLOTS_PER_ROW - 1) / SLOTS_PER_ROW;

        // Calculate starting position (centered, at top of menu)
        int startX = 8;
        int startY = 18;

        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < SLOTS_PER_ROW; col++) {
                int slotIndex = row * SLOTS_PER_ROW + col;
                if (slotIndex >= npcSlotCount) break;

                int x = startX + col * SLOT_SIZE;
                int y = startY + row * SLOT_SIZE;
                this.addSlot(new Slot(npcContainer, slotIndex, x, y));
            }
        }
    }

    /**
     * Add player inventory slots (standard vanilla layout).
     * Uses vanilla chest formula: playerY = rows*18 + 31
     */
    private void addPlayerInventorySlots(Inventory playerInventory) {
        int npcRows = (npcSlotCount + SLOTS_PER_ROW - 1) / SLOTS_PER_ROW;
        // Vanilla formula: (rows - 4) * 18 + 103 = rows*18 + 31
        int playerInvY = npcRows * SLOT_SIZE + 31;

        // Main inventory (3 rows)
        for (int row = 0; row < 3; row++) {
            for (int col = 0; col < 9; col++) {
                int x = 8 + col * SLOT_SIZE;
                int y = playerInvY + row * SLOT_SIZE;
                this.addSlot(
                    new Slot(playerInventory, col + row * 9 + 9, x, y)
                );
            }
        }

        // Hotbar (58 pixels below main inventory)
        int hotbarY = playerInvY + 58;
        for (int col = 0; col < 9; col++) {
            int x = 8 + col * SLOT_SIZE;
            this.addSlot(new Slot(playerInventory, col, x, hotbarY));
        }
    }

    /**
     * Add equipment slots (armor + main hand) on the right side of the GUI.
     * Positioned to align with equipment panel in NpcInventoryScreen.
     * Creates dummy slots if NPC is null (client-side fallback).
     */
    private void addEquipmentSlots() {
        // Equipment panel is at x = 176 (chest width) + 4 (gap) = 180
        // Slots within panel are at panelX + 5 = 185
        int equipX = 185;
        // Panel starts at y = 17 (header), slots start at panelY + 12 = 29
        int equipY = 29;

        // Armor slots (HEAD, CHEST, LEGS, FEET) - top to bottom
        EquipmentSlot[] armorSlots = {
            EquipmentSlot.HEAD,
            EquipmentSlot.CHEST,
            EquipmentSlot.LEGS,
            EquipmentSlot.FEET,
        };

        if (npc != null) {
            // Real equipment slots connected to entity
            for (int i = 0; i < 4; i++) {
                this.addSlot(
                    new NpcArmorSlot(
                        npc,
                        armorSlots[i],
                        equipX,
                        equipY + i * SLOT_SIZE
                    )
                );
            }
            this.addSlot(
                new NpcMainHandSlot(npc, equipX, equipY + 4 * SLOT_SIZE + 4)
            );
        } else {
            // Dummy slots for client-side sync (when entity not found)
            SimpleContainer dummyContainer = new SimpleContainer(5);
            for (int i = 0; i < 4; i++) {
                final EquipmentSlot slotType = armorSlots[i];
                this.addSlot(
                    new Slot(
                        dummyContainer,
                        i,
                        equipX,
                        equipY + i * SLOT_SIZE
                    ) {
                        @Override
                        public boolean mayPlace(ItemStack stack) {
                            if (stack.isEmpty()) return true;
                            if (stack.getItem() instanceof ArmorItem armor) {
                                return armor.getEquipmentSlot() == slotType;
                            }
                            return false;
                        }

                        @Override
                        public int getMaxStackSize() {
                            return 1;
                        }
                    }
                );
            }
            this.addSlot(
                new Slot(dummyContainer, 4, equipX, equipY + 4 * SLOT_SIZE + 4)
            );
        }
    }

    @Override
    public ItemStack quickMoveStack(Player player, int slotIndex) {
        ItemStack result = ItemStack.EMPTY;
        Slot slot = this.slots.get(slotIndex);

        if (slot == null || !slot.hasItem()) {
            return result;
        }

        ItemStack slotStack = slot.getItem();
        result = slotStack.copy();

        // Slot layout:
        // 0 to npcSlotCount-1: NPC inventory
        // npcSlotCount to npcSlotCount+4: Equipment (4 armor + 1 mainhand)
        // npcSlotCount+5 to end: Player inventory
        int equipmentStart = npcSlotCount;
        int equipmentEnd = npcSlotCount + 5; // 4 armor + 1 mainhand
        int playerInvStart = equipmentEnd;

        if (slotIndex < npcSlotCount) {
            // From NPC inventory -> player inventory
            if (
                !this.moveItemStackTo(
                    slotStack,
                    playerInvStart,
                    this.slots.size(),
                    true
                )
            ) {
                return ItemStack.EMPTY;
            }
        } else if (slotIndex < equipmentEnd) {
            // From equipment slots -> player inventory
            if (
                !this.moveItemStackTo(
                    slotStack,
                    playerInvStart,
                    this.slots.size(),
                    true
                )
            ) {
                return ItemStack.EMPTY;
            }
        } else {
            // From player inventory -> try equipment first, then NPC inventory
            boolean moved = false;

            // Try equipment slots first (armor items go to armor slots)
            if (slotStack.getItem() instanceof ArmorItem) {
                moved = this.moveItemStackTo(
                    slotStack,
                    equipmentStart,
                    equipmentStart + 4,
                    false
                );
            }

            // Then try NPC inventory
            if (!moved && !slotStack.isEmpty()) {
                moved = this.moveItemStackTo(slotStack, 0, npcSlotCount, false);
            }

            if (!moved) {
                return ItemStack.EMPTY;
            }
        }

        if (slotStack.isEmpty()) {
            slot.setByPlayer(ItemStack.EMPTY);
        } else {
            slot.setChanged();
        }

        return result;
    }

    @Override
    public boolean stillValid(Player player) {
        // Menu stays valid as long as NPC exists and is nearby
        if (npc == null || !npc.isAlive()) {
            return false;
        }
        return player.distanceToSqr(npc) <= 64.0; // 8 block range
    }

    /**
     * Get NPC's display name for screen title.
     */
    public String getNpcName() {
        return npcName;
    }

    /**
     * Get the NPC entity (may be null on client).
     */
    @Nullable
    public EntityDamsel getNpc() {
        return npc;
    }

    /**
     * Get number of NPC inventory slots.
     */
    public int getNpcSlotCount() {
        return npcSlotCount;
    }

    /**
     * Container wrapper that delegates to EntityDamsel's inventory.
     * Allows vanilla Container mechanics to work with NPC inventory.
     */
    private static class NpcInventoryContainer implements Container {

        private final EntityDamsel npc;

        NpcInventoryContainer(EntityDamsel npc) {
            this.npc = npc;
        }

        @Override
        public int getContainerSize() {
            return npc.getNpcInventorySize();
        }

        @Override
        public boolean isEmpty() {
            for (int i = 0; i < getContainerSize(); i++) {
                if (!getItem(i).isEmpty()) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public ItemStack getItem(int slot) {
            return npc.getNpcInventory().get(slot);
        }

        @Override
        public ItemStack removeItem(int slot, int amount) {
            ItemStack stack = getItem(slot);
            if (stack.isEmpty()) {
                return ItemStack.EMPTY;
            }
            if (amount >= stack.getCount()) {
                ItemStack removed = stack.copy();
                npc.getNpcInventory().set(slot, ItemStack.EMPTY);
                setChanged();
                return removed;
            } else {
                ItemStack split = stack.split(amount);
                setChanged();
                return split;
            }
        }

        @Override
        public ItemStack removeItemNoUpdate(int slot) {
            ItemStack stack = getItem(slot).copy();
            npc.getNpcInventory().set(slot, ItemStack.EMPTY);
            return stack;
        }

        @Override
        public void setItem(int slot, ItemStack stack) {
            npc.getNpcInventory().set(slot, stack);
            setChanged();
        }

        @Override
        public void setChanged() {
            // Mark entity as dirty for saving
            // The NPC will save inventory in its NBT
        }

        @Override
        public boolean stillValid(Player player) {
            return npc.isAlive() && player.distanceToSqr(npc) <= 64.0;
        }

        @Override
        public void clearContent() {
            for (int i = 0; i < getContainerSize(); i++) {
                npc.getNpcInventory().set(i, ItemStack.EMPTY);
            }
            setChanged();
        }
    }

    /**
     * Custom slot for NPC armor equipment.
     * Reads/writes directly to entity's equipment slots.
     */
    private static class NpcArmorSlot extends Slot {

        private final EntityDamsel npc;
        private final EquipmentSlot equipmentSlot;

        NpcArmorSlot(EntityDamsel npc, EquipmentSlot slot, int x, int y) {
            // Use a dummy container - we override all access methods
            super(new SimpleContainer(1), 0, x, y);
            this.npc = npc;
            this.equipmentSlot = slot;
        }

        @Override
        public boolean mayPlace(ItemStack stack) {
            if (stack.isEmpty()) return true;
            // Only allow armor items that match this slot
            if (stack.getItem() instanceof ArmorItem armor) {
                return armor.getEquipmentSlot() == this.equipmentSlot;
            }
            return false;
        }

        @Override
        public ItemStack getItem() {
            return npc.getItemBySlot(equipmentSlot);
        }

        @Override
        public void setByPlayer(ItemStack stack) {
            npc.setItemSlot(equipmentSlot, stack);
        }

        @Override
        public void set(ItemStack stack) {
            npc.setItemSlot(equipmentSlot, stack);
        }

        @Override
        public ItemStack remove(int amount) {
            ItemStack current = getItem();
            if (current.isEmpty()) return ItemStack.EMPTY;

            if (amount >= current.getCount()) {
                ItemStack removed = current.copy();
                npc.setItemSlot(equipmentSlot, ItemStack.EMPTY);
                return removed;
            } else {
                ItemStack split = current.split(amount);
                npc.setItemSlot(equipmentSlot, current);
                return split;
            }
        }

        @Override
        public boolean hasItem() {
            return !getItem().isEmpty();
        }

        @Override
        public int getMaxStackSize() {
            return 1;
        }
    }

    /**
     * Custom slot for NPC main hand equipment.
     * Reads/writes directly to entity's main hand.
     */
    private static class NpcMainHandSlot extends Slot {

        private final EntityDamsel npc;

        NpcMainHandSlot(EntityDamsel npc, int x, int y) {
            super(new SimpleContainer(1), 0, x, y);
            this.npc = npc;
        }

        @Override
        public ItemStack getItem() {
            return npc.getMainHandItem();
        }

        @Override
        public void setByPlayer(ItemStack stack) {
            npc.setMainHandItem(stack);
        }

        @Override
        public void set(ItemStack stack) {
            npc.setMainHandItem(stack);
        }

        @Override
        public ItemStack remove(int amount) {
            ItemStack current = getItem();
            if (current.isEmpty()) return ItemStack.EMPTY;

            if (amount >= current.getCount()) {
                ItemStack removed = current.copy();
                npc.setMainHandItem(ItemStack.EMPTY);
                return removed;
            } else {
                ItemStack split = current.split(amount);
                npc.setMainHandItem(current);
                return split;
            }
        }

        @Override
        public boolean hasItem() {
            return !getItem().isEmpty();
        }

        @Override
        public int getMaxStackSize() {
            return 64;
        }
    }
}
