package net.minecraft.world.entity.monster.warden;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;

public class WardenSpawnTracker {
    public static final Codec<WardenSpawnTracker> CODEC = RecordCodecBuilder.create(
        instance -> instance.group(
                ExtraCodecs.NON_NEGATIVE_INT
                    .fieldOf("ticks_since_last_warning")
                    .orElse(0)
                    .forGetter(wardenSpawnTracker -> wardenSpawnTracker.ticksSinceLastWarning),
                ExtraCodecs.NON_NEGATIVE_INT.fieldOf("warning_level").orElse(0).forGetter(wardenSpawnTracker -> wardenSpawnTracker.warningLevel),
                ExtraCodecs.NON_NEGATIVE_INT.fieldOf("cooldown_ticks").orElse(0).forGetter(wardenSpawnTracker -> wardenSpawnTracker.cooldownTicks)
            )
            .apply(instance, WardenSpawnTracker::new)
    );
    public static final int MAX_WARNING_LEVEL = 4;
    private static final double PLAYER_SEARCH_RADIUS = 16.0;
    private static final int WARNING_CHECK_DIAMETER = 48;
    private static final int DECREASE_WARNING_LEVEL_EVERY_INTERVAL = 12000;
    private static final int WARNING_LEVEL_INCREASE_COOLDOWN = 200;
    public int ticksSinceLastWarning;
    private int warningLevel;
    public int cooldownTicks;

    public WardenSpawnTracker(int ticksSinceLastWarning, int warningLevel, int cooldownTicks) {
        this.ticksSinceLastWarning = ticksSinceLastWarning;
        this.warningLevel = warningLevel;
        this.cooldownTicks = cooldownTicks;
    }

    public WardenSpawnTracker() {
        this(0, 0, 0);
    }

    public void tick() {
        if (this.ticksSinceLastWarning >= 12000) {
            this.decreaseWarningLevel();
            this.ticksSinceLastWarning = 0;
        } else {
            this.ticksSinceLastWarning++;
        }

        if (this.cooldownTicks > 0) {
            this.cooldownTicks--;
        }
    }

    public void reset() {
        this.ticksSinceLastWarning = 0;
        this.warningLevel = 0;
        this.cooldownTicks = 0;
    }

    public static OptionalInt tryWarn(ServerLevel level, BlockPos pos, ServerPlayer player) {
        if (hasNearbyWarden(level, pos)) {
            return OptionalInt.empty();
        } else {
            List<ServerPlayer> nearbyPlayers = getNearbyPlayers(level, pos);
            if (!nearbyPlayers.contains(player)) {
                nearbyPlayers.add(player);
            }

            if (nearbyPlayers.stream().anyMatch(serverPlayer -> serverPlayer.getWardenSpawnTracker().map(WardenSpawnTracker::onCooldown).orElse(false))) {
                return OptionalInt.empty();
            } else {
                Optional<WardenSpawnTracker> optional = nearbyPlayers.stream()
                    .flatMap(serverPlayer -> serverPlayer.getWardenSpawnTracker().stream())
                    .max(Comparator.comparingInt(WardenSpawnTracker::getWarningLevel));
                if (optional.isPresent()) {
                    WardenSpawnTracker wardenSpawnTracker = optional.get();
                    wardenSpawnTracker.increaseWarningLevel();
                    nearbyPlayers.forEach(
                        serverPlayer -> serverPlayer.getWardenSpawnTracker().ifPresent(wardenSpawnTracker1 -> wardenSpawnTracker1.copyData(wardenSpawnTracker))
                    );
                    return OptionalInt.of(wardenSpawnTracker.warningLevel);
                } else {
                    return OptionalInt.empty();
                }
            }
        }
    }

    private boolean onCooldown() {
        return this.cooldownTicks > 0;
    }

    private static boolean hasNearbyWarden(ServerLevel level, BlockPos pos) {
        AABB aabb = AABB.ofSize(Vec3.atCenterOf(pos), 48.0, 48.0, 48.0);
        return !level.getEntitiesOfClass(Warden.class, aabb).isEmpty();
    }

    private static List<ServerPlayer> getNearbyPlayers(ServerLevel level, BlockPos pos) {
        Vec3 vec3 = Vec3.atCenterOf(pos);
        return level.getPlayers(serverPlayer -> !serverPlayer.isSpectator() && serverPlayer.position().closerThan(vec3, 16.0) && serverPlayer.isAlive());
    }

    public void increaseWarningLevel() {
        if (!this.onCooldown()) {
            this.ticksSinceLastWarning = 0;
            this.cooldownTicks = 200;
            this.setWarningLevel(this.getWarningLevel() + 1);
        }
    }

    private void decreaseWarningLevel() {
        this.setWarningLevel(this.getWarningLevel() - 1);
    }

    public void setWarningLevel(int warningLevel) {
        this.warningLevel = Mth.clamp(warningLevel, 0, 4);
    }

    public int getWarningLevel() {
        return this.warningLevel;
    }

    private void copyData(WardenSpawnTracker other) {
        this.warningLevel = other.warningLevel;
        this.cooldownTicks = other.cooldownTicks;
        this.ticksSinceLastWarning = other.ticksSinceLastWarning;
    }
}
