//
// Created by sebastian on 30.07.26.
//

#include "ModelImporter.h"
#define TINYOBJLOADER_IMPLEMENTATION
#include <filesystem>
#include <unordered_set>

#include "tiny_obj_loader.h"
#include "loader/textures/TextureImporter.h"


engine::RawModelBundle engine::ModelImporter::importFromFile(const std::string& objPath, ModelSplitPolicy policy) {
    tinyobj::ObjReaderConfig config;
    config.mtl_search_path = std::filesystem::path(objPath).parent_path().string();
    config.triangulate = false; // GL braucht Dreiecke, keine n-Ecke

    tinyobj::ObjReader reader;
    if (!reader.ParseFromFile(objPath, config)) {
        throw std::runtime_error("OBJ-Import fehlgeschlagen '" + objPath + "': " + reader.Error());
    }

    const auto& attrib      = reader.GetAttrib();
    const auto& shapes      = reader.GetShapes();
    const auto& tinyMaterials = reader.GetMaterials();

    RawModelBundle bundle;
    bundle.model.name = std::filesystem::path(objPath).stem().string();

    // 1. Materialien übersetzen
    for (const auto& mat : tinyMaterials) {
        RawMaterialData material;
        material.name = mat.name;
        if (!mat.diffuse_texname.empty())  material.texturePaths[TextureType::Diffuse]  = mat.diffuse_texname;
        if (!mat.specular_texname.empty()) material.texturePaths[TextureType::Specular] = mat.specular_texname;
        if (!mat.bump_texname.empty())     material.texturePaths[TextureType::Normal]   = mat.bump_texname;
        if (!mat.emissive_texname.empty()) material.texturePaths[TextureType::Emissive] = mat.emissive_texname;
        if (!mat.alpha_texname.empty())    material.texturePaths[TextureType::Opacity]  = mat.alpha_texname;
        material.shininess = mat.shininess;
        material.opacity   = mat.dissolve; // 'd' in der .mtl
        bundle.materials.push_back(std::move(material));
    }

    // 2. Referenzierte Texturen dekodieren (dedupliziert innerhalb dieses Imports)
    std::unordered_set<std::string> seen;
    auto mtlDir = std::filesystem::path(objPath).parent_path();
    for (const auto& material : bundle.materials) {
        for (const auto& [type, relPath] : material.texturePaths) {
            if (seen.insert(relPath).second) {
                bundle.textures.push_back(TextureImporter::import((mtlDir / relPath).string()));
            }
        }
    }

    // 3. Shapes -> Objekte, Faces nach Material gruppieren
    for (const auto& shape : shapes) {
        RawObjectData object;
        object.name = shape.name.empty() ? "default" : shape.name;

        std::unordered_map<int, RawMeshSection> sectionsByMaterial;

        size_t indexOffset = 0;
        for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); ++f) {
            int faceVertexCount = shape.mesh.num_face_vertices[f];
            int materialId = shape.mesh.material_ids[f]; // -1 = kein Material zugewiesen

            auto& section = sectionsByMaterial[materialId];
            section.name = object.name;
            if (materialId >= 0) section.materialName = tinyMaterials[materialId].name;

            for (int v = 0; v < faceVertexCount; ++v) {
                tinyobj::index_t idx = shape.mesh.indices[indexOffset + v];

                section.vertices.insert(section.vertices.end(), {
                    attrib.vertices[3*idx.vertex_index + 0],
                    attrib.vertices[3*idx.vertex_index + 1],
                    attrib.vertices[3*idx.vertex_index + 2]
                });
                if (idx.normal_index >= 0) {
                    section.normals.insert(section.normals.end(), {
                        attrib.normals[3*idx.normal_index + 0],
                        attrib.normals[3*idx.normal_index + 1],
                        attrib.normals[3*idx.normal_index + 2]
                    });
                }
                if (idx.texcoord_index >= 0) {
                    section.textureCoords.insert(section.textureCoords.end(), {
                        attrib.texcoords[2*idx.texcoord_index + 0],
                        attrib.texcoords[2*idx.texcoord_index + 1]
                    });
                }
                section.indices.push_back(static_cast<int>(section.indices.size()));
            }
            indexOffset += faceVertexCount;
        }

        for (auto& [materialId, section] : sectionsByMaterial) {
            object.subModels.push_back(std::move(section));
        }
        bundle.model.objects.push_back(std::move(object));
    }
    bundle.model.splitPolicy = policy;
    return bundle;
}
