#ifndef IMGUI_IMAGE_VIEWER_H
#define IMGUI_IMAGE_VIEWER_H

#include "app_image.h" // Needs the AppImage class definition
#include "imgui.h"
#define IMGUI_DEFINE_MATH_OPERATORS // Allows ImVec2 operators
#include "imgui_internal.h" // Need ImFloorSigned, ImClamp, ImMax, ImMin, ImAbs
#include <cmath>           // For pow, round, floor
#include <vector>
#include <cstdint>
#include <algorithm>       // For std::max, std::min

#define IMGUI_DEFINE_MATH_OPERATORS // Allows ImVec2 operators
// --- Graphics API ---
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <SDL_opengles2.h>
#else
#include <SDL_opengl.h>
#endif


// --- User Instructions ---
// (Same as before: Include after dependencies, define IMPLEMENTATION once)

class ImGuiImageViewer {
public:
    ImGuiImageViewer();
    ~ImGuiImageViewer();

    ImGuiImageViewer(const ImGuiImageViewer&) = delete;
    ImGuiImageViewer& operator=(const ImGuiImageViewer&) = delete;

    bool LoadImage(const AppImage& appImage);
    void UnloadImage();
    // Renders within the available space, maintaining aspect ratio.
    void Render(const ImVec2& availableSize);
    void ResetView(); // Resets zoom to 100%, centers view

    // --- Getters ---
    float GetZoom() const { return m_zoom; }
    // Pan offset returns the center of the view in UV coordinates (0.0-1.0)
    ImVec2 GetPanOffsetUV() const { return m_panOffsetUV; }
    bool IsImageLoaded() const { return m_textureId != 0; }
    // Get the raw OpenGL texture ID for external use
    GLuint GetTextureId() const { return m_textureId; }

private:
    GLuint m_textureId = 0;
    int m_textureWidth = 0;
    int m_textureHeight = 0;

    float m_zoom = 1.0f; // Scale factor (1.0 = 100%)
    // Pan offset: Center of the view in UV coordinates (0.0 to 1.0)
    ImVec2 m_panOffsetUV = ImVec2(0.5f, 0.5f);

    // Interaction state
    bool m_isDragging = false;
    ImVec2 m_dragStartMousePos = ImVec2(0, 0);
    ImVec2 m_dragStartPanOffsetUV = ImVec2(0, 0);

    // Configuration
    const float m_minZoom = 0.01f; // 1%
    const float m_maxZoom = 50.0f; // 5000%
    const float m_zoomSpeed = 1.1f;

    // Clamps pan offset (UV) based on the container size and zoom.
    void ClampPanOffset(const ImVec2& containerSize);
};


// ============================================================================
// =================== IMPLEMENTATION SECTION =================================
// ============================================================================
#ifdef IMGUI_IMAGE_VIEWER_IMPLEMENTATION

// --- Helper namespace ---
namespace ImGuiImageViewerUtil {
    // Linear float [0,1+] -> sRGB approx [0,1]
    inline float linear_to_srgb_approx(float linearVal) {
        if (linearVal <= 0.0f) return 0.0f;
        linearVal = std::fmax(0.0f, std::fmin(1.0f, linearVal)); // Clamp for display
        if (linearVal <= 0.0031308f) { return linearVal * 12.92f; }
        else { return 1.055f * std::pow(linearVal, 1.0f / 2.4f) - 0.055f; }
    }

    // Round float to nearest integer
    inline float Round(float f) { return ImFloor(f + 0.5f); }
} // namespace ImGuiImageViewerUtil


ImGuiImageViewer::ImGuiImageViewer() {}
ImGuiImageViewer::~ImGuiImageViewer() { UnloadImage(); }

void ImGuiImageViewer::UnloadImage() {
    if (m_textureId != 0) {
        glDeleteTextures(1, &m_textureId);
        m_textureId = 0;
        m_textureWidth = 0;
        m_textureHeight = 0;
        ResetView();
    }
}

bool ImGuiImageViewer::LoadImage(const AppImage& appImage) {
    // --- Basic Error Checking ---
    if (appImage.isEmpty() || appImage.getWidth() == 0 || appImage.getHeight() == 0) {
        UnloadImage(); return false;
    }
    if (!appImage.isLinear()) { /* Log warning */ }
    const int width = static_cast<int>(appImage.getWidth());
    const int height = static_cast<int>(appImage.getHeight());
    const int channels = static_cast<int>(appImage.getChannels());
    if (channels != 1 && channels != 3 && channels != 4) { UnloadImage(); return false; }
    const float* linearData = appImage.getData();

    // --- Prepare 8-bit sRGB data for OpenGL (RGBA format) ---
    GLenum internalFormat = GL_RGBA8; GLenum dataFormat = GL_RGBA; int outputChannels = 4;
    std::vector<unsigned char> textureData(static_cast<size_t>(width) * height * outputChannels);
    unsigned char* outPtr = textureData.data(); const float* inPtr = linearData;
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            float r=0,g=0,b=0,a=1; if(channels==1){r=g=b=*inPtr++;} else if(channels==3){r=*inPtr++;g=*inPtr++;b=*inPtr++;} else{r=*inPtr++;g=*inPtr++;b=*inPtr++;a=*inPtr++;}
            float sr=ImGuiImageViewerUtil::linear_to_srgb_approx(r), sg=ImGuiImageViewerUtil::linear_to_srgb_approx(g), sb=ImGuiImageViewerUtil::linear_to_srgb_approx(b); a=std::fmax(0.f,std::fmin(1.f,a));
            *outPtr++=static_cast<unsigned char>(std::max(0, std::min(255, static_cast<int>(ImGuiImageViewerUtil::Round(sr*255.f)))));
            *outPtr++=static_cast<unsigned char>(std::max(0, std::min(255, static_cast<int>(ImGuiImageViewerUtil::Round(sg*255.f)))));
            *outPtr++=static_cast<unsigned char>(std::max(0, std::min(255, static_cast<int>(ImGuiImageViewerUtil::Round(sb*255.f)))));
            *outPtr++=static_cast<unsigned char>(std::max(0, std::min(255, static_cast<int>(ImGuiImageViewerUtil::Round(a*255.f)))));
        }
    }

    // --- Upload to OpenGL Texture ---
    GLint lastTexture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTexture);
    if (m_textureId == 0) { glGenTextures(1, &m_textureId); }
    glBindTexture(GL_TEXTURE_2D, m_textureId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // No filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // No filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, dataFormat, GL_UNSIGNED_BYTE, textureData.data());
    glBindTexture(GL_TEXTURE_2D, lastTexture);

    m_textureWidth = width; m_textureHeight = height;
    ResetView(); // Reset view when loading a new image
    return true;
}

void ImGuiImageViewer::ResetView() {
    m_zoom = 1.0f;
    m_panOffsetUV = ImVec2(0.5f, 0.5f); // Center view
    m_isDragging = false;
}

// Clamp pan offset (UV coordinates)
void ImGuiImageViewer::ClampPanOffset(const ImVec2& containerSize) {
    if (m_textureWidth <= 0 || m_textureHeight <= 0 || m_zoom <= 0) return;

    // Size of the image content projected onto the screen at current zoom
    ImVec2 zoomedImageScreenSize = ImVec2(m_textureWidth * m_zoom, m_textureHeight * m_zoom);

    // Calculate the size of the visible area in UV coordinates
    ImVec2 viewSizeUV = containerSize / zoomedImageScreenSize; // If zoom=1, size=texSize, this is containerSize/texSize
    viewSizeUV.x = ImMin(viewSizeUV.x, 1.0f); // Cannot see more than 1.0 UV width
    viewSizeUV.y = ImMin(viewSizeUV.y, 1.0f); // Cannot see more than 1.0 UV height

    // Calculate min/max allowed pan values (center of view in UV space)
    ImVec2 minPanUV, maxPanUV;

    // If the zoomed image is smaller than the container, center it.
    // The pan offset (center of view) should be locked to 0.5 UV.
    if (zoomedImageScreenSize.x <= containerSize.x) {
        minPanUV.x = maxPanUV.x = 0.5f;
    } else {
        // Image is wider: Allow panning.
        // Min pan: Center of view is half the view-width (in UV) from the left edge (UV=0)
        minPanUV.x = viewSizeUV.x * 0.5f;
        // Max pan: Center of view is half the view-width (in UV) from the right edge (UV=1)
        maxPanUV.x = 1.0f - viewSizeUV.x * 0.5f;
    }

    if (zoomedImageScreenSize.y <= containerSize.y) {
        // Image is shorter: Center it.
        minPanUV.y = maxPanUV.y = 0.5f;
    } else {
        // Image is taller: Allow panning.
        minPanUV.y = viewSizeUV.y * 0.5f;
        maxPanUV.y = 1.0f - viewSizeUV.y * 0.5f;
    }

    // Apply clamping
    m_panOffsetUV.x = ImClamp(m_panOffsetUV.x, minPanUV.x, maxPanUV.x);
    m_panOffsetUV.y = ImClamp(m_panOffsetUV.y, minPanUV.y, maxPanUV.y);
}


void ImGuiImageViewer::Render(const ImVec2& availableSize) {
    if (m_textureId == 0 || m_textureWidth <= 0 || m_textureHeight <= 0) {
        ImGui::Dummy(availableSize); return;
    }

    ImGuiIO& io = ImGui::GetIO();
    ImGuiStyle& style = ImGui::GetStyle();

    // Begin a child window to establish a canvas with a background
    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.0f)); // Dark background for contrast
    ImGui::BeginChild("ImageViewerCanvas", availableSize, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration);

    ImVec2 containerSize = ImGui::GetContentRegionAvail();
    if (containerSize.x < 1.0f) containerSize.x = 1.0f;
    if (containerSize.y < 1.0f) containerSize.y = 1.0f;
    ImVec2 containerTopLeftScreen = ImGui::GetCursorScreenPos();

    // Calculate aspect-correct display size and padding within the container
    float imageAspect = static_cast<float>(m_textureWidth) / static_cast<float>(m_textureHeight);
    float containerAspect = containerSize.x / containerSize.y;
    ImVec2 displaySize = containerSize; // Final screen size of the image content
    ImVec2 displayPadding = ImVec2(0, 0); // Padding for letter/pillarboxing

    if (containerAspect > imageAspect) { // Letterbox: Container wider than image
        displaySize.x = containerSize.y * imageAspect;
        displayPadding.x = (containerSize.x - displaySize.x) * 0.5f;
    } else { // Pillarbox: Container taller than image (or same aspect)
        displaySize.y = containerSize.x / imageAspect;
        displayPadding.y = (containerSize.y - displaySize.y) * 0.5f;
    }
    // Round padding to avoid pixel jitter
    displayPadding.x = ImFloor(displayPadding.x);
    displayPadding.y = ImFloor(displayPadding.y);
    displaySize = containerSize - displayPadding * 2.0f; // Recalculate based on rounded padding


    // Screen position where the image content rendering starts
    ImVec2 displayTopLeftScreen = containerTopLeftScreen + displayPadding;

    // Invisible button covering the *entire container* for capturing inputs
    ImGui::SetCursorScreenPos(containerTopLeftScreen);
    ImGui::InvisibleButton("##canvas_interaction", containerSize);
    bool isHoveredOnCanvas = ImGui::IsItemHovered();
    bool isActiveOnCanvas = ImGui::IsItemActive();

    // Calculate mouse position relative to the *display area* top-left corner
    ImVec2 mousePosDisplay = io.MousePos - displayTopLeftScreen;

    // Check if the mouse is actually over the image content, not the padding
    bool isMouseInDisplayArea = (mousePosDisplay.x >= 0.0f && mousePosDisplay.x < displaySize.x &&
                                 mousePosDisplay.y >= 0.0f && mousePosDisplay.y < displaySize.y);

    // 1. Zooming (centered on mouse, only when hovering the image content)
    if (isHoveredOnCanvas && isMouseInDisplayArea && io.MouseWheel != 0.0f) {
        // Mouse position relative to display center, in screen pixels
        ImVec2 mouseRelCenter = mousePosDisplay - displaySize * 0.5f;
        // Convert mouse position to UV coordinates *before* zoom change
        ImVec2 mouseUV = m_panOffsetUV + mouseRelCenter / (ImVec2((float)m_textureWidth, (float)m_textureHeight) * m_zoom);

        float oldZoom = m_zoom;
        m_zoom *= std::pow(m_zoomSpeed, io.MouseWheel);
        m_zoom = ImClamp(m_zoom, m_minZoom, m_maxZoom); // Clamp zoom

        // Keep the same UV coordinate under the mouse cursor after zoom
        // NewPan = MouseUV - (MouseScreenPosRelCenter / NewZoomedImageScreenSize)
        m_panOffsetUV = mouseUV - mouseRelCenter / (ImVec2((float)m_textureWidth, (float)m_textureHeight) * m_zoom);

        ClampPanOffset(containerSize); // Re-clamp after zoom
    }

    // 2. Panning (allow dragging anywhere on the canvas)
    if (isActiveOnCanvas && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
        if (!m_isDragging) {
            m_isDragging = true;
            m_dragStartMousePos = io.MousePos;
            m_dragStartPanOffsetUV = m_panOffsetUV;
        }
        ImVec2 mouseDelta = io.MousePos - m_dragStartMousePos;
        // Convert screen pixel delta to UV delta
        ImVec2 uvDelta = mouseDelta / (ImVec2((float)m_textureWidth, (float)m_textureHeight) * m_zoom);

        m_panOffsetUV = m_dragStartPanOffsetUV - uvDelta; // Subtract because dragging right moves content left (reduces UV offset)

        ClampPanOffset(containerSize); // Clamp during drag

    } else if (m_isDragging) {
        m_isDragging = false;
    }

    // --- UV Calculation ---
    // Calculate the size of the viewable rectangle in UV coordinates
    ImVec2 viewSizeUV = displaySize / (ImVec2((float)m_textureWidth, (float)m_textureHeight) * m_zoom);
    // Calculate top-left (uv0) and bottom-right (uv1) UV coordinates based on the centered pan offset
    ImVec2 uv0 = m_panOffsetUV - viewSizeUV * 0.5f;
    ImVec2 uv1 = m_panOffsetUV + viewSizeUV * 0.5f;

    // --- Draw the Image ---
    ImGui::SetCursorScreenPos(displayTopLeftScreen); // Position cursor for image drawing
    ImGui::Image((ImTextureID)(intptr_t)m_textureId, displaySize, uv0, uv1);

    ImGui::EndChild();
    ImGui::PopStyleColor(); // Pop ChildBg color
}

#endif // IMGUI_IMAGE_VIEWER_IMPLEMENTATION

#endif // IMGUI_IMAGE_VIEWER_H