package top.r3944realms.lib39.mixin.callback;

import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.platform.WindowEventHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.server.Services;
import net.minecraft.server.WorldStem;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.util.thread.ReentrantBlockableEventLoop;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import top.r3944realms.lib39.api.callback.MinecraftSetUpServiceCallback;
import top.r3944realms.lib39.api.callback.client.ClientWorldCallback;

import javax.annotation.Nullable;

/**
 * The type Mixin minecraft.
 */
@Mixin(Minecraft.class)
public abstract class MixinMinecraft extends ReentrantBlockableEventLoop<Runnable> implements WindowEventHandler {
    /**
     * The Level.
     */
    @Shadow @Nullable public ClientLevel level;

    /**
     * Instantiates a new Mixin minecraft.
     *
     * @param name the name
     */
    public MixinMinecraft(String name) {
        super(name);
    }

    /**
     * Set level callback.
     *
     * @param levelClient the level client
     * @param original    the original
     */
    @WrapMethod(method = "setLevel")
    public void setLevel$callback(ClientLevel levelClient, Operation<Void> original) {
        if (levelClient != null) ClientWorldCallback.UNLOAD.invoker().onWorldUnload(levelClient);
        original.call(levelClient);
    }

    /**
     * Clear level callback.
     *
     * @param original the original
     */
    @WrapMethod(method = "clearLevel()V")
    public void clearLevel$callback(Operation<Void> original) {
        if (level != null) ClientWorldCallback.UNLOAD.invoker().onWorldUnload(level);
        original.call();
    }

    /**
     * Set level setup.
     *
     * @param levelClient the level client
     * @param ci          the ci
     * @param services    the services
     */
    @Inject(
            method = "setLevel",
            at = @At(
                    value = "INVOKE",
                    target = "Lnet/minecraft/server/players/GameProfileCache;setUsesAuthentication(Z)V",
                    shift = At.Shift.AFTER
            )
    )
    public void setLevel$setup(ClientLevel levelClient, CallbackInfo ci,
                               @Local(ordinal = 0) Services services) {
        MinecraftSetUpServiceCallback.EVENT.invoker().load(services,this);
    }

    /**
     * Do world load setup.
     *
     * @param levelId        the level id
     * @param level          the level
     * @param packRepository the pack repository
     * @param worldStem      the world stem
     * @param newWorld       the new world
     * @param ci             the ci
     * @param services       the services
     */
    @Inject(
            method = "doWorldLoad",
            at = @At(
                    value = "INVOKE",
                    target = "Lnet/minecraft/server/players/GameProfileCache;setUsesAuthentication(Z)V",
                    shift = At.Shift.AFTER
            )
    )
    public void doWorldLoad$setup(String levelId, LevelStorageSource.LevelStorageAccess level, PackRepository packRepository, WorldStem worldStem, boolean newWorld, CallbackInfo ci,
                                  @Local(ordinal = 0) Services services) {
        MinecraftSetUpServiceCallback.EVENT.invoker().load(services,this);
    }
}

