package dev.crucifixbeta.mod.gui;

import com.mojang.blaze3d.systems.RenderSystem;
import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.core.impl.FontManager;
import dev.crucifixbeta.mod.gui.fonts.FontRenderer;
import dev.crucifixbeta.mod.gui.menu.EpsilonMenuStyle;
import java.awt.Color;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.gui.screen.option.OptionsScreen;
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferRenderer;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.VertexFormat.DrawMode;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
import org.joml.Matrix4f;

@Environment(EnvType.CLIENT)
public class EpsilonMainMenuScreen extends Screen {
   public static final EpsilonMainMenuScreen INSTANCE;
   private static final DateTimeFormatter CLOCK_FORMAT;
   private static final DateTimeFormatter DATE_FORMAT;
   private static final Identifier LOGO;
   private static final float LOGO_TEXTURE_WIDTH;
   private static final float LOGO_TEXTURE_HEIGHT;
   private static final long INTRO_DURATION_MS;
   private static final long CLICK_ANIMATION_MS;
   private static final long EXIT_ANIMATION_MS;
   private static final long BACKGROUND_BLUR_IN_MS;
   private static final long BACKGROUND_BLUR_OUT_MS;
   private final List<EpsilonMainMenuScreen.MenuEntry> entries = new ArrayList<>();
   private long introStartMs;
   private long transitionStartMs;
   private long lastBackgroundBlurUpdateMs;
   private float backgroundBlurProgress;
   private boolean initialized;
   private EpsilonMainMenuScreen.MenuEntry selectedEntry;
   private Runnable pendingAction;

   private EpsilonMainMenuScreen() {
      super(Text.literal("MainMenuScreen"));
      this.entries.add(new EpsilonMainMenuScreen.MenuEntry("Singleplayer", () -> this.client.setScreen(new SelectWorldScreen(this))));
      this.entries.add(new EpsilonMainMenuScreen.MenuEntry("Multiplayer", () -> this.client.setScreen(new MultiplayerScreen(this))));
      this.entries
         .add(new EpsilonMainMenuScreen.MenuEntry("Options", () -> this.client.setScreen(new OptionsScreen(this, this.client.options))));
      this.entries.add(new EpsilonMainMenuScreen.MenuEntry("Quit Game", () -> this.client.scheduleStop()));
   }

   @Override
   protected void init() {
      if (!this.initialized) {
         this.initialized = true;
         this.introStartMs = Util.getMeasuringTimeMs();
         this.lastBackgroundBlurUpdateMs = this.introStartMs;
         this.backgroundBlurProgress = 0.0F;
         this.clearTransition();
         EpsilonMenuBackgroundRenderer.resetTime();

         for (EpsilonMainMenuScreen.MenuEntry var2 : this.entries) {
            var2.hoverProgress = 0.0F;
            var2.setBounds(0.0F, 0.0F, 0.0F, 0.0F);
         }
      }
   }

   @Override
   public void tick() {
      if (this.pendingAction != null) {
         long var1 = Util.getMeasuringTimeMs() - this.transitionStartMs;
         if (var1 >= 440L) {
            Runnable var3 = this.pendingAction;
            this.clearTransition();
            var3.run();
         }
      }
   }

   @Override
   public void render(DrawContext context, int mouseX, int mouseY, float delta) {
      EpsilonMenuBackgroundRenderer.render(context, mouseX, mouseY);
      this.drawMenu(context, mouseX, mouseY);
      float var5 = this.getExitProgress(Util.getMeasuringTimeMs());
      if (var5 > 0.001F) {
         int var6 = Math.round(120.0F * easeInOutCubic(var5));
         context.fill(0, 0, this.width, this.height, var6 << 24);
      }
   }

   private void drawMenu(DrawContext context, int mouseX, int mouseY) {
      long var4 = Util.getMeasuringTimeMs();
      float var6 = easeOutCubic(clamp((float)(var4 - this.introStartMs) / 1150.0F, 0.0F, 1.0F));
      float var7 = easeInOutCubic(this.getExitProgress(var4));
      float var8 = 1.0F - var7 * 0.88F;
      EpsilonMainMenuScreen.Layout var9 = EpsilonMainMenuScreen.Layout.resolve(this.width, this.height, this.entries.size());
      int var10 = withAlpha(new Color(230, 224, 233), 0.96F * var8).getRGB();
      int var11 = withAlpha(new Color(202, 196, 208), 0.9F * var8).getRGB();
      float var12 = var7 * 8.0F * var9.scale;
      ArrayList<EpsilonMainMenuScreen.ButtonFrame> var13 = new ArrayList<>(this.entries.size());
      boolean var14 = false;

      for (int var15 = 0; var15 < this.entries.size(); var15++) {
         EpsilonMainMenuScreen.ButtonFrame var16 = this.prepareEntry(this.entries.get(var15), var15, mouseX, mouseY, var6, var7, var4, var9);
         if (var16 != null) {
            var14 |= var16.hovered;
            var13.add(var16);
         }
      }

      this.updateBackgroundBlur(var14, var4);
      this.applyBackgroundHoverBlur(var9);
      LocalDateTime var18 = LocalDateTime.now();
      this.drawClientText(context, CLOCK_FORMAT.format(var18), var9.titleX, var9.titleY + var12, var10, var9.titleScale, this.getTitleFont());
      this.drawClientText(
         context, DATE_FORMAT.format(var18), var9.titleX, var9.titleY + 39.0F * var9.scale + var12, var11, var9.subtitleScale, this.getSubtitleFont()
      );
      this.drawLogo(context, var9, var8, var6, var7);

      for (EpsilonMainMenuScreen.ButtonFrame var17 : var13) {
         this.drawEntry(context, var17, var8, var9);
      }
   }

   private void drawLogo(DrawContext context, EpsilonMainMenuScreen.Layout layout, float menuAlpha, float intro, float exit) {
      float var6 = easeOutCubic(clamp((intro - 0.12F) / 0.64F, 0.0F, 1.0F));
      float var7 = var6 * menuAlpha;
      if (!(var7 <= 0.001F)) {
         float var8 = layout.logoWidth();
         float var9 = var8 * 779.0F / 2048.0F;
         float var10 = (float)this.width * 0.5F - var8 * 0.5F;
         float var11 = layout.buttonsY - var9 - 13.0F * layout.scale + (1.0F - var6) * 18.0F * layout.scale + exit * 8.0F * layout.scale;
         drawTexture(context, LOGO, var10, var11, var8, var9, var7);
      }
   }

   private EpsilonMainMenuScreen.ButtonFrame prepareEntry(
      EpsilonMainMenuScreen.MenuEntry entry, int index, int mouseX, int mouseY, float intro, float exit, long now, EpsilonMainMenuScreen.Layout layout
   ) {
      float var10 = clamp((intro - (float)index * 0.08F) / 0.52F, 0.0F, 1.0F);
      float var11 = easeOutCubic(var10);
      if (var11 <= 0.001F) {
         entry.setBounds(0.0F, 0.0F, 0.0F, 0.0F);
         return null;
      } else {
         float var12 = layout.entryWidth(index);
         float var13 = layout.entryX(index);
         float var14 = layout.entryY(index) + (1.0F - var11) * layout.buttonRevealDistance + exit * 11.0F * layout.scale;
         boolean var15 = entry == this.selectedEntry;
         boolean var16 = this.pendingAction == null && entry.isHovered((double)mouseX, (double)mouseY);
         entry.hoverProgress = lerp(!var16 && !var15 ? 0.16F : 0.24F, entry.hoverProgress, !var16 && !var15 ? 0.0F : 1.0F);
         float var17 = entry.hoverProgress;
         float var18 = var15 ? easeOutCubic(clamp((float)(now - this.transitionStartMs) / 180.0F, 0.0F, 1.0F)) : 0.0F;
         float var19 = var15 ? 1.0F - Math.abs(var18 * 2.0F - 1.0F) : 0.0F;
         float var20 = var14 - var17 * 2.5F * layout.scale + var19 * 1.4F * layout.scale;
         entry.setBounds(var13 - layout.buttonHitPaddingX, var20 - layout.buttonHitPaddingTop, var12 + layout.buttonHitPaddingX * 2.0F, layout.buttonHitHeight);
         float var21 = Math.min(layout.buttonHitHeight * 0.22F, 4.0F * layout.scale);
         return new EpsilonMainMenuScreen.ButtonFrame(entry, var13, var20, var12, layout.buttonHitHeight, var11, var17, var19, var21, var16);
      }
   }

   private void updateBackgroundBlur(boolean hovered, long now) {
      long var4 = Math.max(0L, now - this.lastBackgroundBlurUpdateMs);
      this.lastBackgroundBlurUpdateMs = now;
      float var6 = hovered ? 180.0F : 260.0F;
      float var7 = var6 <= 0.0F ? 1.0F : (float)var4 / var6;
      if (hovered) {
         this.backgroundBlurProgress = clamp(this.backgroundBlurProgress + var7, 0.0F, 1.0F);
      } else {
         this.backgroundBlurProgress = clamp(this.backgroundBlurProgress - var7, 0.0F, 1.0F);
      }
   }

   private void applyBackgroundHoverBlur(EpsilonMainMenuScreen.Layout layout) {
      if (CrucifixBeta.BLUR != null && !(this.backgroundBlurProgress <= 0.001F) && this.width > 0 && this.height > 0) {
         float var2 = easeInOutCubic(this.backgroundBlurProgress);
         CrucifixBeta.BLUR.applyBlur((10.0F + 4.0F * layout.scale) * var2, 0.0F, 0.0F, (float)this.width, (float)this.height, 1.0F);
      }
   }

   private void drawEntry(DrawContext context, EpsilonMainMenuScreen.ButtonFrame frame, float menuAlpha, EpsilonMainMenuScreen.Layout layout) {
      Color var5 = withAlpha(new Color(230, 224, 233), 0.94F * frame.appear * menuAlpha);
      Color var6 = withAlpha(new Color(234, 221, 255), 0.98F * frame.appear * menuAlpha);
      Color var7 = lerpColor(var5, var6, Math.max(frame.hover * 0.68F, frame.press * 0.82F));
      Color var8 = withAlpha(new Color(0, 0, 0, 170), (0.48F + frame.hover * 0.16F + frame.press * 0.08F) * frame.appear * menuAlpha);
      Color var9 = withAlpha(new Color(255, 255, 255), (0.12F + frame.hover * 0.18F + frame.press * 0.1F) * frame.appear * menuAlpha);
      EpsilonMenuStyle.drawSoftFrame(context, frame.x, frame.y, frame.width, frame.height, frame.radius, var8, var9);
      EpsilonMainMenuScreen.MenuEntry var10 = frame.entry;
      FontRenderer var11 = this.getMenuFont();
      float var12 = this.getClientTextWidth(var10.title, var11) * layout.buttonTextScale;
      float var13 = this.getClientTextHeight(var10.title, var11) * layout.buttonTextScale;
      this.drawClientText(
         context,
         var10.title,
         frame.x + (frame.width - var12) / 2.0F,
         frame.y + (frame.height - var13) / 2.0F + layout.buttonTextOffsetY,
         var7.getRGB(),
         layout.buttonTextScale,
         var11,
         false
      );
   }

   private void drawClientText(DrawContext context, String text, float x, float y, int color, float scale) {
      this.drawClientText(context, text, x, y, color, scale, FontManager.ui);
   }

   private static void drawTexture(DrawContext context, Identifier texture, float x, float y, float width, float height, float alpha) {
      int var7 = Math.round(clamp(alpha, 0.0F, 1.0F) * 255.0F);
      if (var7 > 0 && !(width <= 0.0F) && !(height <= 0.0F)) {
         RenderSystem.enableBlend();
         RenderSystem.defaultBlendFunc();
         RenderSystem.setShader(GameRenderer::getPositionTexColorProgram);
         RenderSystem.setShaderTexture(0, texture);
         Matrix4f var8 = context.getMatrices().peek().getPositionMatrix();
         BufferBuilder var9 = Tessellator.getInstance().begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR);
         var9.vertex(var8, x, y + height, 0.0F).texture(0.0F, 1.0F).color(255, 255, 255, var7);
         var9.vertex(var8, x + width, y + height, 0.0F).texture(1.0F, 1.0F).color(255, 255, 255, var7);
         var9.vertex(var8, x + width, y, 0.0F).texture(1.0F, 0.0F).color(255, 255, 255, var7);
         var9.vertex(var8, x, y, 0.0F).texture(0.0F, 0.0F).color(255, 255, 255, var7);
         BufferRenderer.drawWithGlobalProgram(var9.end());
         RenderSystem.disableBlend();
      }
   }

   private void drawClientText(DrawContext context, String text, float x, float y, int color, float scale, FontRenderer font) {
      this.drawClientText(context, text, x, y, color, scale, font, true);
   }

   private void drawClientText(DrawContext context, String text, float x, float y, int color, float scale, FontRenderer font, boolean shadow) {
      context.getMatrices().push();
      context.getMatrices().scale(scale, scale, 1.0F);
      float var9 = x / scale;
      float var10 = y / scale;
      if (font != null) {
         if (shadow) {
            float var11 = 0.6F / scale;
            font.drawString(context.getMatrices(), text, (double)(var9 + var11), (double)(var10 + var11), shadowColor(color), false);
         }

         font.drawString(context.getMatrices(), text, (double)var9, (double)var10, color, false);
      } else {
         context.drawTextWithShadow(this.textRenderer, text, Math.round(var9), Math.round(var10), color);
      }

      context.getMatrices().pop();
   }

   private float getClientTextWidth(String text) {
      return this.getClientTextWidth(text, FontManager.ui);
   }

   private float getClientTextWidth(String text, FontRenderer font) {
      return font != null ? font.getWidth(text) : (float)this.textRenderer.getWidth(text);
   }

   private float getClientTextHeight(String text) {
      return this.getClientTextHeight(text, FontManager.ui);
   }

   private float getClientTextHeight(String text, FontRenderer font) {
      return font != null ? font.getFontHeight(text) : 9.0F;
   }

   private FontRenderer getMenuFont() {
      return FontManager.mainMenu != null ? FontManager.mainMenu : FontManager.ui;
   }

   private FontRenderer getTitleFont() {
      return FontManager.mainMenuTitle != null ? FontManager.mainMenuTitle : FontManager.ui;
   }

   private FontRenderer getSubtitleFont() {
      return FontManager.mainMenuSubtitle != null ? FontManager.mainMenuSubtitle : this.getTitleFont();
   }

   @Override
   public boolean mouseClicked(double mouseX, double mouseY, int button) {
      if (button == 0 && this.pendingAction == null) {
         for (EpsilonMainMenuScreen.MenuEntry var7 : this.entries) {
            if (var7.isHovered(mouseX, mouseY)) {
               this.selectedEntry = var7;
               this.pendingAction = var7.action;
               this.transitionStartMs = Util.getMeasuringTimeMs();
               return true;
            }
         }
      }

      return super.mouseClicked(mouseX, mouseY, button);
   }

   @Override
   public void removed() {
      super.removed();
      this.initialized = false;
      EpsilonMenuBackgroundRenderer.close();
   }

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

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

   private static float easeOutCubic(float value) {
      float var1 = clamp(value, 0.0F, 1.0F);
      float var2 = 1.0F - var1;
      return 1.0F - var2 * var2 * var2;
   }

   private static float easeInOutCubic(float value) {
      float var1 = clamp(value, 0.0F, 1.0F);
      return var1 < 0.5F ? 4.0F * var1 * var1 * var1 : 1.0F - (float)Math.pow((double)(-2.0F * var1 + 2.0F), 3.0) / 2.0F;
   }

   private float getExitProgress(long now) {
      return this.pendingAction == null ? 0.0F : clamp((float)(now - this.transitionStartMs - 180L) / 260.0F, 0.0F, 1.0F);
   }

   private void clearTransition() {
      this.selectedEntry = null;
      this.pendingAction = null;
      this.transitionStartMs = 0L;
   }

   private static float clamp(float value, float min, float max) {
      return Math.max(min, Math.min(max, value));
   }

   private static float lerp(float delta, float start, float end) {
      return start + (end - start) * delta;
   }

   private static Color withAlpha(Color color, float factor) {
      float var2 = clamp(factor, 0.0F, 1.0F);
      return new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.round((float)color.getAlpha() * var2));
   }

   private static int shadowColor(int color) {
      int var1 = color >>> 24;
      return Math.round((float)var1 * 0.28F) << 24;
   }

   private static Color lerpColor(Color from, Color to, float delta) {
      float var3 = clamp(delta, 0.0F, 1.0F);
      int var4 = Math.round(lerp(var3, (float)from.getRed(), (float)to.getRed()));
      int var5 = Math.round(lerp(var3, (float)from.getGreen(), (float)to.getGreen()));
      int var6 = Math.round(lerp(var3, (float)from.getBlue(), (float)to.getBlue()));
      int var7 = Math.round(lerp(var3, (float)from.getAlpha(), (float)to.getAlpha()));
      return new Color(var4, var5, var6, var7);
   }

   static {

      BACKGROUND_BLUR_OUT_MS = 260L;
      BACKGROUND_BLUR_IN_MS = 180L;
      EXIT_ANIMATION_MS = 260L;
      CLICK_ANIMATION_MS = 180L;
      INTRO_DURATION_MS = 1150L;
      LOGO_TEXTURE_HEIGHT = 779.0F;
      LOGO_TEXTURE_WIDTH = 2048.0F;
      INSTANCE = new EpsilonMainMenuScreen();
      CLOCK_FORMAT = DateTimeFormatter.ofPattern("hh:mm a", Locale.ENGLISH);
      DATE_FORMAT = DateTimeFormatter.ofPattern("MMMM d, yyyy", Locale.ENGLISH);
      LOGO = Identifier.of("crucifixbeta", "textures/gui/crucifix_logo.png");
   }
@Environment(EnvType.CLIENT)
   private static final class ButtonFrame {
      private final EpsilonMainMenuScreen.MenuEntry entry;
      private final float x;
      private final float y;
      private final float width;
      private final float height;
      private final float appear;
      private final float hover;
      private final float press;
      private final float radius;
      private final boolean hovered;

      private ButtonFrame(
         EpsilonMainMenuScreen.MenuEntry entry,
         float x,
         float y,
         float width,
         float height,
         float appear,
         float hover,
         float press,
         float radius,
         boolean hovered
      ) {
         this.entry = entry;
         this.x = x;
         this.y = y;
         this.width = width;
         this.height = height;
         this.appear = appear;
         this.hover = hover;
         this.press = press;
         this.radius = radius;
         this.hovered = hovered;
      }
   }

   @Environment(EnvType.CLIENT)
   private static record Layout(
      float scale,
      float titleX,
      float titleY,
      float titleScale,
      float subtitleScale,
      float titleAccentWidth,
      float titleAccentHeight,
      float buttonsStartX,
      float buttonsY,
      float buttonWidth,
      float buttonGap,
      float buttonRowGap,
      int buttonColumnCount,
      float buttonLineHeight,
      float buttonHitPaddingX,
      float buttonHitPaddingTop,
      float buttonHitHeight,
      float buttonRevealDistance,
      float buttonTextScale,
      float buttonTextOffsetY
   ) {
      private static EpsilonMainMenuScreen.Layout resolve(int width, int height, int entryCount) {
         float var3 = EpsilonMainMenuScreen.clamp(((float)width * 2.0F + (float)height) / 900.0F + 0.08F, 0.72F, 1.24F);
         float var4 = Math.max(12.0F * var3, (float)width / 15.0F);
         float var5 = Math.max(8.0F * var3, var4 * 0.5F);
         float var6 = 0.86F * var3;
         float var7 = 0.92F * var3;
         float var8 = 68.0F * var3;
         float var9 = Math.max(1.6F, 1.8F * var3);
         float var10 = 4.0F * var3;
         float var11 = 7.0F * var3;
         int var12 = Math.min(2, Math.max(1, entryCount));
         int var13 = entryCount <= 1 ? 1 : Math.min(3, entryCount);
         float var14 = Math.max(14.0F * var3, (float)width / 12.0F);
         float var15 = Math.max(36.0F * var3, ((float)width - var14 * 2.0F - var10 * (float)Math.max(0, var12 - 1)) / (float)var12);
         float var16 = EpsilonMainMenuScreen.clamp(69.0F * var3, 42.0F * var3, var15);
         float var17 = 16.0F * var3;
         float var18 = (float)var12 * var16 + (float)Math.max(0, var12 - 1) * var10;
         float var19 = ((float)width - var18) * 0.5F;
         float var20 = (float)var13 * var17 + (float)Math.max(0, var13 - 1) * var11;
         float var21 = ((float)height - var20) * 0.5F;
         return new EpsilonMainMenuScreen.Layout(
            var3,
            var4,
            var5,
            var6,
            var7,
            var8,
            var9,
            var19,
            var21,
            var16,
            var10,
            var11,
            var12,
            Math.max(2.0F, 2.0F * var3),
            4.0F * var3,
            0.0F,
            var17,
            Math.max(62.0F * var3, (float)height * 0.24F),
            0.74F * var3,
            0.0F
         );
      }

      private float entryWidth(int index) {
         return index <= 1 && this.buttonColumnCount > 1 ? this.buttonWidth * 2.0F + this.buttonGap : this.buttonWidth;
      }

      private float entryX(int index) {
         if (index <= 1 && this.buttonColumnCount > 1) {
            return this.buttonsStartX;
         } else {
            int var2 = Math.max(0, index - 2) % Math.max(1, this.buttonColumnCount);
            return this.buttonsStartX + (float)var2 * (this.buttonWidth + this.buttonGap);
         }
      }

      private float entryY(int index) {
         int var2;
         if (index <= 0) {
            var2 = 0;
         } else if (index == 1) {
            var2 = 1;
         } else {
            var2 = 2 + Math.max(0, index - 2) / Math.max(1, this.buttonColumnCount);
         }

         return this.buttonsY + (float)var2 * (this.buttonHitHeight + this.buttonRowGap);
      }

      private float logoWidth() {
         return Math.min(this.entryWidth(0) * 0.71F, 123.0F * this.scale);
      }
   }

   @Environment(EnvType.CLIENT)
   private static final class MenuEntry {
      private final String title;
      private final Runnable action;
      private float x;
      private float y;
      private float width;
      private float height;
      private float hoverProgress;

      private MenuEntry(String title, Runnable action) {
         this.title = title;
         this.action = action;
      }

      private void setBounds(float x, float y, float width, float height) {
         this.x = x;
         this.y = y;
         this.width = width;
         this.height = height;
      }

      private boolean isHovered(double mouseX, double mouseY) {
         return mouseX >= (double)this.x && mouseX <= (double)(this.x + this.width) && mouseY >= (double)this.y && mouseY <= (double)(this.y + this.height);
      }
   }
}
