package com.trunksbomb.minetriad.client.render;

import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import com.trunksbomb.minetriad.card.CardDefinition;
import com.trunksbomb.minetriad.card.CardRegistry;
import com.trunksbomb.minetriad.card.CardStackData;
import com.trunksbomb.minetriad.registry.TriadDataComponents;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.util.FormattedCharSequence;
import org.joml.Matrix4f;

public final class TriadCardItemRenderer extends BlockEntityWithoutLevelRenderer {
    private static final float CARD_MIN = -0.42F;
    private static final float CARD_MAX = 0.42F;
    private static final float FRONT_Z = 0.024F;
    private static final float BACK_Z = -0.024F;
    private static final float BORDER_Z = 0.026F;
    private static final float VALUE_Z = 0.030F;
    private static final float INNER_MIN = -0.37F;
    private static final float INNER_MAX = 0.37F;
    private static TriadCardItemRenderer INSTANCE;
    private static final CardPalette NEUTRAL_PALETTE = new CardPalette(
            new float[] {0.97F, 0.93F, 0.80F},
            new float[] {0.20F, 0.22F, 0.28F});

    private TriadCardItemRenderer(BlockEntityRenderDispatcher blockEntityRenderDispatcher, EntityModelSet entityModelSet) {
        super(blockEntityRenderDispatcher, entityModelSet);
    }

    public static TriadCardItemRenderer getInstance() {
        if (INSTANCE == null) {
            Minecraft minecraft = Minecraft.getInstance();
            INSTANCE = new TriadCardItemRenderer(minecraft.getBlockEntityRenderDispatcher(), minecraft.getEntityModels());
        }
        return INSTANCE;
    }

    @Override
    public void renderByItem(ItemStack stack, ItemDisplayContext displayContext, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
        renderCard(stack, poseStack, buffer, NEUTRAL_PALETTE, CardLayout.forDisplayContext(displayContext));
    }

    public static void renderCard(ItemStack stack, PoseStack poseStack, MultiBufferSource buffer, CardPalette palette) {
        renderCard(stack, poseStack, buffer, palette, CardLayout.BOARD);
    }

    public static CardPalette neutralPalette() {
        return NEUTRAL_PALETTE;
    }

    public static void renderCardForDisplayContext(
            ItemStack stack,
            PoseStack poseStack,
            MultiBufferSource buffer,
            CardPalette palette,
            ItemDisplayContext displayContext) {
        renderCard(stack, poseStack, buffer, palette, CardLayout.forDisplayContext(displayContext));
    }

    private static void renderCard(ItemStack stack, PoseStack poseStack, MultiBufferSource buffer, CardPalette palette, CardLayout layout) {
        CardStackData cardData = stack.get(TriadDataComponents.CARD_DATA);
        CardDefinition card = cardData == null || cardData.equals(CardStackData.EMPTY) ? null : CardRegistry.get(cardData.cardId());

        poseStack.pushPose();
        drawBack(poseStack, buffer);
        if (card != null) {
            drawFrontArt(poseStack, buffer, artTexture(card));
        } else {
            drawFallbackFace(poseStack, buffer, palette);
        }
        drawBorderOverlay(poseStack, buffer, palette);
        if (card != null) {
            Font font = Minecraft.getInstance().font;
            drawValue(poseStack, buffer, font, displayRank(card.top()), 0.0F, layout.topY(), layout.valueScale());
            drawValue(poseStack, buffer, font, displayRank(card.bottom()), 0.0F, layout.bottomY(), layout.valueScale());
            drawValue(poseStack, buffer, font, displayRank(card.left()), -layout.sideX(), layout.sideY(), layout.valueScale());
            drawValue(poseStack, buffer, font, displayRank(card.right()), layout.sideX(), layout.sideY(), layout.valueScale());
        }
        poseStack.popPose();
    }

    private static String displayRank(int rank) {
        return rank == 10 ? "A" : Integer.toString(rank);
    }

    private static void drawFallbackFace(PoseStack poseStack, MultiBufferSource buffer, CardPalette palette) {
        LevelRenderer.addChainedFilledBoxVertices(
                poseStack,
                buffer.getBuffer(RenderType.debugFilledBox()),
                CARD_MIN,
                CARD_MIN,
                FRONT_Z,
                CARD_MAX,
                CARD_MAX,
                FRONT_Z + 0.001F,
                palette.face()[0],
                palette.face()[1],
                palette.face()[2],
                1.0F);
    }

    private static void drawBack(PoseStack poseStack, MultiBufferSource buffer) {
        drawTexturedQuad(poseStack, buffer, backTexture(), BACK_Z, true);
    }

    private static void drawBorderOverlay(PoseStack poseStack, MultiBufferSource buffer, CardPalette palette) {
        float[] border = palette.border();
        drawBorderStrip(poseStack, buffer, CARD_MIN, INNER_MAX, CARD_MAX, CARD_MAX, border);
        drawBorderStrip(poseStack, buffer, CARD_MIN, CARD_MIN, CARD_MAX, INNER_MIN, border);
        drawBorderStrip(poseStack, buffer, CARD_MIN, INNER_MIN, INNER_MIN, INNER_MAX, border);
        drawBorderStrip(poseStack, buffer, INNER_MAX, INNER_MIN, CARD_MAX, INNER_MAX, border);
    }

    private static void drawBorderStrip(PoseStack poseStack, MultiBufferSource buffer, float minX, float minY, float maxX, float maxY, float[] color) {
        LevelRenderer.addChainedFilledBoxVertices(
                poseStack,
                buffer.getBuffer(RenderType.debugFilledBox()),
                minX,
                minY,
                BORDER_Z,
                maxX,
                maxY,
                BORDER_Z + 0.001F,
                color[0],
                color[1],
                color[2],
                1.0F);
    }

    private static void drawValue(PoseStack poseStack, MultiBufferSource buffer, Font font, String value, float x, float y, float scale) {
        poseStack.pushPose();
        poseStack.translate(x, y, VALUE_Z);
        poseStack.scale(scale, -scale, scale);
        float width = font.width(value);
        FormattedCharSequence sequence = FormattedCharSequence.forward(value, net.minecraft.network.chat.Style.EMPTY);
        font.drawInBatch8xOutline(
                sequence,
                -width / 2.0F,
                0.0F,
                0xFFFFFFFF,
                0xFF101010,
                poseStack.last().pose(),
                buffer,
                LightTexture.FULL_BRIGHT);
        poseStack.popPose();
    }

    private static void drawFrontArt(PoseStack poseStack, MultiBufferSource buffer, ResourceLocation textureLocation) {
        drawTexturedQuad(poseStack, buffer, textureLocation, FRONT_Z, false);
    }

    private static void drawTexturedQuad(PoseStack poseStack, MultiBufferSource buffer, ResourceLocation textureLocation, float z, boolean reverseWinding) {
        VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityCutoutNoCull(textureLocation));
        Matrix4f matrix = poseStack.last().pose();
        if (reverseWinding) {
            addArtVertex(vertexConsumer, matrix, CARD_MIN, CARD_MIN, z, 0.0F, 1.0F, -1.0F);
            addArtVertex(vertexConsumer, matrix, CARD_MAX, CARD_MIN, z, 1.0F, 1.0F, -1.0F);
            addArtVertex(vertexConsumer, matrix, CARD_MAX, CARD_MAX, z, 1.0F, 0.0F, -1.0F);
            addArtVertex(vertexConsumer, matrix, CARD_MIN, CARD_MAX, z, 0.0F, 0.0F, -1.0F);
        } else {
            addArtVertex(vertexConsumer, matrix, CARD_MIN, CARD_MAX, z, 0.0F, 0.0F, 1.0F);
            addArtVertex(vertexConsumer, matrix, CARD_MAX, CARD_MAX, z, 1.0F, 0.0F, 1.0F);
            addArtVertex(vertexConsumer, matrix, CARD_MAX, CARD_MIN, z, 1.0F, 1.0F, 1.0F);
            addArtVertex(vertexConsumer, matrix, CARD_MIN, CARD_MIN, z, 0.0F, 1.0F, 1.0F);
        }
    }

    private static void addArtVertex(VertexConsumer vertexConsumer, Matrix4f matrix, float x, float y, float z, float u, float v, float normalZ) {
        vertexConsumer.addVertex(matrix, x, y, z)
                .setColor(255, 255, 255, 255)
                .setUv(u, v)
                .setOverlay(OverlayTexture.NO_OVERLAY)
                .setLight(LightTexture.FULL_BRIGHT)
                .setNormal(0.0F, 0.0F, normalZ);
    }

    private static ResourceLocation artTexture(CardDefinition card) {
        ResourceLocation id = card.id();
        return ResourceLocation.fromNamespaceAndPath(id.getNamespace(), "textures/item/cards/" + id.getPath() + ".png");
    }

    private static ResourceLocation backTexture() {
        return ResourceLocation.fromNamespaceAndPath("minetriad", "textures/item/card_back.png");
    }

    public record CardPalette(float[] border, float[] face) {
    }

    private record CardLayout(float topY, float bottomY, float sideX, float sideY, float valueScale) {
        private static final CardLayout BOARD = new CardLayout(0.41F, -0.29F, 0.35F, 0.06F, 0.03F);
        private static final CardLayout ITEM = new CardLayout(0.26F, -0.17F, 0.21F, 0.03F, 0.03F);

        private static CardLayout forDisplayContext(ItemDisplayContext displayContext) {
            return switch (displayContext) {
                case GUI, GROUND, FIXED, FIRST_PERSON_LEFT_HAND, FIRST_PERSON_RIGHT_HAND,
                        THIRD_PERSON_LEFT_HAND, THIRD_PERSON_RIGHT_HAND -> ITEM;
                default -> BOARD;
            };
        }
    }
}
