package me.earthme.luminol.afktracking;

import me.earthme.luminol.config.modules.optimizations.AfkEntityTrackingConfig;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Tracks AFK players and reduces entity tracking update frequency for them.
 * Ported from ESU EntityTrackingEfficiency (LGPL-3.0).
 *
 * <p>"AFK" means a player has not produced movement/look input for
 * {@link AfkEntityTrackingConfig#afkThresholdSeconds} seconds. When every
 * viewer that could see an entity is AFK and the entity is outside the
 * {@code alwaysTrackRadius}, the entity's tracking updates are throttled to
 * one out of every {@code updateIntervalTicks} ticks. The moment any viewer
 * becomes active (or moves close) full-rate tracking is restored immediately.
 */
public final class AfkTrackingManager {

    /** Player UUID -> game-time tick of last recorded input. */
    private static final Map<UUID, Long> LAST_ACTIVE = new ConcurrentHashMap<>();
    /** Tracked-entity UUID -> current skip counter. */
    private static final Map<UUID, Integer> SKIP_COUNTER = new ConcurrentHashMap<>();

    private AfkTrackingManager() {}

    /** Record input from a player (called from the movement packet hook). Resets the AFK timer. */
    public static void recordActivity(ServerPlayer player) {
        LAST_ACTIVE.put(player.getUUID(), player.level().getGameTime());
    }

    /**
     * Decide whether this tick's tracking update for {@code tracked} should be skipped.
     *
     * @return {@code true} if the caller should NOT call {@code serverEntity.sendChanges()} this tick.
     */
    public static boolean shouldThrottle(Entity tracked) {
        if (!AfkEntityTrackingConfig.enabled || AfkEntityTrackingConfig.updateIntervalTicks <= 1) return false;
        if (tracked == null) return false;
        // Never throttle players themselves: their own client needs accurate state.
        if (tracked instanceof ServerPlayer) return false;
        if (!(tracked.level() instanceof ServerLevel level)) return false;

        final long now = level.getGameTime();
        final long afkThresholdTicks = AfkEntityTrackingConfig.afkThresholdSeconds * 20L;
        final double alwaysTrackSq = AfkEntityTrackingConfig.alwaysTrackRadius * AfkEntityTrackingConfig.alwaysTrackRadius;

        // If any potential viewer is active, or is AFK but close, do not throttle.
        for (ServerPlayer viewer : level.players()) {
            long lastActive = LAST_ACTIVE.getOrDefault(viewer.getUUID(), 0L);
            if (now - lastActive < afkThresholdTicks) {
                return false; // at least one active viewer
            }
            if (viewer.distanceToSqr(tracked) < alwaysTrackSq) {
                return false; // AFK but close enough to need full-rate updates
            }
        }

        // Every viewer is AFK and far away: skip until the counter rolls over.
        int skip = SKIP_COUNTER.merge(tracked.getUUID(), 1, Integer::sum);
        if (skip < AfkEntityTrackingConfig.updateIntervalTicks) {
            return true;
        }
        SKIP_COUNTER.remove(tracked.getUUID());
        return false;
    }

    public static void onPlayerQuit(ServerPlayer player) {
        LAST_ACTIVE.remove(player.getUUID());
        SKIP_COUNTER.remove(player.getUUID());
    }
}
