/*
 *  Copyright 2025-2026 R3944Realms
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package top.r3944realms.eroticdungeongame.mixin.minecraft;

import com.google.common.collect.Maps;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.brigadier.ParseResults;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.PacketUtils;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.vehicle.Boat;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import top.r3944realms.eroticdungeongame.client.ClientNPCManager;
import top.r3944realms.eroticdungeongame.client.EDGKeyBindings;
import top.r3944realms.eroticdungeongame.content.entity.SeatEntity;
import top.r3944realms.eroticdungeongame.content.entity.npc.NPCRemotePlayer;
import top.r3944realms.eroticdungeongame.util.IEDGClientPacketListener;

import javax.annotation.Nullable;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

@SuppressWarnings("ConstantValue")
@Mixin(ClientPacketListener.class)
public abstract class MixinClientPacketListener implements IEDGClientPacketListener {
    @Unique
    private final Map<UUID, PlayerInfo> edg$npcPlayerInfoMap = Maps.newHashMap();
    @Unique
    private final Set<PlayerInfo> edg$listedNPCPlayers = new ReferenceOpenHashSet<>();
    @Shadow
    @Final
    private Minecraft minecraft;

    @Shadow @Nullable public abstract PlayerInfo getPlayerInfo(String name);

    @Shadow @Nullable public abstract PlayerInfo getPlayerInfo(UUID uniqueId);

    @Shadow @Final private Set<PlayerInfo> listedPlayers;
    @Shadow private ClientLevel level;

    @Shadow public abstract void handleSetEntityMotion(ClientboundSetEntityMotionPacket packet);

    @Shadow public abstract void handleSetEntityData(ClientboundSetEntityDataPacket packet);

    @Shadow public abstract void handleUpdateAttributes(ClientboundUpdateAttributesPacket packet);

    @Shadow public abstract void handleSetEquipment(ClientboundSetEquipmentPacket packet);

    @Shadow public abstract void handleSetEntityPassengersPacket(ClientboundSetPassengersPacket packet);

    @Shadow public abstract void handleEntityLinkPacket(ClientboundSetEntityLinkPacket packet);

    @Shadow protected abstract ParseResults<SharedSuggestionProvider> parseCommand(String command);

    @Shadow public abstract void handleRotateMob(ClientboundRotateHeadPacket packet);

    @Shadow public abstract void handleMoveEntity(ClientboundMoveEntityPacket packet);

    @Shadow public abstract void handleTeleportEntity(ClientboundTeleportEntityPacket packet);

    @Unique
    private Entity edg$cachedMount = null;

    @Inject(method = "handleSetEntityPassengersPacket", locals = LocalCapture.CAPTURE_FAILEXCEPTION,
            at = @At(value = "INVOKE_ASSIGN", ordinal = 0, shift = At.Shift.AFTER, target = "Lnet/minecraft/client/multiplayer/ClientLevel;getEntity(I)Lnet/minecraft/world/entity/Entity;"))
    private void edg$cacheMountedEntity(ClientboundSetPassengersPacket packet, CallbackInfo ci, Entity mounted) {
        edg$cachedMount = mounted;
    }

    @ModifyVariable(method = "handleSetEntityPassengersPacket", index = 9,
            at = @At(value = "INVOKE_ASSIGN", ordinal = 0, shift = At.Shift.AFTER, target = "Lnet/minecraft/network/chat/Component;translatable(Ljava/lang/String;[Ljava/lang/Object;)Lnet/minecraft/network/chat/MutableComponent;"))
    private Component edg$modifyMountMessage(Component old) {
        if(edg$cachedMount != null && edg$cachedMount instanceof SeatEntity) {
            return Component.translatable("mount.eroticdungeongame.seat.onboard", EDGKeyBindings.KEY_QUIT.getTranslatedKeyMessage());
        } else return old;
    }
    @WrapMethod(
            method = "handleAddPlayer"
    )
    private void edg$handleAddPlayer(ClientboundAddPlayerPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        PlayerInfo playerinfo = this.getPlayerInfo(packet.getPlayerId());
        if (playerinfo == null) {
            PlayerInfo npcPlayerInfo = getNPCPlayerInfo(packet.getPlayerId());
            if (npcPlayerInfo == null) {
                ClientNPCManager.cacheNPCIfNeeded(packet);
                original.call(packet);
            } else {
                edg$createNPCPlayer(packet, npcPlayerInfo);
            }
        } else original.call(packet);
    }

    @WrapMethod(
            method = "handleSetEntityData"
    )
    private void edg$handleSetEntityData(ClientboundSetEntityDataPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = this.level.getEntity(packet.id());
        if (entity == null) {
            ClientNPCManager.cacheEntityDataIfNeeded(packet);
            original.call(packet);
        } else original.call(packet);
    }

    @WrapMethod(
            method = "handleSetEntityMotion"
    )
    private void edg$handleSetEntityMotion(ClientboundSetEntityMotionPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = this.level.getEntity(packet.getId());
        if (entity == null) {
            ClientNPCManager.cacheMotionIfNeeded(packet);
            original.call(packet);
        } else original.call(packet);
    }

    @WrapMethod(
            method = "handleTeleportEntity"
    )
    private void edg$handleTeleportEntity(ClientboundTeleportEntityPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = this.level.getEntity(packet.getId());
        if (entity == null) {
            ClientNPCManager.cacheTeleportEntityIfNeeded(packet);
            original.call(packet);
        } else original.call(packet);
    }

    @WrapMethod(
            method = "handleMoveEntity"
    )
    private void edg$handleMoveEntity(ClientboundMoveEntityPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = packet.getEntity(this.level);
        if (entity == null) {
            ClientNPCManager.cacheMoveEntityIfNeeded(packet);
            original.call(packet);
        } else original.call(packet);
    }

    @WrapMethod(
            method = "handleUpdateAttributes"
    )
    private void edg$handleUpdateAttributes(ClientboundUpdateAttributesPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = this.level.getEntity(packet.getEntityId());
        if (entity == null) {
            ClientNPCManager.cacheAttributesIfNeeded(packet);
            original.call(packet);
        } else original.call(packet);
    }

    @WrapMethod(
            method = "handleSetEntityPassengersPacket"
    )
    private void edg$handleSetEntityPassengersPacket(ClientboundSetPassengersPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = this.level.getEntity(packet.getVehicle());
        if (entity == null) {
            ClientNPCManager.cachePassengersIfNeeded(packet);
            original.call(packet);
        } else {
            for(int i : packet.getPassengers()) {
                Entity entity1 = this.level.getEntity(i);
                if (entity1 == null) {
                    ClientNPCManager.cachePassengersIfNeeded(packet);
                    break;
                }
            }
            original.call(packet);
        }
    }

    @WrapMethod(
            method = "handleSetEquipment"
    )
    private void edg$handleSetEquipment(ClientboundSetEquipmentPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = this.level.getEntity(packet.getEntity());
        if (entity == null) {
            ClientNPCManager.cacheEquipmentIfNeeded(packet);
            original.call(packet);
        } else original.call(packet);
    }

    @WrapMethod(
            method = "handleEntityLinkPacket"
    )
    private void edg$handleEntityLinkPacket(ClientboundSetEntityLinkPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = this.level.getEntity(packet.getSourceId());
        if (entity == null) {
            ClientNPCManager.cacheLinkIfNeeded(packet);
            original.call(packet);
        } else original.call(packet);
    }

    @WrapMethod(
            method = "handleRotateMob"
    )
    private void edg$handleRotateMob(ClientboundRotateHeadPacket packet, Operation<Void> original){
        PacketUtils.ensureRunningOnSameThread(packet, ClientPacketListener.class.cast(this), this.minecraft);
        Entity entity = packet.getEntity(this.level);
        if (entity == null) {
            ClientNPCManager.cacheHeadRotationIfNeeded(packet);
            original.call(packet);
        } else original.call(packet);
    }

    @Override
    public void edg$createNPCPlayer(@NotNull ClientboundAddPlayerPacket packet, @NotNull PlayerInfo npcPlayerInfo) {
        double d0 = packet.getX();
        double d1 = packet.getY();
        double d2 = packet.getZ();
        float f = (float)(packet.getyRot() * 360) / 256.0F;
        float f1 = (float)(packet.getxRot() * 360) / 256.0F;
        int i = packet.getEntityId();
        assert this.minecraft.level != null;
        NPCRemotePlayer npcRemotePlayer = new NPCRemotePlayer(this.minecraft.level, npcPlayerInfo.getProfile());
        npcRemotePlayer.setUUID(packet.getPlayerId());
        npcRemotePlayer.setId(i);
        npcRemotePlayer.syncPacketPositionCodec(d0, d1, d2);
        npcRemotePlayer.absMoveTo(d0, d1, d2, f, f1);
        npcRemotePlayer.setOldPosAndRot();
        this.level.addPlayer(i, npcRemotePlayer);
    }

    @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"})
    @WrapOperation(
            method = "handlePlayerInfoUpdate",
            at = @At(
                    value = "INVOKE",
                    target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;"
            )
    )
    private<V> V edg$handlePlayerInfoUpdate(Map<?,?> instance, Object object, @NotNull Operation<V> original) {
        V call = original.call(instance, object);
        if (call == null) {
           call = (V) getNPCPlayerInfoMap().get(object);
        }
        return call;
    }

    @SuppressWarnings("AddedMixinMembersNamePattern")
    @Override
    public Map<UUID, PlayerInfo> getNPCPlayerInfoMap() {
        return edg$npcPlayerInfoMap;
    }

    @SuppressWarnings("AddedMixinMembersNamePattern")
    @Override
    public Set<PlayerInfo> getListedNPCPlayers() {
        return listedPlayers;
    }

    @Override
    public void edg$setEntityMotion(ClientboundSetEntityMotionPacket packet) {
        handleSetEntityMotion(packet);
    }

    @Override
    public void edg$setEntityData(ClientboundSetEntityDataPacket packet) {
        handleSetEntityData(packet);
    }

    @Override
    public void edg$updateAttributes(ClientboundUpdateAttributesPacket packet) {
        handleUpdateAttributes(packet);
    }

    @Override
    public void edg$setEquipment(ClientboundSetEquipmentPacket packet) {
        handleSetEquipment(packet);
    }

    @Override
    public void edg$setPassengers(ClientboundSetPassengersPacket packet) {
        handleSetEntityPassengersPacket(packet);
    }

    @Override
    public void edg$setEntityLink(ClientboundSetEntityLinkPacket packet) {
        handleEntityLinkPacket(packet);
    }

    @Override
    public void edg$rotateHead(ClientboundRotateHeadPacket packet) {
        handleRotateMob(packet);
    }

    @Override
    public void edg$moveEntity(ClientboundMoveEntityPacket packet) {
        handleMoveEntity(packet);
    }

    @Override
    public void edg$teleportEntity(ClientboundTeleportEntityPacket packet) {
        handleTeleportEntity(packet);
    }
}
