package com.hypixel.hytale.server.npc.systems;

import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;

import javax.annotation.Nonnull;

/**
 * Per-NPC state for distance-based tick skipping optimization.
 * Caches player distance (100ms TTL) and accumulates skipped delta time.
 */
public final class TickSkipState {
    private static final float DISTANCE_CACHE_TTL = 0.1f; // 100ms
    private static final float MIN_DISTANCE = 32.0f;
    private static final float MAX_DISTANCE = 96.0f;
    private static final float DISTANCE_RANGE = MAX_DISTANCE - MIN_DISTANCE;
    private static final int MAX_TICK_INTERVAL = 20;

    private static final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> PLAYER_SPATIAL =
            EntityModule.get().getPlayerSpatialResourceType();

    private float accumulated;
    private float cachedDistanceSq;
    private float distanceCacheAge;

    public void add(float dt) {
        this.accumulated += dt;
        this.distanceCacheAge += dt;
    }

    public float get() {
        return this.accumulated;
    }

    public float consume() {
        float value = this.accumulated;
        this.accumulated = 0f;
        return value;
    }

    /**
     * Updates cached distance if stale, then checks if tick should be processed.
     */
    public boolean shouldProcess(@Nonnull Vector3d position, @Nonnull Store<EntityStore> store, float dt) {
        // Refresh distance cache if stale
        if (this.distanceCacheAge >= DISTANCE_CACHE_TTL) {
            this.cachedDistanceSq = getDistanceSquaredToNearestPlayer(position, store);
            this.distanceCacheAge = 0f;
        }

        int interval = calculateTickInterval(this.cachedDistanceSq);
        if (interval <= 1) {
            return true;
        }
        return this.accumulated >= dt * interval;
    }

    private static float getDistanceSquaredToNearestPlayer(Vector3d position, Store<EntityStore> store) {
        SpatialResource<Ref<EntityStore>, EntityStore> spatial = store.getResource(PLAYER_SPATIAL);
        if (spatial == null) {
            return 0f;
        }
        Ref<EntityStore> closest = spatial.getSpatialStructure().closest(position);
        if (closest == null || !closest.isValid()) {
            return Float.MAX_VALUE;
        }
        TransformComponent transform = store.getComponent(closest, TransformComponent.getComponentType());
        if (transform == null) {
            return Float.MAX_VALUE;
        }
        return (float) position.distanceSquaredTo(transform.getPosition());
    }

    private static int calculateTickInterval(float distanceSq) {
        float distance = (float) Math.sqrt(distanceSq);
        if (distance <= MIN_DISTANCE) {
            return 1;
        }
        if (distance >= MAX_DISTANCE) {
            return MAX_TICK_INTERVAL;
        }
        float t = (distance - MIN_DISTANCE) / DISTANCE_RANGE;
        return 1 + (int) ((MAX_TICK_INTERVAL - 1) * t * t);
    }
}
