package com.tiedup.remake.blocks.entity;

import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GaggingComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;

/**
 * Base BlockEntity for blocks that store bondage items.
 *
 *
 * Stores up to 6 bondage items:
 * - Bind (ropes, chains, straitjacket, etc.)
 * - Gag
 * - Blindfold
 * - Earplugs
 * - Collar
 * - Clothes
 *
 * Features:
 * - Full NBT serialization
 * - Network synchronization for client rendering
 * - Item type validation on load
 *
 * Based on original TileEntityBondageItemHandler from 1.12.2
 */
public abstract class BondageItemBlockEntity
    extends BlockEntity
    implements IBondageItemHolder
{

    // STORED ITEMS

    private ItemStack bind = ItemStack.EMPTY;
    private ItemStack gag = ItemStack.EMPTY;
    private ItemStack blindfold = ItemStack.EMPTY;
    private ItemStack earplugs = ItemStack.EMPTY;
    private ItemStack collar = ItemStack.EMPTY;
    private ItemStack clothes = ItemStack.EMPTY;

    /**
     * Off-mode prevents network updates.
     * Used when reading NBT for tooltips without affecting the world.
     */
    private final boolean offMode;

    // CONSTRUCTORS

    public BondageItemBlockEntity(
        BlockEntityType<?> type,
        BlockPos pos,
        BlockState state
    ) {
        this(type, pos, state, false);
    }

    public BondageItemBlockEntity(
        BlockEntityType<?> type,
        BlockPos pos,
        BlockState state,
        boolean offMode
    ) {
        super(type, pos, state);
        this.offMode = offMode;
    }

    // BIND

    @Override
    public ItemStack getBind() {
        return this.bind;
    }

    @Override
    public void setBind(ItemStack bind) {
        this.bind = bind != null ? bind : ItemStack.EMPTY;
        this.setChangedAndSync();
    }

    // GAG

    @Override
    public ItemStack getGag() {
        return this.gag;
    }

    @Override
    public void setGag(ItemStack gag) {
        this.gag = gag != null ? gag : ItemStack.EMPTY;
        this.setChangedAndSync();
    }

    // BLINDFOLD

    @Override
    public ItemStack getBlindfold() {
        return this.blindfold;
    }

    @Override
    public void setBlindfold(ItemStack blindfold) {
        this.blindfold = blindfold != null ? blindfold : ItemStack.EMPTY;
        this.setChangedAndSync();
    }

    // EARPLUGS

    @Override
    public ItemStack getEarplugs() {
        return this.earplugs;
    }

    @Override
    public void setEarplugs(ItemStack earplugs) {
        this.earplugs = earplugs != null ? earplugs : ItemStack.EMPTY;
        this.setChangedAndSync();
    }

    // COLLAR

    @Override
    public ItemStack getCollar() {
        return this.collar;
    }

    @Override
    public void setCollar(ItemStack collar) {
        this.collar = collar != null ? collar : ItemStack.EMPTY;
        this.setChangedAndSync();
    }

    // CLOTHES

    @Override
    public ItemStack getClothes() {
        return this.clothes;
    }

    @Override
    public void setClothes(ItemStack clothes) {
        this.clothes = clothes != null ? clothes : ItemStack.EMPTY;
        this.setChangedAndSync();
    }

    // STATE

    @Override
    public boolean isArmed() {
        return !this.bind.isEmpty();
    }

    /**
     * Clear all stored bondage items.
     * Called after applying items to a target.
     */
    public void clearAllItems() {
        this.bind = ItemStack.EMPTY;
        this.gag = ItemStack.EMPTY;
        this.blindfold = ItemStack.EMPTY;
        this.earplugs = ItemStack.EMPTY;
        this.collar = ItemStack.EMPTY;
        this.clothes = ItemStack.EMPTY;
        this.setChangedAndSync();
    }

    // NBT SERIALIZATION

    @Override
    public void load(CompoundTag tag) {
        super.load(tag);
        this.readBondageData(tag);
    }

    @Override
    protected void saveAdditional(CompoundTag tag) {
        super.saveAdditional(tag);
        this.writeBondageData(tag);
    }

    @Override
    public void readBondageData(CompoundTag tag) {
        // Read bind with type validation (V2 ARMS-region item)
        if (tag.contains("bind")) {
            ItemStack bindStack = ItemStack.of(tag.getCompound("bind"));
            if (!bindStack.isEmpty() && BindModeHelper.isBindItem(bindStack)) {
                this.bind = bindStack;
            }
        }

        // Read gag with type validation (V2 GAGGING component)
        if (tag.contains("gag")) {
            ItemStack gagStack = ItemStack.of(tag.getCompound("gag"));
            if (!gagStack.isEmpty()
                    && DataDrivenBondageItem.getComponent(gagStack, ComponentType.GAGGING, GaggingComponent.class) != null) {
                this.gag = gagStack;
            }
        }

        // Read blindfold with type validation (V2 EYES-region item)
        if (tag.contains("blindfold")) {
            ItemStack blindfoldStack = ItemStack.of(tag.getCompound("blindfold"));
            if (!blindfoldStack.isEmpty() && isDataDrivenForRegion(blindfoldStack, BodyRegionV2.EYES)) {
                this.blindfold = blindfoldStack;
            }
        }

        // Read earplugs with type validation (V2 EARS-region item)
        if (tag.contains("earplugs")) {
            ItemStack earplugsStack = ItemStack.of(tag.getCompound("earplugs"));
            if (!earplugsStack.isEmpty() && isDataDrivenForRegion(earplugsStack, BodyRegionV2.EARS)) {
                this.earplugs = earplugsStack;
            }
        }

        // Read collar with type validation (V2 collar)
        if (tag.contains("collar")) {
            ItemStack collarStack = ItemStack.of(tag.getCompound("collar"));
            if (!collarStack.isEmpty() && CollarHelper.isCollar(collarStack)) {
                this.collar = collarStack;
            }
        }

        // Read clothes with type validation
        if (tag.contains("clothes")) {
            ItemStack clothesStack = ItemStack.of(tag.getCompound("clothes"));
            if (
                !clothesStack.isEmpty() &&
                clothesStack.getItem() instanceof GenericClothes
            ) {
                this.clothes = clothesStack;
            }
        }
    }

    @Override
    public CompoundTag writeBondageData(CompoundTag tag) {
        if (!this.bind.isEmpty()) {
            tag.put("bind", this.bind.save(new CompoundTag()));
        }
        if (!this.gag.isEmpty()) {
            tag.put("gag", this.gag.save(new CompoundTag()));
        }
        if (!this.blindfold.isEmpty()) {
            tag.put("blindfold", this.blindfold.save(new CompoundTag()));
        }
        if (!this.earplugs.isEmpty()) {
            tag.put("earplugs", this.earplugs.save(new CompoundTag()));
        }
        if (!this.collar.isEmpty()) {
            tag.put("collar", this.collar.save(new CompoundTag()));
        }
        if (!this.clothes.isEmpty()) {
            tag.put("clothes", this.clothes.save(new CompoundTag()));
        }
        return tag;
    }

    // V2 HELPERS

    /** Check if a stack is a data-driven item occupying the given body region. */
    private static boolean isDataDrivenForRegion(ItemStack stack, BodyRegionV2 region) {
        DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
        return def != null && def.occupiedRegions().contains(region);
    }

    // NETWORK SYNC

    /**
     * Mark dirty and sync to clients.
     */
    protected void setChangedAndSync() {
        if (!this.offMode && this.level != null) {
            this.setChanged();
            // Notify clients of block update
            this.level.sendBlockUpdated(
                this.worldPosition,
                this.getBlockState(),
                this.getBlockState(),
                3
            );
        }
    }

    @Override
    public CompoundTag getUpdateTag() {
        CompoundTag tag = super.getUpdateTag();
        this.writeBondageData(tag);
        return tag;
    }

    @Nullable
    @Override
    public Packet<ClientGamePacketListener> getUpdatePacket() {
        return ClientboundBlockEntityDataPacket.create(this);
    }

    @Override
    public void handleUpdateTag(CompoundTag tag) {
        if (!this.offMode) {
            this.readBondageData(tag);
        }
    }
}
