package fr.koka99cab.spiderman.entity;

import fr.koka99cab.spiderman.grapple.GrappleController;
import fr.koka99cab.spiderman.registry.SpidermanEntityTypes;
import fr.koka99cab.spiderman.registry.SpidermanItems;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.projectile.ItemSupplier;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;

public class GrapplingHookEntity extends Projectile implements ItemSupplier {
	private static final EntityDataAccessor<Boolean> ANCHORED = SynchedEntityData.defineId(GrapplingHookEntity.class, EntityDataSerializers.BOOLEAN);
	private static final double GRAVITY = 0.035D;
	private static final double DRAG = 0.985D;
	private static final double MAX_FLIGHT_DISTANCE = 72.0D;
	private static final int MAX_FLIGHT_TICKS = 80;

	private double traveledDistance;

	public GrapplingHookEntity(EntityType<? extends GrapplingHookEntity> entityType, Level level) {
		super(entityType, level);
		this.noPhysics = true;
		this.setNoGravity(true);
	}

	public GrapplingHookEntity(Level level, Entity owner, Vec3 startPosition, Vec3 velocity) {
		this(SpidermanEntityTypes.GRAPPLING_HOOK, level);
		this.setOwner(owner);
		this.snapTo(startPosition.x, startPosition.y, startPosition.z);
		this.setDeltaMovement(velocity);
	}

	@Override
	protected void defineSynchedData(SynchedEntityData.Builder builder) {
		builder.define(ANCHORED, false);
	}

	@Override
	public void tick() {
		super.tick();
		this.noPhysics = true;
		this.setNoGravity(true);

		if (!this.level().isClientSide() && (this.getOwner() == null || !this.getOwner().isAlive())) {
			this.discard();
			return;
		}

		if (this.isAnchored()) {
			this.setDeltaMovement(Vec3.ZERO);
			return;
		}

		Vec3 velocity = this.getDeltaMovement();
		Vec3 from = this.position();
		Vec3 to = from.add(velocity);
		BlockHitResult hitResult = this.level().clip(new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this));

		if (hitResult.getType() == HitResult.Type.BLOCK) {
			this.anchor(hitResult);
			if (!this.level().isClientSide() && this.getOwner() instanceof ServerPlayer player) {
				GrappleController.attach(player, this, hitResult);
			}
			return;
		}

		this.move(MoverType.SELF, velocity);
		this.traveledDistance += velocity.length();
		this.setDeltaMovement(velocity.scale(DRAG).add(0.0D, -GRAVITY, 0.0D));

		if (!this.level().isClientSide() && this.getOwner() instanceof ServerPlayer player && (this.traveledDistance > MAX_FLIGHT_DISTANCE || this.tickCount > MAX_FLIGHT_TICKS)) {
			GrappleController.expire(player, this);
		}
	}

	public void anchor(BlockHitResult hitResult) {
		this.entityData.set(ANCHORED, true);
		this.noPhysics = true;
		this.setNoGravity(true);
		this.setDeltaMovement(Vec3.ZERO);
		Vec3 hitLocation = hitResult.getLocation();
		this.snapTo(hitLocation.x, hitLocation.y, hitLocation.z);
	}

	public boolean isAnchored() {
		return this.entityData.get(ANCHORED);
	}

	@Override
	public ItemStack getItem() {
		return new ItemStack(SpidermanItems.GRAPPLING_HAND);
	}

	@Override
	public boolean isPickable() {
		return false;
	}

	@Override
	protected void addAdditionalSaveData(ValueOutput output) {
		super.addAdditionalSaveData(output);
	}

	@Override
	protected void readAdditionalSaveData(ValueInput input) {
		super.readAdditionalSaveData(input);
	}
}
