/*
 *  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.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
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 top.r3944realms.eroticdungeongame.api.workspace.Services;
import top.r3944realms.eroticdungeongame.config.EDGConfig;
import top.r3944realms.eroticdungeongame.content.block.blockentity.BaseSeatBlockEntity;
import top.r3944realms.eroticdungeongame.content.entity.IAnchorableEntity;
import top.r3944realms.eroticdungeongame.content.entity.SeatEntity;
import top.r3944realms.eroticdungeongame.core.service.SeatService;
import top.r3944realms.lib39.util.nbt.NBTWriter;

import java.util.Optional;
import java.util.UUID;

@Mixin(LivingEntity.class)
public abstract class MixinLivingEntity extends Entity implements IAnchorableEntity {
    @Unique
    @SuppressWarnings("WrongEntityDataParameterClass")
    private static final EntityDataAccessor<Integer> EDG$DATA_ANCHOR_ID = SynchedEntityData.defineId(LivingEntity.class,
            EntityDataSerializers.INT);
    @Unique
    private static final String EDG$ANCHOR_TAG = "EDG$Anchor";
    @Unique
    @Nullable
    private Entity edg$anchor = null;
    @Unique
    private boolean edg$shouldBeLoaded = false;
    @Unique
    private UUID edg$uuidToBeLoaded;
    public MixinLivingEntity(EntityType<?> entityType, Level level) {
        super(entityType, level);
    }

    @WrapOperation(
            method = "baseTick",
            at = @At(
                    value = "INVOKE",
                    target= "Lnet/minecraft/world/entity/LivingEntity;isInWall()Z"
            )
    )
    private boolean edg$inWall$Warp(LivingEntity instance, Operation<Boolean> original) {
        return Services.WORK_SPACE.tryToDoIfInDeviceAndRet(instance, data -> false, () -> original.call(instance));
    }

    @WrapMethod(
            method = "stopRiding"
    )
    private void edg$redefineStopRiding(Operation<Void> original) {
        LivingEntity livingEntity = LivingEntity.class.cast(this);
        Services.WORK_SPACE.tryToDoIfInDevice(livingEntity,
                data -> {
                    if (data.getDeviceMainBlockPos() == null || !(level().getBlockEntity(data.getDeviceMainBlockPos()) instanceof BaseSeatBlockEntity seatBe) || !seatBe.hasLockCode()) {
                        original.call();
                    } else if (livingEntity instanceof Player player) {
                        Optional<SeatEntity> playerSeat = SeatService.getPlayerSeat(player);
                        if (playerSeat.isEmpty() || playerSeat.get().isRemoved()) {
                            original.call();
                        }
                    }
                },
                original::call
        );
    }

    @Override
    public boolean edg$isAnchored() {
        return edg$getAnchor() != null;
    }

    @Override
    public Entity edg$getAnchor() {
        return edg$anchor;
    }

    @Override
    public Entity edg$getAnchorClientSide() {
        return level().getEntity(entityData.get(EDG$DATA_ANCHOR_ID));
    }

    @Override
    public void edg$setAnchoredTo(@Nullable Entity e) {
        if (!level().isClientSide()) {
            if (e == null) {
                edg$setAnchor(null);

                edg$wasAnchored = false;

                ItemStack stack = new ItemStack(Items.CHAIN, 1);
                ItemEntity en = new ItemEntity(level(), this.position().x(), this.position().y(), this.position().z(),
                        stack);
                en.setDefaultPickUpDelay();
                level().addFreshEntity(en);
            } else
                edg$setAnchor(e);
        }
    }

    @Override
    public void edg$setAnchor(@Nullable Entity e) {
        edg$anchor = e;
    }

    @WrapMethod(method = "defineSynchedData")
    private void edg$onDefineSynchedData(Operation<Void> original) {
        original.call();
        entityData.define(EDG$DATA_ANCHOR_ID, -1);
    }

    @Unique
    boolean edg$wasAnchored;
    @WrapMethod(method = "tick")
    private void edg$onTick(Operation<Void> original) {
        original.call();
        LivingEntity thisEntity = LivingEntity.class.cast(this);
        if(!level().isClientSide()) {
            if(edg$shouldBeLoaded) {
                if(level() instanceof ServerLevel l) {
                    Entity a = l.getEntity(edg$uuidToBeLoaded);

                    edg$setAnchor(a);

                    if(a != null)
                        edg$shouldBeLoaded = false;
                }
            }

            if((edg$anchor == null || edg$anchor.isRemoved()) && edg$wasAnchored) {
                edg$setAnchoredTo(null);
                edg$wasAnchored = false;
            }

            if(edg$anchor != null) {
                edg$wasAnchored = true;
                entityData.set(EDG$DATA_ANCHOR_ID, edg$anchor.getId());

                double minDist = EDGConfig.SERVER.ANCHORING_MAX_CHAIN_LENGTH.get();
                double maxDist = fallDistance > 0.2f ? EDGConfig.SERVER.ANCHORING_MAX_CHAIN_LENGTH.get(): EDGConfig.SERVER.ANCHORING_SUFFOCATION_LENGTH.get();


                if (this.distanceTo(edg$getAnchor()) > minDist) {
                    float distance = distanceTo(edg$getAnchor());

                    double dx = (edg$getAnchor().getX() - getX()) / (double) distance;
                    double dy = (edg$getAnchor().getY() - getY()) / (double) distance;
                    double dz = (edg$getAnchor().getZ() - getZ()) / (double) distance;

                    setDeltaMovement(
                            Math.copySign(dx * dx * (distance / 5D) * .45, dx),
                            Math.copySign(dy * dy * (distance / 5D) * .45, dy),
                            Math.copySign(dz * dz * (distance / 5D) * .45, dz));

                    if(distanceTo(edg$getAnchor()) > maxDist) {
                       //TODO 距离
                    }
                }
            } else
                entityData.set(EDG$DATA_ANCHOR_ID, -1);
        } else if(thisEntity instanceof Player player && edg$getAnchorClientSide() != null) {
            double minDist = EDGConfig.SERVER.ANCHORING_MAX_CHAIN_LENGTH.get();

            if (this.distanceTo(edg$getAnchorClientSide()) > minDist) {
                float distance = distanceTo(edg$getAnchorClientSide());

                double dx = (edg$getAnchorClientSide().getX() - getX()) / (double) distance;
                double dy = (edg$getAnchorClientSide().getY() - getY()) / (double) distance;
                double dz = (edg$getAnchorClientSide().getZ() - getZ()) / (double) distance;

                player.setDeltaMovement(
                        Math.copySign(dx * dx * (distance / 5D) * .45, dx),
                        Math.copySign(dy * dy * (distance / 5D) * .45, dy),
                        Math.copySign(dz * dz * (distance / 5D) * .45, dz));
            }
        }
    }

    @WrapMethod(method = "addAdditionalSaveData")
    private void edg$onAddAdditionalSaveData(CompoundTag compound, @NotNull Operation<Void> original) {
        original.call(compound);
        if (!level().isClientSide()) {
            if (edg$isAnchored())
                NBTWriter.of(compound)
                        .uuid(EDG$ANCHOR_TAG, edg$uuidToBeLoaded);
        }
    }
    @WrapMethod(method = "readAdditionalSaveData")
    private void edg$onReadAdditionalSaveData(CompoundTag compound, @NotNull Operation<Void> original) {
        original.call(compound);
        if (!level().isClientSide()) {
            if(compound.contains(EDG$ANCHOR_TAG)) {
                edg$uuidToBeLoaded = compound.getUUID(EDG$ANCHOR_TAG);
                edg$shouldBeLoaded = true;
            } else
                edg$setAnchor(null);
        }

    }
}
