package com.tiedup.remake.client.animation.tick;

import com.mojang.logging.LogUtils;
import com.tiedup.remake.client.animation.AnimationStateRegistry;
import com.tiedup.remake.client.animation.BondageAnimationManager;
import com.tiedup.remake.client.animation.PendingAnimationManager;
import com.tiedup.remake.client.animation.context.AnimationContext;
import com.tiedup.remake.client.animation.context.AnimationContextResolver;
import com.tiedup.remake.client.animation.context.RegionBoneMapper;
import com.tiedup.remake.client.animation.util.AnimationIdBuilder;
import com.tiedup.remake.client.events.CellHighlightHandler;
import com.tiedup.remake.client.events.LeashProxyClientHandler;
import com.tiedup.remake.client.gltf.GltfAnimationApplier;
import com.tiedup.remake.client.state.ClothesClientCache;
import com.tiedup.remake.client.state.MovementStyleClientState;
import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.bondage.movement.MovementStyle;
import java.util.Map;
import java.util.UUID;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.slf4j.Logger;

/**
 * Event handler for player animation tick updates.
 *
 * <p>Simplified handler that:
 * <ul>
 *   <li>Tracks tied/struggling/sneaking state for players</li>
 *   <li>Plays animations via BondageAnimationManager when state changes</li>
 *   <li>Handles cleanup on logout/world unload</li>
 * </ul>
 *
 * <p>Registered on the FORGE event bus (not MOD bus).
 */
@Mod.EventBusSubscriber(
    modid = "tiedup",
    value = Dist.CLIENT,
    bus = Mod.EventBusSubscriber.Bus.FORGE
)
@OnlyIn(Dist.CLIENT)
public class AnimationTickHandler {

    private static final Logger LOGGER = LogUtils.getLogger();

    /** Tick counter for periodic cleanup tasks */
    private static int cleanupTickCounter = 0;

    /**
     * Client tick event - called every tick on the client.
     * Updates animations for all players when their bondage state changes.
     */
    @SubscribeEvent
    public static void onClientTick(TickEvent.ClientTickEvent event) {
        if (event.phase != TickEvent.Phase.END) {
            return;
        }

        Minecraft mc = Minecraft.getInstance();
        if (mc.level == null || mc.isPaused()) {
            return;
        }

        // Process pending animations first (retry failed animations for remote players)
        PendingAnimationManager.processPending(mc.level);

        // Periodic cleanup of stale cache entries (every 60 seconds = 1200 ticks)
        if (++cleanupTickCounter >= 1200) {
            cleanupTickCounter = 0;
            ClothesClientCache.cleanupStale();
        }

        // Then update all player animations
        for (Player player : mc.level.players()) {
            if (player instanceof AbstractClientPlayer clientPlayer) {
                updatePlayerAnimation(clientPlayer);
            }
            // Safety: remove stale furniture animations for players no longer on seats
            BondageAnimationManager.tickFurnitureSafety(player);
        }
    }

    /**
     * Update animation for a single player.
     */
    private static void updatePlayerAnimation(AbstractClientPlayer player) {
        // Safety check: skip for removed/dead players
        if (player.isRemoved() || !player.isAlive()) {
            return;
        }

        PlayerBindState state = PlayerBindState.getInstance(player);
        UUID uuid = player.getUUID();

        // Check if player has ANY V2 bondage item equipped (not just ARMS).
        // isTiedUp() only checks ARMS, but items on LEGS, HEAD, etc. also need animation.
        boolean isTied =
            state != null &&
            (state.isTiedUp() || V2EquipmentHelper.hasAnyEquipment(player));
        boolean wasTied =
            AnimationStateRegistry.getLastTiedState().getOrDefault(uuid, false);

        // Pet bed animations take priority over bondage animations
        if (PetBedClientState.get(uuid) != 0) {
            // Lock body rotation to bed facing (prevents camera from rotating the model)
            float lockedRot = PetBedClientState.getFacing(uuid);
            player.yBodyRot = lockedRot;
            player.yBodyRotO = lockedRot;

            // Clamp head rotation to ±50° from body (like vehicle)
            float headRot = player.getYHeadRot();
            float clamped =
                lockedRot +
                net.minecraft.util.Mth.clamp(
                    net.minecraft.util.Mth.wrapDegrees(headRot - lockedRot),
                    -50f,
                    50f
                );
            player.setYHeadRot(clamped);
            player.yHeadRotO = clamped;

            AnimationStateRegistry.getLastTiedState().put(uuid, isTied);
            return;
        }

        // Human chair: clamp 1st-person camera only (body lock handled by MixinLivingEntityBodyRot)
        // NO return — animation HUMAN_CHAIR must continue playing below
        if (isTied && state != null) {
            ItemStack chairBind = state.getEquipment(BodyRegionV2.ARMS);
            if (HumanChairHelper.isActive(chairBind)) {
                // 1st person only: clamp yRot so player can't look behind
                // 3rd person: yRot untouched → camera orbits freely 360°
                if (
                    player == Minecraft.getInstance().player &&
                    Minecraft.getInstance().options.getCameraType() ==
                    net.minecraft.client.CameraType.FIRST_PERSON
                ) {
                    float lockedRot = HumanChairHelper.getFacing(chairBind);
                    float camClamped =
                        lockedRot +
                        net.minecraft.util.Mth.clamp(
                            net.minecraft.util.Mth.wrapDegrees(
                                player.getYRot() - lockedRot
                            ),
                            -90f,
                            90f
                        );
                    player.setYRot(camClamped);
                    player.yRotO =
                        lockedRot +
                        net.minecraft.util.Mth.clamp(
                            net.minecraft.util.Mth.wrapDegrees(
                                player.yRotO - lockedRot
                            ),
                            -90f,
                            90f
                        );
                }
            }
        }

        if (isTied) {
            // Resolve V2 equipped items
            IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(
                player
            );
            Map<BodyRegionV2, ItemStack> equipped =
                equipment != null ? equipment.getAllEquipped() : Map.of();

            // Resolve ALL V2 items with GLB models and per-item bone ownership
            java.util.List<RegionBoneMapper.V2ItemAnimInfo> v2Items =
                RegionBoneMapper.resolveAllV2Items(equipped);

            if (!v2Items.isEmpty()) {
                // V2 path: multi-item composite animation
                java.util.Set<String> allOwnedParts =
                    RegionBoneMapper.computeAllOwnedParts(v2Items);
                MovementStyle activeStyle = MovementStyleClientState.get(
                    player.getUUID()
                );
                AnimationContext context = AnimationContextResolver.resolve(
                    player,
                    state,
                    activeStyle
                );
                GltfAnimationApplier.applyMultiItemV2Animation(
                    player,
                    v2Items,
                    context,
                    allOwnedParts
                );
                // Clear V1 tracking so transition back works
                AnimationStateRegistry.getLastAnimId().remove(uuid);
            } else {
                // V1 fallback
                if (GltfAnimationApplier.hasActiveState(player)) {
                    GltfAnimationApplier.clearV2Animation(player);
                }
                String animId = buildAnimationId(player, state);
                String lastId = AnimationStateRegistry.getLastAnimId().get(
                    uuid
                );
                if (!animId.equals(lastId)) {
                    boolean success = BondageAnimationManager.playAnimation(
                        player,
                        animId
                    );
                    if (success) {
                        AnimationStateRegistry.getLastAnimId().put(
                            uuid,
                            animId
                        );
                    }
                }
            }
        } else if (wasTied) {
            // Was tied, now free - stop all animations
            if (GltfAnimationApplier.hasActiveState(player)) {
                GltfAnimationApplier.clearV2Animation(player);
            } else {
                BondageAnimationManager.stopAnimation(player);
            }
            AnimationStateRegistry.getLastAnimId().remove(uuid);
        }

        AnimationStateRegistry.getLastTiedState().put(uuid, isTied);
    }

    /**
     * Build animation ID from player's current state (V1 path).
     */
    private static String buildAnimationId(
        Player player,
        PlayerBindState state
    ) {
        ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
        PoseType poseType = PoseType.STANDARD;

        if (bind.getItem() instanceof ItemBind itemBind) {
            poseType = itemBind.getPoseType();

            // Human chair mode: override DOG pose to HUMAN_CHAIR (straight limbs)
            poseType = HumanChairHelper.resolveEffectivePose(poseType, bind);
        }

        // Derive bound state from V2 regions (works client-side, synced via capability)
        boolean armsBound = V2EquipmentHelper.isRegionOccupied(
            player,
            BodyRegionV2.ARMS
        );
        boolean legsBound = V2EquipmentHelper.isRegionOccupied(
            player,
            BodyRegionV2.LEGS
        );

        // V1 fallback: if no V2 regions are set but player is tied, derive from ItemBind NBT
        if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {
            armsBound = ItemBind.hasArmsBound(bind);
            legsBound = ItemBind.hasLegsBound(bind);
        }

        boolean isStruggling = state.isStruggling();
        boolean isSneaking = player.isCrouching();
        boolean isMoving =
            player.getDeltaMovement().horizontalDistanceSqr() > 1e-6;

        // Build animation ID with sneak and movement support
        return AnimationIdBuilder.build(
            poseType,
            armsBound,
            legsBound,
            null,
            isStruggling,
            true,
            isSneaking,
            isMoving
        );
    }

    /**
     * Player logout event - cleanup animation data.
     */
    @SubscribeEvent
    public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) {
        if (event.getEntity().level().isClientSide()) {
            UUID uuid = event.getEntity().getUUID();
            AnimationStateRegistry.getLastTiedState().remove(uuid);
            AnimationStateRegistry.getLastAnimId().remove(uuid);
            BondageAnimationManager.cleanup(uuid);
            GltfAnimationApplier.removeTracking(uuid);
        }
    }

    /**
     * World unload event - clear all animation and cache data.
     * FIX: Now also clears client-side caches to prevent memory leaks and stale data.
     */
    @SubscribeEvent
    public static void onWorldUnload(
        net.minecraftforge.event.level.LevelEvent.Unload event
    ) {
        if (event.getLevel().isClientSide()) {
            // Animation state (includes BondageAnimationManager, PendingAnimationManager,
            // DogPoseRenderHandler, MCAAnimationTickCache)
            // AnimationStateRegistry.clearAll() handles GltfAnimationApplier.clearAll() transitively
            AnimationStateRegistry.clearAll();

            // Non-animation client-side caches
            PetBedClientState.clearAll();
            MovementStyleClientState.clearAll();
            com.tiedup.remake.client.state.CollarRegistryClient.clear();
            CellHighlightHandler.clearCache();
            LeashProxyClientHandler.clearAll();
            com.tiedup.remake.client.state.ClientLaborState.clearTask();
            com.tiedup.remake.client.state.ClothesClientCache.clearAll();
            com.tiedup.remake.client.texture.DynamicTextureManager.getInstance().clearAll();

            // C1: Player bind state client instances (prevents stale Player references)
            PlayerBindState.clearClientInstances();

            // C2: Armor stand bondage data (entity IDs are not stable across worlds)
            com.tiedup.remake.entities.armorstand.ArmorStandBondageClientCache.clear();

            // C3: Furniture GLB model cache (resource-backed, also cleared on F3+T reload)
            com.tiedup.remake.v2.furniture.client.FurnitureGltfCache.clear();

            LOGGER.debug(
                "Cleared all animation and cache data due to world unload"
            );
        }
    }
}
