package land.chipmunk.chipmunkmod.mixin;

import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import io.netty.channel.ChannelFutureListener;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.listeners.Listener;
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
import land.chipmunk.chipmunkmod.modules.SelfCare;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.network.Connection;
import net.minecraft.network.DisconnectionDetails;
import net.minecraft.network.PacketListener;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import net.minecraft.network.protocol.game.ServerboundCommandSuggestionPacket;
import net.minecraft.resources.Identifier;
import net.minecraft.sounds.SoundEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
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.callback.CallbackInfo;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Mixin(Connection.class)
public class ConnectionMixin {
    @Unique
    private static final double MAX_PARTICLES_PER_PACKET = 1000;
    @Unique
    private static final Pattern CUSTOM_PITCH_PATTERN = Pattern.compile(".*\\.pitch\\.(.*)");

    // handle packet "genericsFtw" such advanced
    @Inject(method = "genericsFtw", at = @At("HEAD"), cancellable = true)
    private static <T extends PacketListener> void genericsFtw (
            final Packet<@NotNull T> packet,
            final PacketListener packetListener,
            final CallbackInfo ci
    ) {
        for (final Listener listener : ListenerManager.listeners) {
            listener.packetReceived(packet);
        }

        final Minecraft client = Minecraft.getInstance();

        // this check is very easy to bypass in 2025
        if (
                packet instanceof final ClientboundLevelParticlesPacket t_packet
                        && t_packet.getCount() > MAX_PARTICLES_PER_PACKET
        ) {
            ci.cancel();
        } else if (packet instanceof final ClientboundSoundPacket t_packet) {
            final SoundEvent soundEvent = t_packet.getSound().value();

            final Identifier sound = soundEvent.location();

            final Matcher matcher = CUSTOM_PITCH_PATTERN.matcher(sound.getPath());

            if (!matcher.find()) return;

            try {
                final String stringPitch = matcher.group(1);

                final float pitch = Float.parseFloat(stringPitch);

                final ClientLevel world = client.level;

                if (world == null) return;

                final SoundEvent newSound = SoundEvent.createVariableRangeEvent(
                        Identifier.fromNamespaceAndPath(
                                sound.getNamespace(),
                                sound.getPath().substring(0, sound.getPath().length() - (".pitch." + stringPitch).length())
                        )
                );

                client.execute(() -> world.playSeededSound(
                        client.player,

                        t_packet.getX(),
                        t_packet.getY(),
                        t_packet.getZ(),

                        newSound,
                        t_packet.getSource(),

                        t_packet.getVolume(),
                        pitch,

                        t_packet.getSeed()
                ));

                ci.cancel();
                return;
            } catch (final NumberFormatException e) {
                ChipmunkMod.LOGGER.error("Failed to parse custom pitch", e);
            }

            if (t_packet.getVolume() == 1 && sound.getPath().equals("entity.enderman.scream")) {
                ci.cancel();
            }
        }
    }

    @WrapMethod(method = "send(Lnet/minecraft/network/protocol/Packet;Lio/netty/channel/ChannelFutureListener;Z)V")
    private void send (
            final Packet<?> packet,
            final @Nullable ChannelFutureListener channelFutureListener,
            final boolean flush,
            final Operation<Void> original
    ) {
        if (
                packet instanceof final ServerboundCommandSuggestionPacket t_packet
                        && t_packet.getCommand().length() > 2048
        ) return;

        for (final Listener listener : ListenerManager.listeners) {
            listener.packetSent(packet);
        }

        original.call(packet, channelFutureListener, flush);
    }

    @Inject(method = "disconnect(Lnet/minecraft/network/DisconnectionDetails;)V", at = @At("TAIL"))
    private void disconnect (final DisconnectionDetails disconnectionInfo, final CallbackInfo ci) {
        SelfCare.INSTANCE.cleanup();
    }
}
