package com.tiedup.remake.entities.armorstand;

import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.Map;
import net.minecraft.core.Rotations;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.item.ItemStack;

/**
 * Helper class for storing and retrieving bondage items on armor stands.
 * Uses NBT storage in the armor stand's persistent data.
 *
 * <p>Keys in PersistentData use {@link BodyRegionV2#name()} (e.g., "ARMS", "MOUTH").
 * Legacy keys from pre-Epic-7D ("bind", "gag", etc.) are read transparently
 * via {@link #LEGACY_KEYS} fallback in {@link #getStackInSlot}.
 */
public class ArmorStandBondageHelper {

    private static final String NBT_KEY = "TiedUpBondage";
    private static final String ORIGINAL_POSE_KEY = "originalPose";

    /**
     * The 7 body regions used by armor stand bondage slots.
     * The 7 bondage-relevant body regions for armor stands.
     */
    public static final BodyRegionV2[] VALID_REGIONS = {
        BodyRegionV2.ARMS,
        BodyRegionV2.MOUTH,
        BodyRegionV2.EYES,
        BodyRegionV2.EARS,
        BodyRegionV2.NECK,
        BodyRegionV2.TORSO,
        BodyRegionV2.HANDS,
    };

    /**
     * Legacy NBT key mapping from pre-Epic-7D format.
     * Used as fallback when reading old armor stand data.
     */
    private static final Map<String, BodyRegionV2> LEGACY_KEYS = Map.of(
        "bind",
        BodyRegionV2.ARMS,
        "gag",
        BodyRegionV2.MOUTH,
        "blindfold",
        BodyRegionV2.EYES,
        "earplugs",
        BodyRegionV2.EARS,
        "collar",
        BodyRegionV2.NECK,
        "clothes",
        BodyRegionV2.TORSO,
        "mittens",
        BodyRegionV2.HANDS
    );

    /**
     * Get a bondage item from an armor stand slot.
     *
     * <p>Tries the V2 key ({@code region.name()}) first, then falls back to
     * the legacy V1 key for backwards compatibility with existing worlds.
     *
     * @param stand The armor stand
     * @param region The body region
     * @return The item in that slot, or empty if none
     */
    public static ItemStack getStackInSlot(
        ArmorStand stand,
        BodyRegionV2 region
    ) {
        CompoundTag tag = stand.getPersistentData();
        if (!tag.contains(NBT_KEY)) return ItemStack.EMPTY;

        CompoundTag bondageTag = tag.getCompound(NBT_KEY);
        String key = region.name();

        // Try V2 key first
        if (bondageTag.contains(key)) {
            return ItemStack.of(bondageTag.getCompound(key));
        }

        // Fallback: try legacy V1 key
        for (Map.Entry<String, BodyRegionV2> entry : LEGACY_KEYS.entrySet()) {
            if (
                entry.getValue() == region &&
                bondageTag.contains(entry.getKey())
            ) {
                return ItemStack.of(bondageTag.getCompound(entry.getKey()));
            }
        }

        return ItemStack.EMPTY;
    }

    /**
     * Set a bondage item in an armor stand slot.
     *
     * <p>Always writes using the V2 key ({@code region.name()}).
     * Also removes any legacy V1 key for the same region to avoid duplication.
     *
     * @param stand The armor stand
     * @param region The body region
     * @param stack The item to set (empty to clear)
     */
    public static void setStackInSlot(
        ArmorStand stand,
        BodyRegionV2 region,
        ItemStack stack
    ) {
        CompoundTag tag = stand.getPersistentData();
        CompoundTag bondageTag = tag.contains(NBT_KEY)
            ? tag.getCompound(NBT_KEY)
            : new CompoundTag();

        String v2Key = region.name();

        if (stack.isEmpty()) {
            bondageTag.remove(v2Key);
        } else {
            bondageTag.put(v2Key, stack.save(new CompoundTag()));
        }

        // Remove legacy key if present to avoid duplication
        for (Map.Entry<String, BodyRegionV2> entry : LEGACY_KEYS.entrySet()) {
            if (entry.getValue() == region) {
                bondageTag.remove(entry.getKey());
                break;
            }
        }

        tag.put(NBT_KEY, bondageTag);

        // Update pose when bind (ARMS region) changes
        if (region == BodyRegionV2.ARMS) {
            updateArmorStandPose(stand);
        }
    }

    /**
     * Check if an armor stand has any bondage items equipped.
     *
     * @param stand The armor stand
     * @return True if any bondage slot has an item
     */
    public static boolean hasBondageItems(ArmorStand stand) {
        for (BodyRegionV2 region : VALID_REGIONS) {
            if (!getStackInSlot(stand, region).isEmpty()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get all bondage items from an armor stand.
     *
     * @param stand The armor stand
     * @return Array of items indexed by {@link #VALID_REGIONS} order
     */
    public static ItemStack[] getAllItems(ArmorStand stand) {
        ItemStack[] items = new ItemStack[VALID_REGIONS.length];
        for (int i = 0; i < VALID_REGIONS.length; i++) {
            items[i] = getStackInSlot(stand, VALID_REGIONS[i]);
        }
        return items;
    }

    /**
     * Update the armor stand's pose based on equipped bind.
     *
     * @param stand The armor stand
     */
    public static void updateArmorStandPose(ArmorStand stand) {
        ItemStack bindStack = getStackInSlot(stand, BodyRegionV2.ARMS);

        if (bindStack.isEmpty()) {
            // Restore original pose if saved
            restoreOriginalPose(stand);
            return;
        }

        // Save original pose if not already saved
        saveOriginalPose(stand);

        // Get pose type from bind (V2 data-driven)
        PoseType poseType = PoseTypeHelper.getPoseType(bindStack);

        // Apply pose
        applyBondagePose(stand, poseType);
    }

    /**
     * Save the armor stand's current pose as the original pose.
     */
    private static void saveOriginalPose(ArmorStand stand) {
        CompoundTag tag = stand.getPersistentData();
        CompoundTag bondageTag = tag.contains(NBT_KEY)
            ? tag.getCompound(NBT_KEY)
            : new CompoundTag();

        if (bondageTag.contains(ORIGINAL_POSE_KEY)) {
            return; // Already saved
        }

        CompoundTag poseTag = new CompoundTag();
        poseTag.put("head", rotationsToTag(stand.getHeadPose()));
        poseTag.put("body", rotationsToTag(stand.getBodyPose()));
        poseTag.put("leftArm", rotationsToTag(stand.getLeftArmPose()));
        poseTag.put("rightArm", rotationsToTag(stand.getRightArmPose()));
        poseTag.put("leftLeg", rotationsToTag(stand.getLeftLegPose()));
        poseTag.put("rightLeg", rotationsToTag(stand.getRightLegPose()));

        bondageTag.put(ORIGINAL_POSE_KEY, poseTag);
        tag.put(NBT_KEY, bondageTag);
    }

    /**
     * Restore the armor stand's original pose.
     */
    private static void restoreOriginalPose(ArmorStand stand) {
        CompoundTag tag = stand.getPersistentData();
        if (!tag.contains(NBT_KEY)) return;

        CompoundTag bondageTag = tag.getCompound(NBT_KEY);
        if (!bondageTag.contains(ORIGINAL_POSE_KEY)) return;

        CompoundTag poseTag = bondageTag.getCompound(ORIGINAL_POSE_KEY);

        stand.setHeadPose(tagToRotations(poseTag.getCompound("head")));
        stand.setBodyPose(tagToRotations(poseTag.getCompound("body")));
        stand.setLeftArmPose(tagToRotations(poseTag.getCompound("leftArm")));
        stand.setRightArmPose(tagToRotations(poseTag.getCompound("rightArm")));
        stand.setLeftLegPose(tagToRotations(poseTag.getCompound("leftLeg")));
        stand.setRightLegPose(tagToRotations(poseTag.getCompound("rightLeg")));

        // Remove saved pose
        bondageTag.remove(ORIGINAL_POSE_KEY);
        tag.put(NBT_KEY, bondageTag);
    }

    /**
     * Apply a bondage pose to an armor stand.
     */
    private static void applyBondagePose(ArmorStand stand, PoseType poseType) {
        switch (poseType) {
            case STANDARD -> {
                // Arms behind back
                stand.setRightArmPose(new Rotations(51.5f, 57.3f, 0f));
                stand.setLeftArmPose(new Rotations(51.5f, -57.3f, 0f));
                stand.setRightLegPose(new Rotations(0f, 0f, -5.7f));
                stand.setLeftLegPose(new Rotations(0f, 0f, 5.7f));
            }
            case STRAITJACKET -> {
                // Arms crossed in front
                stand.setRightArmPose(new Rotations(-45.8f, -28.6f, -17.2f));
                stand.setLeftArmPose(new Rotations(-45.8f, 28.6f, 17.2f));
                stand.setRightLegPose(new Rotations(0f, 0f, 0f));
                stand.setLeftLegPose(new Rotations(0f, 0f, 0f));
            }
            case WRAP -> {
                // Arms at sides, wrapped
                stand.setRightArmPose(new Rotations(0f, 0f, 10f));
                stand.setLeftArmPose(new Rotations(0f, 0f, -10f));
                stand.setRightLegPose(new Rotations(0f, 0f, -3f));
                stand.setLeftLegPose(new Rotations(0f, 0f, 3f));
            }
            case LATEX_SACK -> {
                // Full enclosure, legs together
                stand.setRightArmPose(new Rotations(0f, 0f, 5f));
                stand.setLeftArmPose(new Rotations(0f, 0f, -5f));
                stand.setRightLegPose(new Rotations(0f, 0f, -2f));
                stand.setLeftLegPose(new Rotations(0f, 0f, 2f));
            }
            case DOG, HUMAN_CHAIR -> {
                // On all fours (for armor stand, just bent forward)
                stand.setBodyPose(new Rotations(45f, 0f, 0f));
                stand.setRightArmPose(new Rotations(-90f, 0f, 0f));
                stand.setLeftArmPose(new Rotations(-90f, 0f, 0f));
                stand.setRightLegPose(new Rotations(-45f, 0f, 0f));
                stand.setLeftLegPose(new Rotations(-45f, 0f, 0f));
            }
        }
    }

    private static CompoundTag rotationsToTag(Rotations rotations) {
        CompoundTag tag = new CompoundTag();
        tag.putFloat("x", rotations.getX());
        tag.putFloat("y", rotations.getY());
        tag.putFloat("z", rotations.getZ());
        return tag;
    }

    private static Rotations tagToRotations(CompoundTag tag) {
        return new Rotations(
            tag.getFloat("x"),
            tag.getFloat("y"),
            tag.getFloat("z")
        );
    }
}
