// File: src/Renderer.cpp
#include "Renderer.h"
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>

namespace OX
{
    // ——— Camera2D implementation (unchanged) ———
    Camera2D::Camera2D(float left, float right, float bottom, float top)
        : _left(left), _right(right), _bottom(bottom), _top(top)
    {
        Recalculate();
    }

    void Camera2D::SetPosition(const glm::vec2 &pos)
    {
        _position = pos;
        Recalculate();
    }

    void Camera2D::SetZoom(float z)
    {
        _zoom = z;
        Recalculate();
    }

    void Camera2D::Recalculate()
    {
        float halfW = (_right - _left) * 0.5f / _zoom;
        float halfH = (_top - _bottom) * 0.5f / _zoom;
        glm::mat4 proj = glm::ortho(-halfW, halfW, -halfH, halfH, -1.0f, 1.0f);
        glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(-_position, 0.0f));
        _viewProj = proj * view;
    }

    const glm::mat4 &Camera2D::GetViewProjection() const { return _viewProj; }

    // ——— Renderer ———
    Renderer::~Renderer()
    {
        if (m_quadVAO)
            glDeleteVertexArrays(1, &m_quadVAO);
        if (m_quadVBO)
            glDeleteBuffers(1, &m_quadVBO);
        if (m_fbo)
            glDeleteFramebuffers(1, &m_fbo);
        if (m_colorTex) glDeleteTextures(1, &m_colorTex);
        if (m_depthRBO)
            glDeleteRenderbuffers(1, &m_depthRBO);
    }

    void Renderer::Init(int targetWidth, int targetHeight)
    {
        // create offscreen FBO & attachments
        CreateFramebuffer(targetWidth, targetHeight);

        // compile our sprite shader
        static const char *vs = R"GLSL(
        #version 330 core
        layout(location = 0) in vec2 aPos;
        layout(location = 1) in vec2 aUV;
        uniform mat4 u_ViewProj;
        uniform vec2 u_Offset;
        uniform vec2 u_Scale;
        out vec2 vUV;
        void main(){
            vUV = aUV;
            vec2 pos = aPos * u_Scale + u_Offset;
            gl_Position = u_ViewProj * vec4(pos,0,1);
        }
    )GLSL";
        static const char *fs = R"GLSL(
        #version 330 core
        in vec2 vUV;
        out vec4 FragColor;
        uniform sampler2D u_Texture;
        void main(){
            FragColor = texture(u_Texture, vUV);
        }
    )GLSL";
        m_shader.SetName("SpriteShader");
        m_shader.LoadFromSource(vs, fs);

        CreateQuad();
    }

    void Renderer::CreateFramebuffer(int width, int height)
    {
        size = {width, height};

        // cleanup old
        if (m_fbo)
            glDeleteFramebuffers(1, &m_fbo);
        if (m_colorTex) glDeleteTextures(1, &m_colorTex);
        if (m_depthRBO)
            glDeleteRenderbuffers(1, &m_depthRBO);

        // color texture
        glGenTextures(1, &m_colorTex);
        glBindTexture(GL_TEXTURE_2D, m_colorTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height,
                     0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // depth renderbuffer
        glGenRenderbuffers(1, &m_depthRBO);
        glBindRenderbuffer(GL_RENDERBUFFER, m_depthRBO);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);

        // framebuffer
        glGenFramebuffers(1, &m_fbo);
        glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                               GL_TEXTURE_2D, m_colorTex, 0);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
                                  GL_RENDERBUFFER, m_depthRBO);

        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            std::cerr << "[Renderer] Framebuffer not complete!\n";

        // unbind
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    void Renderer::ResizeTarget(int width, int height)
    {
        OX_PROFILE_FUNCTION();
        if (size == Vec2i{width, height}) {
            return;
        }
        CreateFramebuffer(width, height);
    }

    void Renderer::BeginScene(const Camera2D &camera)
    {
        OX_PROFILE_FUNCTION();
        // bind offscreen
        glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
        // clear
        glViewport(0, 0, size.x, size.y);
        glClearColor(0.1f, 0.1f, 0.1f, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // setup shader & camera
        m_viewProj = camera.GetViewProjection();
        m_shader.Use();
        m_shader.SetMat4("u_ViewProj", m_viewProj);
        m_shader.SetInt("u_Texture", 0);
    }

    void Renderer::DrawSprite(const Sprite &s)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, s.textureID);

        m_shader.SetVec2("u_Offset", s.position);
        m_shader.SetVec2("u_Scale", s.size);

        glBindVertexArray(m_quadVAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
    }

    void Renderer::EndScene()
    {
        // unbind FBO → subsequent draws go to default framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    void Renderer::CreateQuad()
    {
        float verts[] = {
            -0.5f, -0.5f, 0, 0,
            0.5f, -0.5f, 1, 0,
            0.5f, 0.5f, 1, 1,
            -0.5f, 0.5f, 0, 1
        };
        unsigned int idx[] = {0, 1, 2, 2, 3, 0};

        glGenVertexArrays(1, &m_quadVAO);
        glGenBuffers(1, &m_quadVBO);

        glBindVertexArray(m_quadVAO);
        glBindBuffer(GL_ARRAY_BUFFER, m_quadVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);

        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2,GL_FLOAT,GL_FALSE, 4 * sizeof(float), (void *) 0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2,GL_FLOAT,GL_FALSE, 4 * sizeof(float), (void *) (2 * sizeof(float)));

        glBindVertexArray(0);
    }
} // namespace OX
