package com.tiedup.remake.client.renderer.obj;

import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormat;
import java.util.List;
import java.util.Map;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.joml.Matrix3f;
import org.joml.Matrix4f;

/**
 * Renders OBJ models using Minecraft's rendering system.
 * Uses VertexConsumer for hardware-accelerated rendering.
 * Client-side only.
 *
 * <p><b>Important:</b> Uses TRIANGLES mode RenderType, not QUADS,
 * because OBJ models are triangulated during loading.
 */
@OnlyIn(Dist.CLIENT)
public class ObjModelRenderer extends RenderStateShard {

    /** White texture for vertex color rendering */
    public static final ResourceLocation WHITE_TEXTURE =
        ResourceLocation.fromNamespaceAndPath(
            "tiedup",
            "models/obj/shared/white.png"
        );

    private ObjModelRenderer() {
        super("tiedup_obj_renderer", () -> {}, () -> {});
        // Utility class - extends RenderStateShard to access protected members
    }

    /**
     * Create a TRIANGLES-mode RenderType for OBJ rendering.
     * Standard entityCutoutNoCull uses QUADS which causes spiky artifacts
     * when we submit triangles.
     */
    private static RenderType createTriangleRenderType(
        ResourceLocation texture
    ) {
        RenderType.CompositeState state = RenderType.CompositeState.builder()
            .setShaderState(RENDERTYPE_ENTITY_CUTOUT_NO_CULL_SHADER)
            .setTextureState(
                new RenderStateShard.TextureStateShard(texture, false, false)
            )
            .setTransparencyState(NO_TRANSPARENCY)
            .setCullState(NO_CULL)
            .setLightmapState(LIGHTMAP)
            .setOverlayState(OVERLAY)
            .createCompositeState(true);

        return RenderType.create(
            "tiedup_obj_triangles",
            DefaultVertexFormat.NEW_ENTITY,
            VertexFormat.Mode.TRIANGLES, // Key fix: TRIANGLES not QUADS
            1536,
            true,
            false,
            state
        );
    }

    /**
     * Render an OBJ model with vertex colors from materials.
     *
     * @param model The OBJ model to render
     * @param poseStack The current pose stack (with transformations applied)
     * @param buffer The multi-buffer source
     * @param packedLight Packed light value
     * @param packedOverlay Packed overlay value (for hit flash etc.)
     */
    public static void render(
        ObjModel model,
        PoseStack poseStack,
        MultiBufferSource buffer,
        int packedLight,
        int packedOverlay
    ) {
        render(
            model,
            poseStack,
            buffer,
            packedLight,
            packedOverlay,
            1.0f,
            1.0f,
            1.0f,
            1.0f
        );
    }

    /**
     * Render an OBJ model with textures from materials (map_Kd) or vertex colors as fallback.
     *
     * @param model The OBJ model to render
     * @param poseStack The current pose stack (with transformations applied)
     * @param buffer The multi-buffer source
     * @param packedLight Packed light value
     * @param packedOverlay Packed overlay value (for hit flash etc.)
     * @param tintR Red tint multiplier (0-1)
     * @param tintG Green tint multiplier (0-1)
     * @param tintB Blue tint multiplier (0-1)
     * @param alpha Alpha value (0-1)
     */
    public static void render(
        ObjModel model,
        PoseStack poseStack,
        MultiBufferSource buffer,
        int packedLight,
        int packedOverlay,
        float tintR,
        float tintG,
        float tintB,
        float alpha
    ) {
        if (model == null) {
            return;
        }

        Matrix4f pose = poseStack.last().pose();
        Matrix3f normal = poseStack.last().normal();

        // Render faces grouped by material
        for (Map.Entry<String, List<ObjFace>> entry : model
            .getFacesByMaterial()
            .entrySet()) {
            ObjMaterial material = model.getMaterial(entry.getKey());

            // Determine texture and color based on material
            ResourceLocation texture;
            float r, g, b;

            if (material.hasTexture()) {
                // Use texture from map_Kd - resolve to full path
                texture = model.resolveTexture(material.texturePath());
                if (texture == null) {
                    texture = WHITE_TEXTURE;
                }
                // White color to let texture show through, with tint applied
                r = tintR;
                g = tintG;
                b = tintB;
            } else {
                // Use vertex color from Kd
                texture = WHITE_TEXTURE;
                r = material.r() * tintR;
                g = material.g() * tintG;
                b = material.b() * tintB;
            }

            // Get buffer for this material's texture (TRIANGLES mode)
            VertexConsumer vertexConsumer = buffer.getBuffer(
                createTriangleRenderType(texture)
            );

            for (ObjFace face : entry.getValue()) {
                ObjVertex[] verts = face.vertices();

                // Use vertex normals from the OBJ file for smooth shading
                for (ObjVertex vertex : verts) {
                    vertexConsumer
                        .vertex(pose, vertex.x(), vertex.y(), vertex.z())
                        .color(r, g, b, alpha)
                        .uv(vertex.u(), 1.0f - vertex.v())
                        .overlayCoords(packedOverlay)
                        .uv2(packedLight)
                        .normal(normal, vertex.nx(), vertex.ny(), vertex.nz())
                        .endVertex();
                }
            }
        }
    }

    /**
     * Render an OBJ model with selective material tinting.
     * Only materials in the tintMaterials set will be tinted.
     *
     * @param model The OBJ model to render
     * @param poseStack The current pose stack
     * @param buffer The multi-buffer source
     * @param packedLight Packed light value
     * @param packedOverlay Packed overlay value
     * @param tintR Red tint (0-1)
     * @param tintG Green tint (0-1)
     * @param tintB Blue tint (0-1)
     * @param tintMaterials Set of material names to apply tint to (e.g., "Ball")
     */
    public static void renderWithSelectiveTint(
        ObjModel model,
        PoseStack poseStack,
        MultiBufferSource buffer,
        int packedLight,
        int packedOverlay,
        float tintR,
        float tintG,
        float tintB,
        java.util.Set<String> tintMaterials
    ) {
        if (model == null) {
            return;
        }

        Matrix4f pose = poseStack.last().pose();
        Matrix3f normal = poseStack.last().normal();

        for (Map.Entry<String, List<ObjFace>> entry : model
            .getFacesByMaterial()
            .entrySet()) {
            String materialName = entry.getKey();
            ObjMaterial material = model.getMaterial(materialName);

            // Check if this material should be tinted
            boolean shouldTint = tintMaterials.contains(materialName);

            ResourceLocation texture;
            float r, g, b;

            if (material.hasTexture()) {
                texture = model.resolveTexture(material.texturePath());
                if (texture == null) {
                    texture = WHITE_TEXTURE;
                }
                // Apply tint only to specified materials
                if (shouldTint) {
                    r = tintR;
                    g = tintG;
                    b = tintB;
                } else {
                    r = 1.0f;
                    g = 1.0f;
                    b = 1.0f;
                }
            } else {
                // Vertex color from Kd
                texture = WHITE_TEXTURE;
                if (shouldTint) {
                    r = material.r() * tintR;
                    g = material.g() * tintG;
                    b = material.b() * tintB;
                } else {
                    r = material.r();
                    g = material.g();
                    b = material.b();
                }
            }

            VertexConsumer vertexConsumer = buffer.getBuffer(
                createTriangleRenderType(texture)
            );

            for (ObjFace face : entry.getValue()) {
                for (ObjVertex vertex : face.vertices()) {
                    vertexConsumer
                        .vertex(pose, vertex.x(), vertex.y(), vertex.z())
                        .color(r, g, b, 1.0f)
                        .uv(vertex.u(), 1.0f - vertex.v())
                        .overlayCoords(packedOverlay)
                        .uv2(packedLight)
                        .normal(normal, vertex.nx(), vertex.ny(), vertex.nz())
                        .endVertex();
                }
            }
        }
    }

    /**
     * Render an OBJ model with color-suffixed textures.
     * When colorSuffix is provided, ALL textured materials use texture_COLOR.png.
     *
     * @param model The OBJ model to render
     * @param poseStack The current pose stack
     * @param buffer The multi-buffer source
     * @param packedLight Packed light value
     * @param packedOverlay Packed overlay value
     * @param colorSuffix The color suffix (e.g., "red", "blue"), or null for default texture
     */
    public static void renderWithColoredTextures(
        ObjModel model,
        PoseStack poseStack,
        MultiBufferSource buffer,
        int packedLight,
        int packedOverlay,
        String colorSuffix
    ) {
        if (model == null) {
            return;
        }

        Matrix4f pose = poseStack.last().pose();
        Matrix3f normal = poseStack.last().normal();

        for (Map.Entry<String, List<ObjFace>> entry : model
            .getFacesByMaterial()
            .entrySet()) {
            ObjMaterial material = model.getMaterial(entry.getKey());

            ResourceLocation texture;

            if (material.hasTexture()) {
                // Apply color suffix to texture path if provided
                if (colorSuffix != null && !colorSuffix.isEmpty()) {
                    texture = model.resolveTextureWithColorSuffix(
                        material.texturePath(),
                        colorSuffix
                    );
                } else {
                    texture = model.resolveTexture(material.texturePath());
                }
                if (texture == null) {
                    texture = WHITE_TEXTURE;
                }
            } else {
                // No texture - use vertex color from Kd
                texture = WHITE_TEXTURE;
            }

            VertexConsumer vertexConsumer = buffer.getBuffer(
                createTriangleRenderType(texture)
            );

            // Determine vertex color - white for textured, Kd for untextured
            float r, g, b;
            if (material.hasTexture()) {
                r = 1.0f;
                g = 1.0f;
                b = 1.0f;
            } else {
                r = material.r();
                g = material.g();
                b = material.b();
            }

            for (ObjFace face : entry.getValue()) {
                for (ObjVertex vertex : face.vertices()) {
                    vertexConsumer
                        .vertex(pose, vertex.x(), vertex.y(), vertex.z())
                        .color(r, g, b, 1.0f)
                        .uv(vertex.u(), 1.0f - vertex.v())
                        .overlayCoords(packedOverlay)
                        .uv2(packedLight)
                        .normal(normal, vertex.nx(), vertex.ny(), vertex.nz())
                        .endVertex();
                }
            }
        }
    }

    /**
     * Render an OBJ model with a specific texture instead of vertex colors.
     *
     * @param model The OBJ model to render
     * @param poseStack The current pose stack (with transformations applied)
     * @param buffer The multi-buffer source
     * @param packedLight Packed light value
     * @param packedOverlay Packed overlay value
     * @param texture The texture to apply
     */
    public static void renderTextured(
        ObjModel model,
        PoseStack poseStack,
        MultiBufferSource buffer,
        int packedLight,
        int packedOverlay,
        ResourceLocation texture
    ) {
        if (model == null) {
            return;
        }

        Matrix4f pose = poseStack.last().pose();
        Matrix3f normal = poseStack.last().normal();

        // Use custom TRIANGLES-mode RenderType
        VertexConsumer vertexConsumer = buffer.getBuffer(
            createTriangleRenderType(texture)
        );

        // Render all faces with white color (let texture provide color)
        for (Map.Entry<String, List<ObjFace>> entry : model
            .getFacesByMaterial()
            .entrySet()) {
            for (ObjFace face : entry.getValue()) {
                ObjVertex[] verts = face.vertices();

                for (ObjVertex vertex : verts) {
                    vertexConsumer
                        .vertex(pose, vertex.x(), vertex.y(), vertex.z())
                        .color(1.0f, 1.0f, 1.0f, 1.0f)
                        .uv(vertex.u(), 1.0f - vertex.v())
                        .overlayCoords(packedOverlay)
                        .uv2(packedLight)
                        .normal(normal, vertex.nx(), vertex.ny(), vertex.nz())
                        .endVertex();
                }
            }
        }
    }
}
