package com.tiedup.remake.items.clothes;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageItem;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;

/**
 * Generic clothes item with full NBT-based configuration.
 *
 * <p>Clothes are cosmetic items that can:
 * <ul>
 *   <li>Use dynamic textures from URLs</li>
 *   <li>Replace the entire player skin (full-skin mode)</li>
 *   <li>Force slim arm model</li>
 *   <li>Control visibility of wearer's body parts</li>
 *   <li>Be locked with padlocks</li>
 * </ul>
 *
 * <p>Unlike other bondage items, clothes have NO gameplay effects - they are purely visual.
 */
public class GenericClothes extends Item implements ILockable, IV2BondageItem {

    // ========== NBT KEYS ==========
    public static final String NBT_DYNAMIC_TEXTURE = "dynamicTexture";
    public static final String NBT_FULL_SKIN = "fullSkin";
    public static final String NBT_SMALL_ARMS = "smallArms";
    public static final String NBT_KEEP_HEAD = "keepHead";
    public static final String NBT_LAYER_VISIBILITY = "layerVisibility";
    public static final String NBT_LOCKED = "locked";
    public static final String NBT_LOCKABLE = "lockable";
    public static final String NBT_LOCKED_BY_KEY_UUID = "lockedByKeyUUID";

    // Layer visibility keys
    public static final String LAYER_HEAD = "head";
    public static final String LAYER_BODY = "body";
    public static final String LAYER_LEFT_ARM = "leftArm";
    public static final String LAYER_RIGHT_ARM = "rightArm";
    public static final String LAYER_LEFT_LEG = "leftLeg";
    public static final String LAYER_RIGHT_LEG = "rightLeg";

    public GenericClothes() {
        super(new Item.Properties().stacksTo(16));
    }

    // ========== Lifecycle Hooks ==========

    @Override
    public void onEquipped(ItemStack stack, LivingEntity entity) {
        // Clothes have no special equip effects - purely cosmetic
    }

    @Override
    public void onUnequipped(ItemStack stack, LivingEntity entity) {
        // Clothes have no special unequip effects
    }

    /**
     * Called when player right-clicks another entity with clothes.
     * Allows putting clothes on tied-up entities (Players and NPCs).
     *
     * Unlike other bondage items, clothes can also be put on non-tied players
     * if game rules allow it (roleplay scenarios).
     *
     * @param stack The item stack
     * @param player The player using the item
     * @param target The entity being interacted with
     * @param hand The hand holding the item
     * @return SUCCESS if clothes equipped/replaced, PASS otherwise
     */
    @Override
    public InteractionResult interactLivingEntity(
        ItemStack stack,
        Player player,
        LivingEntity target,
        InteractionHand hand
    ) {
        // Server-side only
        if (player.level().isClientSide) {
            return InteractionResult.SUCCESS;
        }

        // Check if target can wear clothes (Player, EntityDamsel, EntityKidnapper)
        IBondageState targetState = KidnappedHelper.getKidnappedState(target);
        if (targetState == null) {
            return InteractionResult.PASS; // Entity cannot wear clothes
        }

        // Unlike gags/blindfolds, clothes can be put on non-tied players too
        // But if tied, always allowed. If not tied, check if target allows it.
        if (!targetState.isTiedUp() && !targetState.canChangeClothes(player)) {
            return InteractionResult.PASS;
        }

        // Case 1: No clothes yet - equip new one
        if (!targetState.hasClothes()) {
            ItemStack clothesCopy = stack.copyWithCount(1);
            targetState.equip(BodyRegionV2.TORSO, clothesCopy);
            stack.shrink(1);

            // Sync equipment to all tracking clients
            if (target instanceof ServerPlayer serverPlayer) {
                SyncManager.syncInventory(serverPlayer);
                SyncManager.syncClothesConfig(serverPlayer);
            }

            TiedUpMod.LOGGER.info(
                "[GenericClothes] {} put clothes on {}",
                player.getName().getString(),
                target.getName().getString()
            );

            return InteractionResult.SUCCESS;
        }
        // Case 2: Already has clothes - replace them
        else {
            ItemStack clothesCopy = stack.copyWithCount(1);
            ItemStack oldClothes = targetState.replaceEquipment(
                BodyRegionV2.TORSO,
                clothesCopy,
                false
            );
            if (!oldClothes.isEmpty()) {
                stack.shrink(1);
                targetState.kidnappedDropItem(oldClothes);

                // Sync equipment to all tracking clients
                if (target instanceof ServerPlayer serverPlayer) {
                    SyncManager.syncInventory(serverPlayer);
                    SyncManager.syncClothesConfig(serverPlayer);
                }

                TiedUpMod.LOGGER.info(
                    "[GenericClothes] {} replaced clothes on {}",
                    player.getName().getString(),
                    target.getName().getString()
                );

                return InteractionResult.SUCCESS;
            }
        }

        return InteractionResult.PASS;
    }

    // ========== Dynamic Texture Methods ==========

    /**
     * Get the dynamic texture URL from this clothes item.
     *
     * @param stack The ItemStack to check
     * @return The URL string, or null if not set
     */
    @Nullable
    public String getDynamicTextureUrl(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag != null && tag.contains(NBT_DYNAMIC_TEXTURE)) {
            String url = tag.getString(NBT_DYNAMIC_TEXTURE);
            return url.isEmpty() ? null : url;
        }
        return null;
    }

    /**
     * Set the dynamic texture URL for this clothes item.
     *
     * @param stack The ItemStack to modify
     * @param url The URL to set
     */
    public void setDynamicTextureUrl(ItemStack stack, String url) {
        if (url != null && !url.isEmpty()) {
            stack.getOrCreateTag().putString(NBT_DYNAMIC_TEXTURE, url);
        }
    }

    /**
     * Remove the dynamic texture URL from this clothes item.
     *
     * @param stack The ItemStack to modify
     */
    public void removeDynamicTextureUrl(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag != null) {
            tag.remove(NBT_DYNAMIC_TEXTURE);
        }
    }

    /**
     * Check if this clothes item has a dynamic texture URL set.
     *
     * @param stack The ItemStack to check
     * @return true if a URL is set
     */
    public boolean hasDynamicTextureUrl(ItemStack stack) {
        return getDynamicTextureUrl(stack) != null;
    }

    // ========== Full Skin / Small Arms Methods ==========

    /**
     * Check if full-skin mode is enabled.
     * In full-skin mode, the clothes texture replaces the entire player skin.
     *
     * @param stack The ItemStack to check
     * @return true if full-skin mode is enabled
     */
    public boolean isFullSkinEnabled(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean(NBT_FULL_SKIN);
    }

    /**
     * Set full-skin mode.
     *
     * @param stack The ItemStack to modify
     * @param enabled true to enable full-skin mode
     */
    public void setFullSkinEnabled(ItemStack stack, boolean enabled) {
        stack.getOrCreateTag().putBoolean(NBT_FULL_SKIN, enabled);
    }

    /**
     * Check if small arms (slim model) should be forced.
     *
     * @param stack The ItemStack to check
     * @return true if small arms should be forced
     */
    public boolean shouldForceSmallArms(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean(NBT_SMALL_ARMS);
    }

    /**
     * Set whether small arms (slim model) should be forced.
     *
     * @param stack The ItemStack to modify
     * @param enabled true to force small arms
     */
    public void setForceSmallArms(ItemStack stack, boolean enabled) {
        stack.getOrCreateTag().putBoolean(NBT_SMALL_ARMS, enabled);
    }

    /**
     * Check if keep head mode is enabled.
     * When enabled, the wearer's head/hat layers are preserved instead of being
     * replaced by the clothes texture. Useful for keeping the original face.
     *
     * @param stack The ItemStack to check
     * @return true if keep head mode is enabled
     */
    public boolean isKeepHeadEnabled(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean(NBT_KEEP_HEAD);
    }

    /**
     * Set keep head mode.
     * When enabled, the wearer's head/hat layers are preserved.
     *
     * @param stack The ItemStack to modify
     * @param enabled true to keep the wearer's head
     */
    public void setKeepHeadEnabled(ItemStack stack, boolean enabled) {
        stack.getOrCreateTag().putBoolean(NBT_KEEP_HEAD, enabled);
    }

    // ========== Layer Visibility Methods ==========

    /**
     * Check if a specific body layer is enabled (visible on wearer).
     * Defaults to true (visible) if not set.
     *
     * @param stack The ItemStack to check
     * @param layer The layer key (use LAYER_* constants)
     * @return true if the layer is visible
     */
    public boolean isLayerEnabled(ItemStack stack, String layer) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains(NBT_LAYER_VISIBILITY)) {
            return true; // Default: all layers visible
        }
        CompoundTag layers = tag.getCompound(NBT_LAYER_VISIBILITY);
        // If not specified, default to visible
        return !layers.contains(layer) || layers.getBoolean(layer);
    }

    /**
     * Set the visibility of a specific body layer on the wearer.
     *
     * @param stack The ItemStack to modify
     * @param layer The layer key (use LAYER_* constants)
     * @param enabled true to show the layer, false to hide it
     */
    public void setLayerEnabled(
        ItemStack stack,
        String layer,
        boolean enabled
    ) {
        CompoundTag tag = stack.getOrCreateTag();
        CompoundTag layers = tag.contains(NBT_LAYER_VISIBILITY)
            ? tag.getCompound(NBT_LAYER_VISIBILITY)
            : new CompoundTag();
        layers.putBoolean(layer, enabled);
        tag.put(NBT_LAYER_VISIBILITY, layers);
    }

    /**
     * Get all layer visibility settings as a compound tag.
     *
     * @param stack The ItemStack to check
     * @return The layer visibility compound, or null if not set
     */
    @Nullable
    public CompoundTag getLayerVisibility(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag != null && tag.contains(NBT_LAYER_VISIBILITY)) {
            return tag.getCompound(NBT_LAYER_VISIBILITY);
        }
        return null;
    }

    // ========== IV2BondageItem Implementation ==========

    private static final Set<BodyRegionV2> REGIONS =
        Collections.unmodifiableSet(EnumSet.of(BodyRegionV2.TORSO));

    @Override
    public Set<BodyRegionV2> getOccupiedRegions() {
        return REGIONS;
    }

    @Override
    @Nullable
    public ResourceLocation getModelLocation() {
        return null; // Clothes use URL-texture rendering, not GLB models
    }

    @Override
    public int getPosePriority() {
        return 0; // Cosmetic item, never forces a pose
    }

    @Override
    public int getEscapeDifficulty() {
        return 0; // Cosmetic, no struggle resistance
    }

    @Override
    public boolean supportsColor() {
        return false; // Color is handled via URL texture, not variant system
    }

    @Override
    public boolean supportsSlimModel() {
        return false; // Slim/wide is handled via NBT smallArms flag, not model variants
    }

    @Override
    public boolean canEquip(ItemStack stack, LivingEntity entity) {
        return true;
    }

    @Override
    public boolean canUnequip(ItemStack stack, LivingEntity entity) {
        return true;
    }

    // ========== ILockable Implementation ==========

    @Override
    public ItemStack setLocked(ItemStack stack, boolean state) {
        stack.getOrCreateTag().putBoolean(NBT_LOCKED, state);
        if (!state) {
            // When unlocking, clear lock-related data
            setJammed(stack, false);
        }
        return stack;
    }

    @Override
    public boolean isLocked(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean(NBT_LOCKED);
    }

    @Override
    public ItemStack setLockable(ItemStack stack, boolean state) {
        stack.getOrCreateTag().putBoolean(NBT_LOCKABLE, state);
        return stack;
    }

    @Override
    public boolean isLockable(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        // Default to true if not set
        return (
            tag == null ||
            !tag.contains(NBT_LOCKABLE) ||
            tag.getBoolean(NBT_LOCKABLE)
        );
    }

    @Override
    @Nullable
    public UUID getLockedByKeyUUID(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag != null && tag.hasUUID(NBT_LOCKED_BY_KEY_UUID)) {
            return tag.getUUID(NBT_LOCKED_BY_KEY_UUID);
        }
        return null;
    }

    @Override
    public void setLockedByKeyUUID(ItemStack stack, @Nullable UUID keyUUID) {
        CompoundTag tag = stack.getOrCreateTag();
        if (keyUUID != null) {
            tag.putUUID(NBT_LOCKED_BY_KEY_UUID, keyUUID);
            setLocked(stack, true);
        } else {
            tag.remove(NBT_LOCKED_BY_KEY_UUID);
            setLocked(stack, false);
        }
    }

    // ========== Tooltip ==========

    @Override
    public void appendHoverText(
        ItemStack stack,
        @Nullable Level level,
        List<Component> tooltip,
        TooltipFlag flag
    ) {
        super.appendHoverText(stack, level, tooltip, flag);

        // Dynamic texture info
        String url = getDynamicTextureUrl(stack);
        if (url != null) {
            tooltip.add(
                Component.translatable(
                    "item.tiedup.clothes.tooltip.has_url"
                ).withStyle(ChatFormatting.GREEN)
            );
            if (isFullSkinEnabled(stack)) {
                tooltip.add(
                    Component.translatable(
                        "item.tiedup.clothes.tooltip.full_skin"
                    ).withStyle(ChatFormatting.AQUA)
                );
            }
            if (shouldForceSmallArms(stack)) {
                tooltip.add(
                    Component.translatable(
                        "item.tiedup.clothes.tooltip.small_arms"
                    ).withStyle(ChatFormatting.AQUA)
                );
            }
        } else {
            tooltip.add(
                Component.translatable(
                    "item.tiedup.clothes.tooltip.no_url"
                ).withStyle(ChatFormatting.GRAY)
            );
        }

        // Layer visibility info
        CompoundTag layers = getLayerVisibility(stack);
        if (layers != null) {
            StringBuilder disabled = new StringBuilder();
            if (!isLayerEnabled(stack, LAYER_HEAD)) disabled.append("head ");
            if (!isLayerEnabled(stack, LAYER_BODY)) disabled.append("body ");
            if (!isLayerEnabled(stack, LAYER_LEFT_ARM)) disabled.append(
                "L.arm "
            );
            if (!isLayerEnabled(stack, LAYER_RIGHT_ARM)) disabled.append(
                "R.arm "
            );
            if (!isLayerEnabled(stack, LAYER_LEFT_LEG)) disabled.append(
                "L.leg "
            );
            if (!isLayerEnabled(stack, LAYER_RIGHT_LEG)) disabled.append(
                "R.leg "
            );

            if (!disabled.isEmpty()) {
                tooltip.add(
                    Component.translatable(
                        "item.tiedup.clothes.tooltip.layers_disabled",
                        disabled.toString().trim()
                    ).withStyle(ChatFormatting.YELLOW)
                );
            }
        }

        // Lock info
        if (isLocked(stack)) {
            tooltip.add(
                Component.translatable("item.tiedup.locked").withStyle(
                    ChatFormatting.RED
                )
            );
        }
    }
}
