package com.tiedup.remake.v2.bondage.datadriven;

import com.tiedup.remake.core.ModSounds;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.V2EquipResult;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
import com.tiedup.remake.v2.bondage.TyingInteractionHelper;
import com.tiedup.remake.v2.bondage.V2BondageItems;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.bondage.component.BuiltInLockComponent;
import com.tiedup.remake.v2.bondage.component.ComponentHolder;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.IItemComponent;
import com.tiedup.remake.v2.bondage.component.LockableComponent;
import com.tiedup.remake.v2.bondage.component.ResistanceComponent;
import com.tiedup.remake.v2.bondage.items.AbstractV2BondageItem;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;

/**
 * Generic Item class for all data-driven bondage items.
 *
 * <p>A single Forge-registered Item. Each ItemStack carries a {@code tiedup_item_id}
 * NBT tag that points to a {@link DataDrivenItemDefinition} in the
 * {@link DataDrivenItemRegistry}. All property methods are overridden to read
 * from the definition via the stack-aware interface methods.</p>
 *
 * <p>The no-arg methods return safe defaults because the singleton item cannot
 * know which definition to use without an ItemStack. The real values come
 * exclusively from the stack-aware overrides.</p>
 */
public class DataDrivenBondageItem extends AbstractV2BondageItem {

    public DataDrivenBondageItem() {
        super(new Properties().stacksTo(1));
    }

    // ===== REGIONS (stack-aware overrides) =====

    @Override
    public Set<BodyRegionV2> getOccupiedRegions() {
        // Safe default for the singleton — real value comes from stack-aware override
        return Set.of();
    }

    @Override
    public Set<BodyRegionV2> getOccupiedRegions(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null ? def.occupiedRegions() : Set.of();
    }

    @Override
    public Set<BodyRegionV2> getBlockedRegions(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null ? def.blockedRegions() : Set.of();
    }

    // ===== 3D MODELS (stack-aware overrides) =====

    @Override
    @Nullable
    public ResourceLocation getModelLocation() {
        return null; // Safe default
    }

    @Override
    @Nullable
    public ResourceLocation getModelLocation(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null ? def.modelLocation() : null;
    }

    @Override
    @Nullable
    public ResourceLocation getSlimModelLocation(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null ? def.slimModelLocation() : null;
    }

    @Override
    public boolean supportsSlimModel(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null && def.slimModelLocation() != null;
    }

    // ===== POSES & ANIMATIONS (stack-aware overrides) =====

    @Override
    public int getPosePriority() {
        return 0; // Safe default
    }

    @Override
    public int getPosePriority(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null ? def.posePriority() : 0;
    }

    // ===== ITEM STATE (stack-aware overrides) =====

    @Override
    public int getEscapeDifficulty(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null ? def.escapeDifficulty() : 0;
    }

    @Override
    public boolean supportsColor(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null && def.supportsColor();
    }

    // ===== INTERACTION ROUTING =====

    @Override
    public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
        ItemStack stack = player.getItemInHand(hand);
        Set<BodyRegionV2> regions = getOccupiedRegions(stack);

        // ARMS items: shift+click cycles bind mode
        if (regions.contains(BodyRegionV2.ARMS) && player.isShiftKeyDown() && !level.isClientSide) {
            BindModeHelper.cycleBindModeId(stack);
            player.playSound(SoundEvents.CHAIN_STEP, 0.5f, 1.2f);
            player.displayClientMessage(
                Component.translatable(
                    "tiedup.message.bindmode_changed",
                    Component.translatable(BindModeHelper.getBindModeTranslationKey(stack))
                ),
                true
            );
            return InteractionResultHolder.success(stack);
        }
        return super.use(level, player, hand);
    }

    @Override
    public InteractionResult interactLivingEntity(
        ItemStack stack, Player player, LivingEntity target, InteractionHand hand
    ) {
        // Client: arm swing
        if (player.level().isClientSide) {
            return InteractionResult.SUCCESS;
        }

        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        if (def == null) return InteractionResult.PASS;

        Set<BodyRegionV2> regions = def.occupiedRegions();

        // ARMS: tying flow (do NOT call super — avoids double equip)
        if (regions.contains(BodyRegionV2.ARMS) && player instanceof ServerPlayer serverPlayer) {
            return TyingInteractionHelper.handleTying(serverPlayer, target, stack, hand);
        }

        // NECK: collar equip flow
        if (regions.contains(BodyRegionV2.NECK) && player instanceof ServerPlayer serverPlayer) {
            // Target must be tied up to collar (V1 rule preserved)
            IRestrainable targetState = KidnappedHelper.getKidnappedState(target);
            if (targetState == null || !targetState.isTiedUp()) {
                return InteractionResult.PASS;
            }

            // Distance + line-of-sight
            if (player.distanceTo(target) > 4.0 || !player.hasLineOfSight(target)) {
                return InteractionResult.PASS;
            }

            // Add player as owner in NBT before equip
            ItemStack collarCopy = stack.copy();
            CollarHelper.addOwner(collarCopy, player);

            // Equip via V2 system (handles conflict resolution, displaced items)
            V2EquipResult result = V2EquipmentHelper.equipItem(target, collarCopy);
            if (result.isSuccess()) {
                for (ItemStack displaced : result.displaced()) {
                    target.spawnAtLocation(displaced);
                }
                stack.shrink(1);
                // Play collar equip sound
                target.level().playSound(null, target.getX(), target.getY(), target.getZ(),
                    ModSounds.COLLAR_PUT.get(),
                    net.minecraft.sounds.SoundSource.PLAYERS, 0.5f, 1.0f);
                V2EquipmentHelper.sync(target);
                TiedUpMod.LOGGER.info("[DataDrivenBondageItem] {} collared {} (owner added)",
                    player.getName().getString(), target.getName().getString());
                return InteractionResult.SUCCESS;
            }
            return InteractionResult.PASS;
        }

        // All other regions (MOUTH, EYES, EARS, HANDS): instant equip via parent
        return super.interactLivingEntity(stack, player, target, hand);
    }

    // ===== IHasResistance IMPLEMENTATION =====

    @Override
    public String getResistanceId() {
        // Safe default for the singleton -- the real resistance comes from
        // getBaseResistance() which bypasses the GameRules switch entirely.
        return "data_driven";
    }

    /**
     * Bypass the GameRules switch lookup entirely for data-driven items.
     *
     * <p>The default IHasResistance implementation calls
     * {@code ModGameRules.getResistance(gameRules, getResistanceId())} which has
     * a hardcoded switch for "rope", "gag", "blindfold", "collar" and defaults
     * to 100 for everything else. This makes the JSON {@code escape_difficulty}
     * field useless.</p>
     *
     * <p>Instead, we scan the entity's equipped items to find ALL data-driven items
     * and return the MAX escape difficulty. This is because IHasResistance has no
     * ItemStack parameter, so we cannot distinguish which specific data-driven item
     * is being queried when multiple are equipped (they all share the same Item
     * singleton). Returning the MAX is the safe choice: it prevents the struggle
     * system from underestimating resistance.</p>
     */
    @Override
    public int getBaseResistance(LivingEntity entity) {
        if (entity != null) {
            IV2BondageEquipment equip = V2EquipmentHelper.getEquipment(entity);
            if (equip != null) {
                int maxDifficulty = -1;
                for (Map.Entry<BodyRegionV2, ItemStack> entry : equip
                    .getAllEquipped()
                    .entrySet()) {
                    ItemStack stack = entry.getValue();
                    if (stack.getItem() == this) {
                        // Try component first (stack-aware, fixes I-03)
                        ResistanceComponent comp = DataDrivenBondageItem
                            .getComponent(
                                stack,
                                ComponentType.RESISTANCE,
                                ResistanceComponent.class
                            );
                        if (comp != null) {
                            maxDifficulty = Math.max(
                                maxDifficulty,
                                comp.getBaseResistance()
                            );
                            continue;
                        }
                        // Fallback: read from definition directly
                        DataDrivenItemDefinition def =
                            DataDrivenItemRegistry.get(stack);
                        if (def != null) {
                            maxDifficulty = Math.max(
                                maxDifficulty,
                                def.escapeDifficulty()
                            );
                        }
                    }
                }
                if (maxDifficulty >= 0) {
                    return maxDifficulty;
                }
            }
        }
        return 100; // safe fallback
    }

    @Override
    public void notifyStruggle(LivingEntity entity) {
        // Play a generic chain sound for data-driven items
        entity
            .level()
            .playSound(
                null,
                entity.getX(),
                entity.getY(),
                entity.getZ(),
                net.minecraft.sounds.SoundEvents.CHAIN_STEP,
                net.minecraft.sounds.SoundSource.PLAYERS,
                0.4f,
                1.0f
            );
    }

    // ===== TOOLTIPS =====

    @Override
    public void appendHoverText(
        ItemStack stack,
        @Nullable Level level,
        List<Component> tooltip,
        TooltipFlag flag
    ) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        if (def != null) {
            // Creator
            if (def.creator() != null && !def.creator().isEmpty()) {
                tooltip.add(
                    Component.translatable(
                        "item.tiedup.tooltip.creator",
                        Component.literal(def.creator())
                            .withStyle(ChatFormatting.WHITE)
                    ).withStyle(ChatFormatting.GRAY)
                );
            }

            // Regions
            String regions = def.occupiedRegions().stream()
                .map(r -> r.name().toLowerCase())
                .collect(Collectors.joining(", "));
            tooltip.add(
                Component.translatable(
                    "item.tiedup.tooltip.regions",
                    regions
                ).withStyle(ChatFormatting.DARK_AQUA)
            );

            // Movement style
            if (def.movementStyle() != null) {
                tooltip.add(
                    Component.translatable(
                        "item.tiedup.tooltip.movement_style",
                        def.movementStyle().name().toLowerCase()
                    ).withStyle(ChatFormatting.DARK_PURPLE)
                );
            }
        }

        // Component tooltips
        ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
        if (holder != null) {
            holder.appendTooltip(stack, level, tooltip, flag);
        }

        // Lock status + escape difficulty (from AbstractV2BondageItem)
        super.appendHoverText(stack, level, tooltip, flag);

        if (def != null && flag.isAdvanced()) {
            // Advanced info (F3+H): pose priority + item ID
            tooltip.add(
                Component.translatable(
                    "item.tiedup.tooltip.pose_priority",
                    def.posePriority()
                ).withStyle(ChatFormatting.DARK_GRAY)
            );
            tooltip.add(
                Component.literal(
                    def.id().toString()
                ).withStyle(ChatFormatting.DARK_GRAY)
            );
        }
    }

    // ===== DISPLAY NAME =====

    @Override
    public Component getName(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        if (def == null) return super.getName(stack);
        if (def.translationKey() != null) {
            return Component.translatable(def.translationKey());
        }
        return Component.literal(def.displayName());
    }

    // ===== COMPONENT LIFECYCLE DELEGATION =====

    @Override
    public void onEquipped(ItemStack stack, LivingEntity entity) {
        ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
        if (holder != null) {
            holder.onEquipped(stack, entity);
        }

        // Initialize currentResistance in NBT from ResistanceComponent at equip time.
        // This avoids the MAX-scan fallback in getBaseResistance(entity) which returns
        // the highest resistance across ALL equipped data-driven items.
        ResistanceComponent resistComp = getComponent(stack, ComponentType.RESISTANCE, ResistanceComponent.class);
        if (resistComp != null) {
            com.tiedup.remake.items.base.IHasResistance resistanceItem =
                (com.tiedup.remake.items.base.IHasResistance) stack.getItem();
            resistanceItem.setCurrentResistance(stack, resistComp.getBaseResistance());
        }

        // NPC speed reduction (players use MovementStyleManager, not this legacy path)
        if (!(entity instanceof Player)) {
            Set<BodyRegionV2> regions = getOccupiedRegions(stack);
            if (regions.contains(BodyRegionV2.ARMS) && BindModeHelper.hasLegsBound(stack)) {
                com.tiedup.remake.items.base.PoseType pose = com.tiedup.remake.v2.bondage.PoseTypeHelper.getPoseType(stack);
                boolean fullImmobilization = pose == com.tiedup.remake.items.base.PoseType.WRAP
                    || pose == com.tiedup.remake.items.base.PoseType.LATEX_SACK;
                com.tiedup.remake.util.RestraintEffectUtils.applyBindSpeedReduction(entity, fullImmobilization);
            }
        }
    }

    @Override
    public void onUnequipped(ItemStack stack, LivingEntity entity) {
        ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
        if (holder != null) {
            holder.onUnequipped(stack, entity);
        }

        // NPC speed cleanup
        if (!(entity instanceof Player)) {
            Set<BodyRegionV2> regions = getOccupiedRegions(stack);
            if (regions.contains(BodyRegionV2.ARMS)) {
                com.tiedup.remake.util.RestraintEffectUtils.removeBindSpeedReduction(entity);
            }
        }
    }

    @Override
    public boolean canUnequip(ItemStack stack, LivingEntity entity) {
        ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
        if (holder != null && holder.blocksUnequip(stack, entity)) {
            return false;
        }
        return super.canUnequip(stack, entity);
    }

    /**
     * Get a specific component from a data-driven item stack.
     *
     * @param stack the ItemStack to inspect
     * @param type  the component type to look up
     * @param clazz the expected component class
     * @param <T>   the component type
     * @return the component, or null if the item is not data-driven or lacks this component
     */
    @Nullable
    public static <T extends IItemComponent> T getComponent(
        ItemStack stack,
        ComponentType type,
        Class<T> clazz
    ) {
        ComponentHolder holder = DataDrivenItemRegistry.getComponents(stack);
        if (holder == null) return null;
        return holder.get(type, clazz);
    }

    /**
     * Get per-item lock resistance from the LockableComponent, if present.
     *
     * <p>Returns the component's JSON-configured value if the stack has a
     * LockableComponent, otherwise falls back to the global config value
     * from {@link com.tiedup.remake.core.SettingsAccessor#getPadlockResistance}.</p>
     *
     * @param stack the ItemStack to inspect
     * @return lock resistance value for this specific item
     */
    public static int getItemLockResistance(ItemStack stack) {
        LockableComponent comp = getComponent(
            stack,
            ComponentType.LOCKABLE,
            LockableComponent.class
        );
        if (comp != null) {
            return comp.getLockResistance();
        }
        return com.tiedup.remake.core.SettingsAccessor.getPadlockResistance(
            null
        );
    }

    /** Check if the item has a built-in lock (organic items: slime, vine, web, tape). */
    public static boolean hasBuiltInLock(ItemStack stack) {
        return getComponent(stack, ComponentType.BUILT_IN_LOCK, BuiltInLockComponent.class) != null;
    }

    /**
     * Stack-aware padlock attachment check for data-driven items.
     * Returns false for organic items (slime, vine, web, tape) that have
     * {@code can_attach_padlock: false} in their JSON definition.
     */
    public static boolean canAttachPadlockTo(ItemStack stack) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        if (def != null) {
            return def.canAttachPadlock();
        }
        return true;
    }

    // ===== FACTORY =====

    /**
     * Create an ItemStack for a data-driven bondage item.
     *
     * @param itemId the definition ID (must exist in {@link DataDrivenItemRegistry})
     * @return a new ItemStack with the {@code tiedup_item_id} NBT tag set,
     *         or {@link ItemStack#EMPTY} if the item is not registered in Forge
     */
    public static ItemStack createStack(ResourceLocation itemId) {
        if (V2BondageItems.DATA_DRIVEN_ITEM == null) return ItemStack.EMPTY;
        ItemStack stack = new ItemStack(V2BondageItems.DATA_DRIVEN_ITEM.get());
        stack
            .getOrCreateTag()
            .putString(DataDrivenItemRegistry.NBT_ITEM_ID, itemId.toString());
        return stack;
    }
}
