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

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;

/**
 * Container for a loaded OBJ model.
 * Holds faces grouped by material and material definitions.
 * Immutable after construction.
 */
public class ObjModel {

    private final Map<String, List<ObjFace>> facesByMaterial;
    private final Map<String, ObjMaterial> materials;
    private final int totalFaces;
    private final int totalVertices;

    /** Base path for resolving relative texture paths (e.g., "tiedup:textures/models/obj/") */
    @Nullable
    private final String textureBasePath;

    // Bounding box (AABB)
    private final float minX, minY, minZ;
    private final float maxX, maxY, maxZ;

    private ObjModel(Builder builder) {
        this.facesByMaterial = Collections.unmodifiableMap(
            new HashMap<>(builder.facesByMaterial)
        );
        this.materials = Collections.unmodifiableMap(
            new HashMap<>(builder.materials)
        );
        this.textureBasePath = builder.textureBasePath;

        // Calculate totals
        int faces = 0;
        for (List<ObjFace> faceList : this.facesByMaterial.values()) {
            faces += faceList.size();
        }
        this.totalFaces = faces;
        this.totalVertices = faces * 3;

        // Calculate AABB
        float minX = Float.MAX_VALUE,
            minY = Float.MAX_VALUE,
            minZ = Float.MAX_VALUE;
        float maxX = -Float.MAX_VALUE,
            maxY = -Float.MAX_VALUE,
            maxZ = -Float.MAX_VALUE;

        for (List<ObjFace> faceList : this.facesByMaterial.values()) {
            for (ObjFace face : faceList) {
                for (ObjVertex v : face.vertices()) {
                    minX = Math.min(minX, v.x());
                    minY = Math.min(minY, v.y());
                    minZ = Math.min(minZ, v.z());
                    maxX = Math.max(maxX, v.x());
                    maxY = Math.max(maxY, v.y());
                    maxZ = Math.max(maxZ, v.z());
                }
            }
        }

        this.minX = minX == Float.MAX_VALUE ? 0 : minX;
        this.minY = minY == Float.MAX_VALUE ? 0 : minY;
        this.minZ = minZ == Float.MAX_VALUE ? 0 : minZ;
        this.maxX = maxX == -Float.MAX_VALUE ? 0 : maxX;
        this.maxY = maxY == -Float.MAX_VALUE ? 0 : maxY;
        this.maxZ = maxZ == -Float.MAX_VALUE ? 0 : maxZ;
    }

    /**
     * Get all faces grouped by material name.
     */
    public Map<String, List<ObjFace>> getFacesByMaterial() {
        return facesByMaterial;
    }

    /**
     * Get a material by name.
     * Returns default white material if not found.
     */
    public ObjMaterial getMaterial(String name) {
        return materials.getOrDefault(name, ObjMaterial.DEFAULT);
    }

    /**
     * Get all materials.
     */
    public Map<String, ObjMaterial> getMaterials() {
        return materials;
    }

    /**
     * Get total number of triangles in the model.
     */
    public int getTotalFaces() {
        return totalFaces;
    }

    /**
     * Get total number of vertices (faces * 3).
     */
    public int getTotalVertices() {
        return totalVertices;
    }

    // AABB getters
    public float getMinX() {
        return minX;
    }

    public float getMinY() {
        return minY;
    }

    public float getMinZ() {
        return minZ;
    }

    public float getMaxX() {
        return maxX;
    }

    public float getMaxY() {
        return maxY;
    }

    public float getMaxZ() {
        return maxZ;
    }

    /**
     * Get the center point of the model's bounding box.
     */
    public float[] getCenter() {
        return new float[] {
            (minX + maxX) / 2f,
            (minY + maxY) / 2f,
            (minZ + maxZ) / 2f,
        };
    }

    /**
     * Get the dimensions of the bounding box.
     */
    public float[] getDimensions() {
        return new float[] { maxX - minX, maxY - minY, maxZ - minZ };
    }

    /**
     * Resolve a texture filename to a full ResourceLocation.
     * Uses the textureBasePath set during loading.
     *
     * @param filename The texture filename (e.g., "ball_gag.png")
     * @return Full ResourceLocation, or null if no base path is set
     */
    @Nullable
    public ResourceLocation resolveTexture(String filename) {
        if (textureBasePath == null || filename == null) {
            return null;
        }
        return ResourceLocation.fromNamespaceAndPath(
            "tiedup",
            textureBasePath + filename
        );
    }

    /**
     * Resolve a texture filename with a color suffix inserted before the extension.
     * Example: "texture.png" + "red" -> "texture_red.png"
     *
     * @param filename The base texture filename (e.g., "texture.png")
     * @param colorSuffix The color suffix to insert (e.g., "red"), or null for no suffix
     * @return Full ResourceLocation with color suffix, or null if no base path is set
     */
    @Nullable
    public ResourceLocation resolveTextureWithColorSuffix(
        String filename,
        @Nullable String colorSuffix
    ) {
        if (textureBasePath == null || filename == null) {
            return null;
        }
        if (colorSuffix == null || colorSuffix.isEmpty()) {
            return resolveTexture(filename);
        }
        // Insert color suffix before extension: "texture.png" -> "texture_red.png"
        int dotIndex = filename.lastIndexOf('.');
        String newFilename;
        if (dotIndex > 0) {
            newFilename =
                filename.substring(0, dotIndex) +
                "_" +
                colorSuffix +
                filename.substring(dotIndex);
        } else {
            newFilename = filename + "_" + colorSuffix;
        }
        return ResourceLocation.fromNamespaceAndPath(
            "tiedup",
            textureBasePath + newFilename
        );
    }

    /**
     * Get the texture base path.
     */
    @Nullable
    public String getTextureBasePath() {
        return textureBasePath;
    }

    /**
     * Builder for constructing ObjModel instances.
     */
    public static class Builder {

        private final Map<String, List<ObjFace>> facesByMaterial =
            new HashMap<>();
        private final Map<String, ObjMaterial> materials = new HashMap<>();
        private String textureBasePath;

        public Builder addFaces(String materialName, List<ObjFace> faces) {
            if (faces != null && !faces.isEmpty()) {
                facesByMaterial.put(materialName, List.copyOf(faces));
            }
            return this;
        }

        public Builder addMaterial(ObjMaterial material) {
            if (material != null) {
                materials.put(material.name(), material);
            }
            return this;
        }

        public Builder setTextureBasePath(String path) {
            this.textureBasePath = path;
            return this;
        }

        public ObjModel build() {
            return new ObjModel(this);
        }
    }

    /**
     * Create a new builder.
     */
    public static Builder builder() {
        return new Builder();
    }
}
