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

import com.tiedup.remake.core.TiedUpMod;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import org.jetbrains.annotations.Nullable;

/**
 * Parses .obj and .mtl files from ResourceLocations.
 * Supports:
 * - Vertex positions (v)
 * - Texture coordinates (vt)
 * - Vertex normals (vn)
 * - Faces (f) with v/vt/vn format, triangles and quads
 * - Material library (mtllib)
 * - Material assignment (usemtl)
 * - MTL diffuse color (Kd)
 */
public class ObjModelLoader {

    private ObjModelLoader() {
        // Utility class
    }

    /**
     * Load an OBJ model from a ResourceLocation.
     *
     * @param location ResourceLocation pointing to the .obj file
     * @return Loaded ObjModel, or null if loading failed
     */
    @Nullable
    public static ObjModel load(ResourceLocation location) {
        ResourceManager resourceManager =
            Minecraft.getInstance().getResourceManager();

        try {
            Optional<Resource> resourceOpt = resourceManager.getResource(
                location
            );
            if (resourceOpt.isEmpty()) {
                TiedUpMod.LOGGER.warn("OBJ file not found: {}", location);
                return null;
            }

            Resource resource = resourceOpt.get();

            // Parse context
            List<float[]> positions = new ArrayList<>();
            List<float[]> texCoords = new ArrayList<>();
            List<float[]> normals = new ArrayList<>();
            Map<String, List<ObjFace>> facesByMaterial = new HashMap<>();
            Map<String, ObjMaterial> materials = new HashMap<>();

            String currentMaterial = "default";
            String mtlLib = null;

            // Parse OBJ file
            try (
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(
                        resource.open(),
                        StandardCharsets.UTF_8
                    )
                )
            ) {
                String line;
                while ((line = reader.readLine()) != null) {
                    line = line.trim();
                    if (line.isEmpty() || line.startsWith("#")) {
                        continue;
                    }

                    String[] parts = line.split("\\s+");
                    if (parts.length == 0) continue;

                    switch (parts[0]) {
                        case "v" -> {
                            // Vertex position: v x y z
                            if (parts.length >= 4) {
                                positions.add(
                                    new float[] {
                                        parseFloat(parts[1]),
                                        parseFloat(parts[2]),
                                        parseFloat(parts[3]),
                                    }
                                );
                            }
                        }
                        case "vt" -> {
                            // Texture coordinate: vt u v
                            if (parts.length >= 3) {
                                texCoords.add(
                                    new float[] {
                                        parseFloat(parts[1]),
                                        parseFloat(parts[2]),
                                    }
                                );
                            }
                        }
                        case "vn" -> {
                            // Vertex normal: vn x y z
                            if (parts.length >= 4) {
                                normals.add(
                                    new float[] {
                                        parseFloat(parts[1]),
                                        parseFloat(parts[2]),
                                        parseFloat(parts[3]),
                                    }
                                );
                            }
                        }
                        case "mtllib" -> {
                            // Material library: mtllib filename.mtl
                            if (parts.length >= 2) {
                                mtlLib = parts[1];
                            }
                        }
                        case "usemtl" -> {
                            // Use material: usemtl MaterialName
                            if (parts.length >= 2) {
                                currentMaterial = parts[1];
                            }
                        }
                        case "f" -> {
                            // Face: f v/vt/vn v/vt/vn v/vt/vn [v/vt/vn]
                            List<ObjVertex> faceVerts = new ArrayList<>();
                            for (int i = 1; i < parts.length; i++) {
                                ObjVertex vert = parseVertex(
                                    parts[i],
                                    positions,
                                    texCoords,
                                    normals
                                );
                                if (vert != null) {
                                    faceVerts.add(vert);
                                }
                            }

                            // Triangulate if needed (quad -> 2 triangles)
                            List<ObjFace> faces = triangulate(faceVerts);
                            facesByMaterial
                                .computeIfAbsent(currentMaterial, k ->
                                    new ArrayList<>()
                                )
                                .addAll(faces);
                        }
                    }
                }
            }

            // Load MTL file if referenced
            if (mtlLib != null) {
                ResourceLocation mtlLocation = resolveMtlPath(location, mtlLib);
                Map<String, ObjMaterial> loadedMaterials = loadMtl(mtlLocation);
                materials.putAll(loadedMaterials);
            }

            // Build and return model
            ObjModel.Builder builder = ObjModel.builder();
            for (Map.Entry<
                String,
                List<ObjFace>
            > entry : facesByMaterial.entrySet()) {
                builder.addFaces(entry.getKey(), entry.getValue());
            }
            for (ObjMaterial mat : materials.values()) {
                builder.addMaterial(mat);
            }

            // Set texture base path (textures are now in the same directory as the OBJ)
            String objPath = location.getPath();
            int lastSlash = objPath.lastIndexOf('/');
            String directory =
                lastSlash >= 0 ? objPath.substring(0, lastSlash + 1) : "";
            // Textures are in the same folder as the OBJ file
            String textureBasePath = directory;
            builder.setTextureBasePath(textureBasePath);

            ObjModel model = builder.build();
            TiedUpMod.LOGGER.info(
                "Loaded OBJ model: {} ({} faces, {} materials)",
                location,
                model.getTotalFaces(),
                materials.size()
            );

            return model;
        } catch (Exception e) {
            TiedUpMod.LOGGER.error("Failed to load OBJ model: {}", location, e);
            return null;
        }
    }

    /**
     * Parse a single vertex from face data.
     * Format: v/vt/vn or v//vn or v/vt or v
     */
    @Nullable
    private static ObjVertex parseVertex(
        String data,
        List<float[]> positions,
        List<float[]> texCoords,
        List<float[]> normals
    ) {
        String[] indices = data.split("/");
        if (indices.length == 0) return null;

        // Position index (required)
        int posIdx = parseInt(indices[0]) - 1; // OBJ indices are 1-based
        if (posIdx < 0 || posIdx >= positions.size()) return null;
        float[] pos = positions.get(posIdx);

        // Texture coordinate index (optional)
        float u = 0,
            v = 0;
        if (indices.length >= 2 && !indices[1].isEmpty()) {
            int texIdx = parseInt(indices[1]) - 1;
            if (texIdx >= 0 && texIdx < texCoords.size()) {
                float[] tex = texCoords.get(texIdx);
                u = tex[0];
                v = tex[1];
            }
        }

        // Normal index (optional)
        float nx = 0,
            ny = 1,
            nz = 0;
        if (indices.length >= 3 && !indices[2].isEmpty()) {
            int normIdx = parseInt(indices[2]) - 1;
            if (normIdx >= 0 && normIdx < normals.size()) {
                float[] norm = normals.get(normIdx);
                nx = norm[0];
                ny = norm[1];
                nz = norm[2];
            }
        }

        return new ObjVertex(pos[0], pos[1], pos[2], u, v, nx, ny, nz);
    }

    /**
     * Triangulate a face (convert quad to 2 triangles).
     */
    private static List<ObjFace> triangulate(List<ObjVertex> vertices) {
        List<ObjFace> result = new ArrayList<>();

        if (vertices.size() < 3) {
            return result;
        }

        // Triangle
        if (vertices.size() == 3) {
            result.add(
                new ObjFace(vertices.get(0), vertices.get(1), vertices.get(2))
            );
        }
        // Quad -> 2 triangles (fan triangulation)
        else if (vertices.size() >= 4) {
            ObjVertex v0 = vertices.get(0);
            for (int i = 1; i < vertices.size() - 1; i++) {
                result.add(
                    new ObjFace(v0, vertices.get(i), vertices.get(i + 1))
                );
            }
        }

        return result;
    }

    /**
     * Resolve the MTL file path relative to the OBJ file.
     */
    private static ResourceLocation resolveMtlPath(
        ResourceLocation objLocation,
        String mtlFileName
    ) {
        String objPath = objLocation.getPath();
        int lastSlash = objPath.lastIndexOf('/');
        String directory =
            lastSlash >= 0 ? objPath.substring(0, lastSlash + 1) : "";
        return ResourceLocation.fromNamespaceAndPath(
            objLocation.getNamespace(),
            directory + mtlFileName
        );
    }

    /**
     * Load materials from an MTL file.
     */
    private static Map<String, ObjMaterial> loadMtl(ResourceLocation location) {
        Map<String, ObjMaterial> materials = new HashMap<>();
        ResourceManager resourceManager =
            Minecraft.getInstance().getResourceManager();

        try {
            Optional<Resource> resourceOpt = resourceManager.getResource(
                location
            );
            if (resourceOpt.isEmpty()) {
                TiedUpMod.LOGGER.warn("MTL file not found: {}", location);
                return materials;
            }

            Resource resource = resourceOpt.get();
            String currentName = null;
            float r = 1,
                g = 1,
                b = 1;
            String texturePath = null;

            try (
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(
                        resource.open(),
                        StandardCharsets.UTF_8
                    )
                )
            ) {
                String line;
                while ((line = reader.readLine()) != null) {
                    line = line.trim();
                    if (line.isEmpty() || line.startsWith("#")) {
                        continue;
                    }

                    String[] parts = line.split("\\s+");
                    if (parts.length == 0) continue;

                    switch (parts[0]) {
                        case "newmtl" -> {
                            // Save previous material
                            if (currentName != null) {
                                materials.put(
                                    currentName,
                                    new ObjMaterial(
                                        currentName,
                                        r,
                                        g,
                                        b,
                                        texturePath
                                    )
                                );
                            }
                            // Start new material
                            currentName =
                                parts.length >= 2 ? parts[1] : "unnamed";
                            r = 1;
                            g = 1;
                            b = 1; // Reset to default
                            texturePath = null;
                        }
                        case "Kd" -> {
                            // Diffuse color: Kd r g b
                            if (parts.length >= 4) {
                                r = parseFloat(parts[1]);
                                g = parseFloat(parts[2]);
                                b = parseFloat(parts[3]);
                            }
                        }
                        case "map_Kd" -> {
                            // Diffuse texture map: map_Kd filename.png
                            if (parts.length >= 2) {
                                texturePath = parts[1];
                            }
                        }
                    }
                }

                // Save last material
                if (currentName != null) {
                    materials.put(
                        currentName,
                        new ObjMaterial(currentName, r, g, b, texturePath)
                    );
                }
            }

            TiedUpMod.LOGGER.debug(
                "Loaded {} materials from MTL: {}",
                materials.size(),
                location
            );
        } catch (IOException e) {
            TiedUpMod.LOGGER.error("Failed to load MTL file: {}", location, e);
        }

        return materials;
    }

    private static float parseFloat(String s) {
        try {
            return Float.parseFloat(s);
        } catch (NumberFormatException e) {
            return 0f;
        }
    }

    private static int parseInt(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}
