// src/Scene.cpp

#include "Scene.h"
#include "Shader.h"
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>

// Add a node with a full transform matrix
size_t Scene::AddNode(std::shared_ptr<Mesh> mesh, const glm::mat4& transform) {
    SceneNode node;
    node.mesh      = std::move(mesh);
    node.transform = transform;
    node.UpdateTransform();
    nodes_.push_back(node);
    node_visibility_cache_.push_back(true);
    return nodes_.size() - 1;
}

// Add a node with position/rotation/scale decomposition
size_t Scene::AddNode(std::shared_ptr<Mesh> mesh,
                      const glm::vec3& position,
                      const glm::vec3& rotation,
                      const glm::vec3& scale) {
    SceneNode node;
    node.mesh     = std::move(mesh);
    node.position = position;
    node.rotation = rotation;
    node.scale    = scale;
    node.UpdateTransform();
    nodes_.push_back(node);
    node_visibility_cache_.push_back(true);
    return nodes_.size() - 1;
}

void Scene::SceneNode::UpdateTransform() {
    // Create transformation matrix from position, rotation, and scale
    glm::mat4 T = glm::translate(glm::mat4(1.0f), position);
    glm::mat4 R = glm::rotate(glm::mat4(1.0f), rotation.x, glm::vec3(1, 0, 0));
    R = glm::rotate(R, rotation.y, glm::vec3(0, 1, 0));
    R = glm::rotate(R, rotation.z, glm::vec3(0, 0, 1));
    glm::mat4 S = glm::scale(glm::mat4(1.0f), scale);
    
    transform = T * R * S;
}

void Scene::RemoveNode(size_t index) {
    if (index < nodes_.size()) {
        nodes_.erase(nodes_.begin() + index);
        node_visibility_cache_.erase(node_visibility_cache_.begin() + index);
    }
}

void Scene::SetNodeTransform(size_t index, const glm::mat4& transform) {
    if (index < nodes_.size()) {
        nodes_[index].transform = transform;
        nodes_[index].UpdateTransform();
    }
}

void Scene::SetNodePosition(size_t index, const glm::vec3& position) {
    if (index < nodes_.size()) {
        nodes_[index].position = position;
        nodes_[index].UpdateTransform();
    }
}

void Scene::SetNodeRotation(size_t index, const glm::vec3& rotation) {
    if (index < nodes_.size()) {
        nodes_[index].rotation = rotation;
        nodes_[index].UpdateTransform();
    }
}

void Scene::SetNodeScale(size_t index, const glm::vec3& scale) {
    if (index < nodes_.size()) {
        nodes_[index].scale = scale;
        nodes_[index].UpdateTransform();
    }
}

void Scene::SetNodeVisibility(size_t index, bool visible) {
    if (index < nodes_.size()) {
        nodes_[index].visible = visible;
        node_visibility_cache_[index] = visible;
    }
}

size_t Scene::AddDirectionalLight(const glm::vec3& direction,
                                  const glm::vec3& color,
                                  float intensity) {
    Light light;
    light.type      = Light::Type::Directional;
    light.direction = direction;
    light.color     = color;
    light.intensity = intensity;
    lights_.push_back(light);
    return lights_.size() - 1;
}

size_t Scene::AddPointLight(const glm::vec3& position,
                            const glm::vec3& color,
                            float intensity) {
    Light light;
    light.type      = Light::Type::Point;
    light.position  = position;
    light.color     = color;
    light.intensity = intensity;
    lights_.push_back(light);
    return lights_.size() - 1;
}

size_t Scene::AddSpotLight(const glm::vec3& position,
                           const glm::vec3& direction,
                           const glm::vec3& color,
                           float intensity,
                           float inner_cutoff,
                           float outer_cutoff) {
    Light light;
    light.type        = Light::Type::Spot;
    light.position    = position;
    light.direction   = direction;
    light.color       = color;
    light.intensity   = intensity;
    light.inner_cutoff = inner_cutoff;
    light.outer_cutoff = outer_cutoff;
    lights_.push_back(light);
    return lights_.size() - 1;
}

void Scene::RemoveLight(size_t index) {
    if (index < lights_.size()) {
        lights_.erase(lights_.begin() + index);
    }
}

void Scene::SetLightEnabled(size_t index, bool enabled) {
    if (index < lights_.size()) {
        lights_[index].enabled = enabled;
    }
}

void Scene::Render(Shader& shader,
                   const glm::mat4& view_matrix,     // Changed from 'view'
                   const glm::mat4& projection_matrix) {  // Changed from 'proj'
    shader.Bind();
    for (size_t i = 0; i < nodes_.size(); ++i) {
        const auto& node = nodes_[i];
        if (!node.visible) continue;
        // Set model matrix
        shader.SetUniform("u_Model", node.transform);
        // Compute normal matrix
        glm::mat3 normalMat = glm::inverseTranspose(glm::mat3(node.transform));
        shader.SetUniform("u_NormalMatrix", normalMat);
        // Set view/projection
        shader.SetUniform("u_View", view_matrix);           // Updated variable name
        shader.SetUniform("u_Projection", projection_matrix); // Updated variable name
        // Draw mesh
        node.mesh->Draw();
    }
}

void Scene::RenderShadowMap(Shader& shader,  // Remove const here
                            const glm::mat4& lightSpace) {
    shader.Bind();
    shader.SetUniform("u_LightSpace", lightSpace);
    for (auto& node : nodes_) {
        if (!node.visible) continue;
        shader.SetUniform("u_Model", node.transform);
        node.mesh->Draw();
    }
}

void Scene::Clear() {
    nodes_.clear();
    lights_.clear();
    node_visibility_cache_.clear();
}

void Scene::PrintSceneInfo() const {
    std::cout << "Scene has " 
              << nodes_.size() << " nodes and " 
              << lights_.size() << " lights.\n";
}

void Scene::SetAllNodesVisibility(bool visible) {
    for (size_t i = 0; i < nodes_.size(); ++i) {
        nodes_[i].visible = visible;
        node_visibility_cache_[i] = visible;
    }
}

void Scene::UpdateAllNodeTransforms() {
    for (auto& node : nodes_) {
        node.UpdateTransform();
    }
}

void Scene::FrustumCull(const glm::mat4& vp) {
    for (size_t i = 0; i < nodes_.size(); ++i) {
        node_visibility_cache_[i] = IsNodeInFrustum(nodes_[i], vp);
        nodes_[i].visible = node_visibility_cache_[i];
    }
}

size_t Scene::GetVisibleNodeCount() const {
    return std::count(node_visibility_cache_.begin(),
                      node_visibility_cache_.end(), true);
}

bool Scene::IsNodeInFrustum(const SceneNode& node, const glm::mat4& vp) const {
    // TODO: Implement proper frustum culling
    // For now, just return true (no culling)
    return true;
}
