package com.r3944realms.leashedplayer.modInterface;

import com.r3944realms.leashedplayer.LeashedPlayer;
import com.r3944realms.leashedplayer.content.criteriaTriggers.ModCriteriaTriggers;
import com.r3944realms.leashedplayer.content.entities.LeashRopeArrow;
import com.r3944realms.leashedplayer.network.client.UpdatePlayerMovement;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.network.PacketDistributor;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface PlayerLeashable extends Leashable {

    static <E extends Entity & Leashable> void tickLeash(ServerLevel level, E p_entity) {
        Leashable.LeashData leashable$leashdata = p_entity.getLeashData();
        if (leashable$leashdata != null && leashable$leashdata.delayedLeashInfo != null) {
            restoreLeashFromSave(p_entity, leashable$leashdata);
        }

        if (leashable$leashdata != null && leashable$leashdata.leashHolder != null) {
            if (!p_entity.isAlive() || !leashable$leashdata.leashHolder.isAlive()) {
                if (level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
                    p_entity.dropLeash();
                } else {
                    p_entity.removeLeash();
                }
            }

            Entity entity = p_entity.getLeashHolder();
            if (entity != null && entity.level() == p_entity.level()) {
                double d0 = p_entity.leashDistanceTo(entity);
                p_entity.whenLeashedTo(entity);
                int keepLeashTick = 0;
                if(entity instanceof IEntityLeadExtension leadExtension) {
                    keepLeashTick = leadExtension.getKeepLeashTick();
                }
                if (d0 > p_entity.leashSnapDistance() && keepLeashTick == 0) {
                    level.playSound(null, entity.getX(), entity.getY(), entity.getZ(), SoundEvents.LEAD_BREAK, SoundSource.NEUTRAL, 1.0F, 1.0F);
                    p_entity.leashTooFarBehaviour();

                } else if (d0 > p_entity.leashElasticDistance() - entity.getBbWidth() - p_entity.getBbWidth()
                        && p_entity.checkElasticInteractions(entity, leashable$leashdata)) {
                    p_entity.onElasticLeashPull();
                } else {
                    p_entity.closeRangeLeashBehaviour(entity);
                }

                p_entity.setYRot((float)(p_entity.getYRot() - leashable$leashdata.angularMomentum));
                leashable$leashdata.angularMomentum = leashable$leashdata.angularMomentum * Leashable.angularFriction(p_entity);
            }
        }
    }
    @Override
    default boolean checkElasticInteractions(Entity p_entity, Leashable.@NotNull LeashData p_418158_) {
        boolean flag = p_entity.supportQuadLeashAsHolder() && this.supportQuadLeash();
        List<Leashable.Wrench> list = computeElasticInteraction(
                (Entity & Leashable)this,
                p_entity,
                flag ? SHARED_QUAD_ATTACHMENT_POINTS : ENTITY_ATTACHMENT_POINT,
                flag ? SHARED_QUAD_ATTACHMENT_POINTS : LEASHER_ATTACHMENT_POINT
        );
        if (list.isEmpty()) {
            return false;
        } else {
            Leashable.Wrench leashable$wrench = Leashable.Wrench.accumulate(list).scale(flag ? 0.25 : 1.0);
            p_418158_.angularMomentum = p_418158_.angularMomentum + 10.0 * leashable$wrench.torque();

            Entity applyMovementEntity = ((Entity & Leashable)this).isPassenger() ? ((Entity & Leashable)this).getVehicle() : ((Entity & Leashable)this);
            Vec3 vec3 = Leashable.getHolderMovement(((Entity & Leashable)this)).subtract(((Entity)this).getKnownMovement());
            if (applyMovementEntity != null) {
                Vec3 add = leashable$wrench.force().multiply(AXIS_SPECIFIC_ELASTICITY).add(vec3.scale(0.11));
                if (applyMovementEntity instanceof ServerPlayer player) {
                    PacketDistributor.sendToPlayer(player,new UpdatePlayerMovement(UpdatePlayerMovement.Operation.ADD, add.x, add.y, add.z));
                } else applyMovementEntity.addDeltaMovement(add);

            }
            return true;
        }
    }
    private static <E extends Entity & Leashable> List<Leashable.Wrench> computeElasticInteraction(
            E p_418297_, Entity p_418456_, List<Vec3> p_418351_, List<Vec3> p_418397_
    ) {
        double d0 = p_418297_.leashElasticDistance();
        Vec3 vec3 = Leashable.getHolderMovement(p_418297_);
        float f = p_418297_.getYRot() * (float) (Math.PI / 180.0);
        Vec3 vec31 = new Vec3(p_418297_.getBbWidth(), p_418297_.getBbHeight(), p_418297_.getBbWidth());
        float f1 = p_418456_.getYRot() * (float) (Math.PI / 180.0);
        Vec3 vec32 = new Vec3(p_418456_.getBbWidth(), p_418456_.getBbHeight(), p_418456_.getBbWidth());
        List<Leashable.Wrench> list = new ArrayList<>();

        for (int i = 0; i < p_418351_.size(); i++) {
            Vec3 vec33 = p_418351_.get(i).multiply(vec31).yRot(-f);
            Vec3 vec34 = p_418297_.position().add(vec33);
            Vec3 vec35 = p_418397_.get(i).multiply(vec32).yRot(-f1);
            Vec3 vec36 = p_418456_.position().add(vec35);
            Leashable.computeDampenedSpringInteraction(vec36, vec34, d0, vec3, vec33).ifPresent(list::add);
        }

        return list;
    }
    @Override
    default double leashElasticDistance() {
        if (this instanceof IEntityLeadExtension entityLeadExtension) {
            return entityLeadExtension.getLeashLength();
        }
        return 6.0;
    }

    @Override
    default double leashSnapDistance() {
        if (this instanceof IEntityLeadExtension entityLeadExtension) {
            return entityLeadExtension.getLeashLength() * LeashedPlayer.M1();
        }
        return 12.0;
    }

    @Override
    default void leashTooFarBehaviour() {
        boolean shouldDrop = !(this instanceof LeashRopeArrow);
        Leashable.dropLeash((Entity & Leashable) this, true, shouldDrop);
    }


    static <E extends Entity & Leashable> void restoreLeashFromSave(E p_entity, Leashable.LeashData leashData) {
        if (leashData.delayedLeashInfo != null && p_entity.level() instanceof ServerLevel serverlevel) {
            Optional<UUID> optional1 = leashData.delayedLeashInfo.left();
            Optional<BlockPos> optional = leashData.delayedLeashInfo.right();
            if (optional1.isPresent()) {
                Entity entity = serverlevel.getEntity(optional1.get());
                if (entity != null) {
                    setLeashedTo(p_entity, entity, true);
                    return;
                }
            } else if (optional.isPresent()) {
                setLeashedTo(p_entity, LeashFenceKnotEntity.getOrCreateKnot(serverlevel, optional.get()), true);
                return;
            }

            if (p_entity.tickCount > 100) {
                p_entity.spawnAtLocation(serverlevel, Items.LEAD);
                p_entity.setLeashData(null);
            }
        }
    }

    @Override
    default void writeLeashData(@NotNull ValueOutput valueOutput, @org.jetbrains.annotations.Nullable Leashable.LeashData leashData) {
        valueOutput.storeNullable("Pl$LeashData", Leashable.LeashData.CODEC, leashData);
    }

    @Override
    default void readLeashData(ValueInput valueInput) {
        LeashData leashData = valueInput.read("Pl$LeashData", Leashable.LeashData.CODEC).orElse(null);
        if (getLeashData() != null && leashData == null) {
           this.removeLeash();
        }
        this.setLeashData(leashData);
    }

    /**
     * 设置栓绳数据
     * @param pLeashHolder 拴绳持有者
     * @param pBroadcastPacket 是否广播包
     */
    @Override
    default void setLeashedTo(@NotNull Entity pLeashHolder, boolean pBroadcastPacket) {
        setLeashedTo((Entity & Leashable)this, pLeashHolder, pBroadcastPacket);
        if(this instanceof ServerPlayer) {
            ModCriteriaTriggers.LEASH_PLAYER_TRIGGER.get().trigger((ServerPlayer) this, pLeashHolder);
        }
    }
    static <E extends Entity & Leashable> void setLeashedTo(E pEntity, Entity pLeashHolder, boolean pBroadcastPacket) {
        LeashData leashable$leashdata = pEntity.getLeashData();
        if (leashable$leashdata == null) {
            leashable$leashdata = new LeashData(pLeashHolder);
            pEntity.setLeashData(leashable$leashdata);
        } else {
            leashable$leashdata.setLeashHolder(pLeashHolder);
        }

        if (pBroadcastPacket && pEntity.level() instanceof ServerLevel serverlevel) {
            serverlevel.getChunkSource().broadcast(pEntity, new ClientboundSetEntityLinkPacket(pEntity, pLeashHolder));
        }
        //这边覆写去掉了乘坐相关的逻辑，即乘坐状态下也可以正常被栓住，不影响其乘坐状态

    }

    static Entity getLeashDataEntityOrThrown(@NotNull ServerPlayer serverPlayer ,@NotNull ServerLevel serverLevel) throws Exception {
        Entity entity = getLeashDataEntity(serverPlayer, serverLevel);
        if(entity == null) throw new Exception("invalid");
        else return entity;
    }
    @Nullable
    static Entity getLeashDataEntity(@NotNull Entity entity, @NotNull ServerLevel level) {
        if (entity instanceof Leashable leashable) {
            LeashData data = leashable.getLeashData();
            if (data != null) {
                return PlayerLeashable.getLeashDataEntity(data, level);
            }
        }
        return null;
    }
    @Nullable
    static Entity getLeashDataEntity(@NotNull Leashable.LeashData leashData, @NotNull ServerLevel level) {
        if(leashData.delayedLeashInfo != null) {
            Optional<UUID> UUID = leashData.delayedLeashInfo.left();
            Optional<BlockPos> BlockPos = leashData.delayedLeashInfo.right();
            if (UUID.isPresent()) {
                return level.getEntity(UUID.get());
            } else return BlockPos.map(pos -> LeashFenceKnotEntity.getOrCreateKnot(level, pos)).orElse(null);
        }
        else if(leashData.leashHolder != null) {
            return leashData.leashHolder;
        }
        else if(leashData.delayedLeashHolderId != 0) {
            return level.getEntity(leashData.delayedLeashHolderId);
        }
        else return null;
    }

    static Entity getLeashDataEntityOrThrown(@NotNull Leashable.LeashData leashDataFromEntityData, @NotNull ServerLevel level) throws Exception {
        if(leashDataFromEntityData.delayedLeashInfo != null) {
            Optional<UUID> UUID = leashDataFromEntityData.delayedLeashInfo.left();
            Optional<BlockPos> BlockPos = leashDataFromEntityData.delayedLeashInfo.right();
            if (UUID.isPresent()) {
                return level.getEntity(UUID.get());
            } else if(BlockPos.isPresent()) {
                return LeashFenceKnotEntity.getOrCreateKnot(level, BlockPos.get());
            } else {
                throw new Exception("invalid delayedLeashInfo");
            }
        }
        else if(leashDataFromEntityData.leashHolder != null) {
            return leashDataFromEntityData.leashHolder;
        }
        else if(leashDataFromEntityData.delayedLeashHolderId != 0) {
            Entity entity = level.getEntity(leashDataFromEntityData.delayedLeashHolderId);
            if(entity == null) {
                throw new Exception("Not found Entity. maybe the pId is invalid");
            }
            return entity;
        }
        else {
            throw new Exception("invalid leash data");
        }
    }
    @Nullable
    static Entity getLeashFenceKnotEntity(ServerLevel pLevel, BlockPos pPos) {
        int i = pPos.getX();
        int j = pPos.getY();
        int k = pPos.getZ();

        for (LeashFenceKnotEntity leashfenceknotentity : pLevel.getEntitiesOfClass(
                LeashFenceKnotEntity.class, new AABB((double)i - 1.0, (double)j - 1.0, (double)k - 1.0, (double)i + 1.0, (double)j + 1.0, (double)k + 1.0)
        )) {
            if (leashfenceknotentity.getPos().equals(pPos)) {
                return leashfenceknotentity;
            }
        }
        return null;
    }
    static Entity createLeashKnotFence(ServerLevel pLevel, BlockPos pPos) {
        LeashFenceKnotEntity leashfenceknotentity = new LeashFenceKnotEntity(pLevel, pPos);
        pLevel.addFreshEntity(leashfenceknotentity);
        return leashfenceknotentity;
    }
}
