package com.tiedup.remake.v2.bondage;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;

/**
 * Static utility for V2 equipment conflict resolution and pose management.
 *
 * Conflict rules:
 * 1. One item per region — cannot equip two items on the same region
 * 2. Items declare which regions they block via getBlockedRegions()
 * 3. Swap: single conflict -> auto-swap if canUnequip
 * 4. Supersede: new item with a global region replaces all conflicting items
 *
 * NOTE: Global regions do NOT automatically block sub-regions.
 * Blocking is always explicit via getBlockedRegions().
 * Example: Hood blocks {HEAD, EYES, EARS, MOUTH}. Handcuffs block {ARMS} only.
 */
public final class V2EquipmentManager {

    private V2EquipmentManager() {}

    /**
     * A conflict between a new item and an existing equipped item.
     */
    public record ConflictEntry(BodyRegionV2 region, ItemStack stack) {}

    /**
     * Check if an item can be equipped without any conflicts (stack-aware).
     */
    public static boolean canEquip(
        IV2BondageEquipment equip,
        IV2BondageItem item,
        ItemStack newStack
    ) {
        return findAllConflicts(equip, item, newStack).isEmpty();
    }

    /**
     * Find all conflicts that would occur if the given item were equipped.
     * Checks direct occupation, existing items' blocked regions, and new item's blocked regions.
     *
     * @param equip    The equipment capability
     * @param item     The V2 bondage item interface
     * @param newStack The ItemStack being equipped (used for stack-aware property lookups)
     */
    public static List<ConflictEntry> findAllConflicts(
        IV2BondageEquipment equip,
        IV2BondageItem item,
        ItemStack newStack
    ) {
        List<ConflictEntry> conflicts = new ArrayList<>();
        IdentityHashMap<ItemStack, Boolean> seen = new IdentityHashMap<>();

        // 1. Direct occupation conflict: new item's regions vs existing items
        for (BodyRegionV2 region : item.getOccupiedRegions(newStack)) {
            ItemStack existing = equip.getInRegion(region);
            if (!existing.isEmpty() && !seen.containsKey(existing)) {
                seen.put(existing, Boolean.TRUE);
                conflicts.add(new ConflictEntry(region, existing));
            }
        }

        // 2. Existing items' getBlockedRegions() block new item's regions
        for (Map.Entry<BodyRegionV2, ItemStack> entry : equip
            .getAllEquipped()
            .entrySet()) {
            ItemStack equipped = entry.getValue();
            if (seen.containsKey(equipped)) continue;
            if (equipped.getItem() instanceof IV2BondageItem equippedItem) {
                for (BodyRegionV2 newRegion : item.getOccupiedRegions(
                    newStack
                )) {
                    if (
                        equippedItem
                            .getBlockedRegions(equipped)
                            .contains(newRegion)
                    ) {
                        seen.put(equipped, Boolean.TRUE);
                        conflicts.add(
                            new ConflictEntry(entry.getKey(), equipped)
                        );
                        break; // One conflict per item is enough
                    }
                }
            }
        }

        // 3. New item's getBlockedRegions() conflict with existing items
        for (BodyRegionV2 blocked : item.getBlockedRegions(newStack)) {
            ItemStack existing = equip.getInRegion(blocked);
            if (!existing.isEmpty() && !seen.containsKey(existing)) {
                seen.put(existing, Boolean.TRUE);
                conflicts.add(new ConflictEntry(blocked, existing));
            }
        }

        return conflicts;
    }

    /**
     * Attempt to equip an item, handling conflicts via swap or supersede.
     *
     * @param equip  The equipment capability
     * @param item   The V2 bondage item interface
     * @param stack  The ItemStack being equipped
     * @param entity The entity being equipped (never null)
     * @return The result of the equip attempt
     */
    @ApiStatus.Internal
    public static V2EquipResult tryEquip(
        IV2BondageEquipment equip,
        IV2BondageItem item,
        ItemStack stack,
        LivingEntity entity
    ) {
        // Fast path: no conflicts
        List<ConflictEntry> conflicts = findAllConflicts(equip, item, stack);
        if (conflicts.isEmpty()) {
            doEquip(equip, item, stack);
            return V2EquipResult.SUCCESS;
        }

        // De-duplicate conflicts by stack identity
        IdentityHashMap<ItemStack, ConflictEntry> uniqueConflicts =
            new IdentityHashMap<>();
        for (ConflictEntry c : conflicts) {
            uniqueConflicts.putIfAbsent(c.stack(), c);
        }

        // Single conflict -> attempt swap
        if (uniqueConflicts.size() == 1) {
            ConflictEntry conflict = uniqueConflicts.values().iterator().next();
            ItemStack conflictStack = conflict.stack();
            if (
                conflictStack.getItem() instanceof IV2BondageItem conflictItem
            ) {
                if (conflictItem.canUnequip(conflictStack, entity)) {
                    removeAllRegionsOf(equip, conflictStack);
                    conflictItem.onUnequipped(conflictStack, entity);
                    doEquip(equip, item, stack);
                    return V2EquipResult.swapped(conflictStack);
                }
            } else {
                // Non-V2 item in region — RISK-003 fix: check if locked before removing
                if (conflictStack.getItem() instanceof ILockable lockable && lockable.isLocked(conflictStack)) {
                    TiedUpMod.LOGGER.warn(
                        "V2EquipmentManager: blocked swap of locked non-V2 item {} from equipment",
                        conflictStack
                    );
                    return V2EquipResult.BLOCKED;
                }
                TiedUpMod.LOGGER.warn(
                    "V2EquipmentManager: swapping out non-V2 item {} from equipment",
                    conflictStack
                );
                removeAllRegionsOf(equip, conflictStack);
                doEquip(equip, item, stack);
                return V2EquipResult.swapped(conflictStack);
            }
            return V2EquipResult.BLOCKED;
        }

        // Multiple conflicts + new item occupies a global region -> supersede
        boolean newItemHasGlobal = false;
        for (BodyRegionV2 region : item.getOccupiedRegions(stack)) {
            if (region.isGlobal()) {
                newItemHasGlobal = true;
                break;
            }
        }

        if (newItemHasGlobal) {
            // Check all conflicting items can be unequipped
            for (ConflictEntry c : uniqueConflicts.values()) {
                if (c.stack().getItem() instanceof IV2BondageItem ci) {
                    if (!ci.canUnequip(c.stack(), entity)) {
                        return V2EquipResult.BLOCKED;
                    }
                }
            }
            // Collect displaced stacks, then unequip all conflicts
            List<ItemStack> displaced = new ArrayList<>();
            for (ConflictEntry c : uniqueConflicts.values()) {
                ItemStack cs = c.stack();
                removeAllRegionsOf(equip, cs);
                if (cs.getItem() instanceof IV2BondageItem ci) {
                    ci.onUnequipped(cs, entity);
                } else {
                    TiedUpMod.LOGGER.warn(
                        "[V2] Supersede removed non-V2 item {} from equipment",
                        cs
                    );
                }
                displaced.add(cs);
            }
            doEquip(equip, item, stack);
            return V2EquipResult.superseded(displaced);
        }

        return V2EquipResult.BLOCKED;
    }

    /**
     * Place an item into all its occupied regions.
     */
    private static void doEquip(
        IV2BondageEquipment equip,
        IV2BondageItem item,
        ItemStack stack
    ) {
        for (BodyRegionV2 region : item.getOccupiedRegions(stack)) {
            equip.setInRegion(region, stack);
        }
    }

    /**
     * Remove an item from all regions by identity scan.
     * Uses full BodyRegionV2.values() scan to prevent orphan stacks.
     */
    public static void removeAllRegionsOf(
        IV2BondageEquipment equip,
        ItemStack stack
    ) {
        for (BodyRegionV2 region : BodyRegionV2.values()) {
            //noinspection ObjectEquality — intentional identity comparison
            if (equip.getInRegion(region) == stack) {
                equip.setInRegion(region, ItemStack.EMPTY);
            }
        }
    }
}
