package zenith.zov.client.hud.elements.component;

import com.mojang.authlib.GameProfile;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import lombok.Generated;
import net.minecraft.client.gui.screen.ChatScreen;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.util.DefaultSkinHelper;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.world.GameMode;
import zenith.zov.Zenith;
import zenith.zov.base.animations.base.Animation;
import zenith.zov.base.animations.base.Easing;
import zenith.zov.base.font.Font;
import zenith.zov.base.font.Fonts;
import zenith.zov.base.theme.Theme;
import zenith.zov.client.hud.elements.draggable.DraggableHudElement;
import zenith.zov.utility.game.other.TextUtil;
import zenith.zov.utility.interfaces.IMinecraft;
import zenith.zov.utility.render.display.base.BorderRadius;
import zenith.zov.utility.render.display.base.CustomDrawContext;
import zenith.zov.utility.render.display.base.color.ColorRGBA;
import zenith.zov.utility.render.display.shader.DrawUtil;

public class StaffComponent extends DraggableHudElement {
    private final Animation animationWidth = new Animation(200L, 100.0F, Easing.QUAD_IN_OUT);
    private final Animation animationScale = new Animation(200L, 0.0F, Easing.QUAD_IN_OUT);
    private final Animation animationVisible = new Animation(200L, 0.0F, Easing.QUAD_IN_OUT);
    private final Map<String, StaffComponent.StaffModule> modules = new LinkedHashMap<>();
    private final Set<String> staffPrefix = Set.of("helper", "admin", "moder", "staff", "curator", "trainee", "employee", "assistant");
    private final Map<String, Identifier> skinTextureCache = new HashMap<>();
    private long lastStaffUpdate = 0L;
    private long lastSkinCacheClear = 0L;
    private final Set<String> currentStaffKeys = new HashSet<>();

    public StaffComponent(
        String name, float initialX, float initialY, float windowWidth, float windowHeight, float offsetX, float offsetY, DraggableHudElement.Align align
    ) {
        super(name, initialX, initialY, windowWidth, windowHeight, offsetX, offsetY, align);
    }

    @Override
    public void render(CustomDrawContext ctx) {
        long currentTime = System.currentTimeMillis();
        if (currentTime - this.lastStaffUpdate > 1000L && mc.getNetworkHandler() != null) {
            this.updateStaffList();
            this.lastStaffUpdate = currentTime;
        }

        if (currentTime - this.lastSkinCacheClear > 30000L) {
            this.skinTextureCache.clear();
            this.lastSkinCacheClear = currentTime;
        }

        this.modules.entrySet().removeIf(entryx -> ((StaffComponent.StaffModule)entryx.getValue()).isDelete());
        boolean hidden = this.modules.size() == 1 && this.modules.values().iterator().next().animation.getTargetValue() == 0.0F;
        this.animationScale.update(hidden ? 0.0F : 1.0F);
        Theme theme = Zenith.getInstance().getThemeManager().getCurrentTheme();
        Font iconFont = Fonts.ICONS.getFont(6.0F);
        Font font = Fonts.MEDIUM.getFont(6.0F);
        float x = this.x;
        float y = this.y;
        float height = (float)(18.0 + this.modules.values().stream().mapToDouble(StaffComponent.StaffModule::getHeight).sum());
        float width = (float)this.modules.values().stream().mapToDouble(StaffComponent.StaffModule::updateWidth).max().orElse(100.0);
        width = this.animationWidth.update(width);
        this.width = width;
        this.height = height;
        ctx.pushMatrix();
        float borderAnim = this.animationScale.getValue() * 4.0F;
        BorderRadius radius = new BorderRadius(4.0F, 4.0F, borderAnim, borderAnim);
        this.animationVisible.update(mc.currentScreen instanceof ChatScreen || !this.modules.isEmpty());
        ctx.getMatrices().translate(x + width / 2.0F, y + height / 2.0F, 0.0F);
        ctx.getMatrices().scale(this.animationVisible.getValue(), this.animationVisible.getValue(), 1.0F);
        ctx.getMatrices().translate(-(x + width / 2.0F), -(y + height / 2.0F), 0.0F);
        DrawUtil.drawBlurHud(ctx.getMatrices(), x, y, width, height, 21.0F, BorderRadius.all(4.0F), ColorRGBA.WHITE);
        ctx.drawRoundedRect(x, y, width, height, radius, theme.getForegroundLight());
        ctx.drawText(iconFont, "P", x + 8.0F, y + (18.0F - iconFont.height()) / 2.0F, theme.getColor());
        ctx.drawText(iconFont, "M", x + width - 8.0F - iconFont.width("M"), y + (18.0F - iconFont.height()) / 2.0F, theme.getWhiteGray());
        ctx.drawText(font, "Staffs", x + 8.0F + 8.0F + 2.0F, y + (18.0F - font.height()) / 2.0F, theme.getWhite());
        if (this.animationVisible.getValue() == 1.0F) {
            float offsetY = y + 18.0F;
            int index = 0;

            for (Entry<String, StaffComponent.StaffModule> entry : this.modules.entrySet()) {
                StaffComponent.StaffModule module = entry.getValue();
                module.render(ctx, x, offsetY, width, index, this.currentStaffKeys.contains(entry.getKey()));
                offsetY += module.getHeight();
                index++;
            }
        }

        ctx.drawRoundedBorder(x, y, width, height, 0.1F, BorderRadius.all(4.0F), theme.getForegroundStroke());
        DrawUtil.drawRoundedCorner(
            ctx.getMatrices(), x, y, width, height, 0.1F, Math.min(20.0F, Math.max(12.0F, height / 2.5F)), theme.getColor(), BorderRadius.all(4.0F)
        );
        ctx.popMatrix();
    }

    private void updateStaffList() {
        if (mc.getNetworkHandler() != null) {
            this.currentStaffKeys.clear();

            for (PlayerListEntry entry : mc.getNetworkHandler().getPlayerList()) {
                GameProfile profile = entry.getProfile();
                Text displayName = entry.getDisplayName();
                if (displayName != null && profile != null) {
                    String display = displayName.getString();
                    String name = profile.getName();
                    String prefix = display.replace(name, "").trim();
                    if (prefix.length() >= 2 && this.containsAnyKeyword(prefix)) {
                        StaffComponent.Status status = entry.getGameMode() == GameMode.SPECTATOR ? StaffComponent.Status.VANISHED : StaffComponent.Status.NONE;
                        displayName = displayName.getString().contains(profile.getName())
                            ? TextUtil.truncateAfterSubstring(displayName, profile.getName(), false)
                            : TextUtil.truncateAfterSecondSpace(displayName, false);
                        this.modules.computeIfAbsent(display, k -> new StaffComponent.StaffModule(displayName, display, name, status));
                        this.currentStaffKeys.add(display);
                    }
                }
            }
        }
    }

    public boolean containsAnyKeyword(String text) {
        String lower = text.toLowerCase(Locale.US);

        for (String keyword : this.staffPrefix) {
            if (lower.contains(keyword)) {
                return true;
            }
        }

        return false;
    }

    private String formatTime(long ms) {
        long minutes = ms / 60000L;
        long seconds = ms % 60000L / 1000L;
        return String.format("%d:%02d", minutes, seconds);
    }

    public static class Staff {
        private Text prefix;
        private String name;
        private boolean isSpec;
        private StaffComponent.Status status;

        @Generated
        public Text getPrefix() {
            return this.prefix;
        }

        @Generated
        public String getName() {
            return this.name;
        }

        @Generated
        public boolean isSpec() {
            return this.isSpec;
        }

        @Generated
        public StaffComponent.Status getStatus() {
            return this.status;
        }

        @Generated
        public void setPrefix(Text prefix) {
            this.prefix = prefix;
        }

        @Generated
        public void setName(String name) {
            this.name = name;
        }

        @Generated
        public void setSpec(boolean isSpec) {
            this.isSpec = isSpec;
        }

        @Generated
        public void setStatus(StaffComponent.Status status) {
            this.status = status;
        }

        @Generated
        @Override
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof StaffComponent.Staff other)) {
                return false;
            } else if (!other.canEqual(this)) {
                return false;
            } else if (this.isSpec() != other.isSpec()) {
                return false;
            } else {
                Object this$prefix = this.getPrefix();
                Object other$prefix = other.getPrefix();
                if (this$prefix == null ? other$prefix == null : this$prefix.equals(other$prefix)) {
                    Object this$name = this.getName();
                    Object other$name = other.getName();
                    if (this$name == null ? other$name == null : this$name.equals(other$name)) {
                        Object this$status = this.getStatus();
                        Object other$status = other.getStatus();
                        return this$status == null ? other$status == null : this$status.equals(other$status);
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        }

        @Generated
        protected boolean canEqual(Object other) {
            return other instanceof StaffComponent.Staff;
        }

        @Generated
        @Override
        public int hashCode() {
            int PRIME = 59;
            int result = 1;
            result = result * 59 + (this.isSpec() ? 79 : 97);
            Object $prefix = this.getPrefix();
            result = result * 59 + ($prefix == null ? 43 : $prefix.hashCode());
            Object $name = this.getName();
            result = result * 59 + ($name == null ? 43 : $name.hashCode());
            Object $status = this.getStatus();
            return result * 59 + ($status == null ? 43 : $status.hashCode());
        }

        @Generated
        @Override
        public String toString() {
            return "StaffComponent.Staff(prefix="
                + this.getPrefix()
                + ", name="
                + this.getName()
                + ", isSpec="
                + this.isSpec()
                + ", status="
                + this.getStatus()
                + ")";
        }

        @Generated
        public Staff(Text prefix, String name, boolean isSpec, StaffComponent.Status status) {
            this.prefix = prefix;
            this.name = name;
            this.isSpec = isSpec;
            this.status = status;
        }
    }

    private class StaffModule {
        private final Animation animation = new Animation(150L, 0.01F, Easing.QUAD_IN_OUT);
        private final Animation animationColor = new Animation(200L, Easing.QUAD_IN_OUT);
        private final Text displayNameText;
        private final String key;
        private final String name;
        private final StaffComponent.Status status;
        private final long appearTime;

        public StaffModule(Text displayNameText, String key, String name, StaffComponent.Status status) {
            this.displayNameText = displayNameText;
            this.key = key;
            this.name = name;
            this.status = status;
            this.appearTime = System.currentTimeMillis();
        }

        public float updateWidth() {
            float width = 100.0F;
            Font font = Fonts.MEDIUM.getFont(6.0F);
            String time = StaffComponent.this.formatTime(System.currentTimeMillis() - this.appearTime);
            float leftTextWidth = 24.0F + font.width(this.displayNameText);
            float rightTextWidth = font.width(time) + 8.0F;
            if (width - (leftTextWidth + rightTextWidth) < 8.0F) {
                width += leftTextWidth + rightTextWidth + 8.0F - width;
            }

            return width;
        }

        public float getHeight() {
            return 18.0F * this.animation.getValue();
        }

        public void render(CustomDrawContext ctx, float x, float y, float width, int i, boolean present) {
            Theme theme = Zenith.getInstance().getThemeManager().getCurrentTheme();
            Font font = Fonts.MEDIUM.getFont(6.0F);
            this.animation.update(present ? 1.0F : 0.0F);
            ctx.pushMatrix();
            ctx.getMatrices().translate(x + width / 2.0F, y + 9.0F, 0.0F);
            ctx.getMatrices().scale(this.animation.getValue(), this.animation.getValue(), 1.0F);
            ctx.getMatrices().translate(-(x + width / 2.0F), -(y + 9.0F), 0.0F);
            this.animationColor.update(i % 2 == 0 ? 1.0F : 0.0F);
            ColorRGBA background = theme.getForegroundLight().mix(theme.getForegroundColor(), this.animationColor.getValue());
            ctx.drawRoundedRect(
                x, y, width, 18.0F, i == StaffComponent.this.modules.size() - 1 ? BorderRadius.bottom(4.0F, 4.0F) : BorderRadius.ZERO, background
            );
            Identifier skinTexture = StaffComponent.this.skinTextureCache.get(this.name);
            if (skinTexture == null && IMinecraft.mc.getNetworkHandler() != null) {
                PlayerListEntry player = IMinecraft.mc
                    .getNetworkHandler()
                    .getPlayerList()
                    .stream()
                    .filter(p -> p.getProfile() != null && this.name.equals(p.getProfile().getName()))
                    .findFirst()
                    .orElse(null);
                if (player != null && player.getSkinTextures() != null) {
                    skinTexture = player.getSkinTextures().texture();
                    StaffComponent.this.skinTextureCache.put(this.name, skinTexture);
                }
            }

            if (skinTexture == null) {
                skinTexture = DefaultSkinHelper.getSteve().texture();
            }

            DrawUtil.drawPlayerHeadWithRoundedShader(ctx.getMatrices(), skinTexture, x + 6.0F, y + 6.0F, 6.0F, BorderRadius.all(1.6F), ColorRGBA.WHITE);
            ctx.drawText(Fonts.BOLD.getFont(8.0F), ".", x + 8.0F + 8.0F + 2.0F, y + 4.0F, theme.getWhiteGray());
            ctx.drawText(font, this.displayNameText, x + 8.0F + 8.0F + 8.0F, y + (18.0F - font.height()) / 2.0F);
            String timeText = StaffComponent.this.formatTime(System.currentTimeMillis() - this.appearTime);
            ctx.drawText(font, timeText, x + width - 8.0F - font.width(timeText), y + (18.0F - font.height()) / 2.0F, theme.getColor());
            ctx.popMatrix();
        }

        public boolean isDelete() {
            return this.animation.getValue() == 0.0F;
        }
    }

    public static enum Status {
        NONE,
        VANISHED;
    }
}
