#ifdef __APPLE__
#include <stdlib.h>
#include <stdio.h>
// Force the environment variable using putenv which is more reliable
// This must be set before any Objective-C classes are loaded
static const char* objc_disable_warning = "OBJC_DISABLE_CLASS_DUP_WARNING=YES";
static bool _init_env = (putenv((char*)objc_disable_warning), true);
#endif

#include <chrono>

// Add these definitions before any includes to prevent Windows API conflicts
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#define NOGDI
#define NOUSER

// Include Windows.h first to gain control over its namespace
#if defined(_WIN32) || defined(_WIN64) || defined(WIN32) || defined(WIN64)
#include <windows.h>
#endif

#include <functional>
#include <string>
#include <cmath>
#include <iostream>
// Include raylib directly but don't use its namespace
#include <raylib.h>
#include <mutex>  // For thread synchronization
#include <queue>  // For the thread-safe image queue

//#include "bella_sdk/bella_engine.h"
//#include "dl_core/dl_fs.h"

#include "../bella_engine_sdk/src/bella_sdk/bella_engine.h" // For rendering and scene creation in Bella
#include "../bella_engine_sdk/src/dl_core/dl_main.inl" // Core functionality from the Diffuse Logic engine
#include "../oom/oom_license.h" // common misc code
#include "../oom/oom_bella_misc.h"    // common misc code
#include "../oom/oom_misc.h"    // common misc code
#include "../oom/oom_bella_engine.h"    // common misc code

// Create namespace aliases for raylib types that conflict with bella_sdk
namespace rl {
    // Explicitly alias raylib types that conflict with bella_sdk
    using Image = ::Image;
    using Texture2D = ::Texture2D;
    using Vector2 = ::Vector2;
    using Color = ::Color;
    
    // Add function aliases - make sure to include ALL raylib functions you use
    using ::InitWindow;
    using ::CloseWindow;
    using ::IsWindowReady;
    using ::WindowShouldClose;
    using ::IsWindowResized;
    using ::GetScreenWidth;
    using ::GetScreenHeight;
    using ::SetTargetFPS;
    using ::SetConfigFlags;
    using ::BeginDrawing;
    using ::EndDrawing;
    using ::ClearBackground;
    using ::DrawText;
    using ::DrawTextureEx;
    using ::LoadImage;
    using ::UnloadImage;
    using ::LoadTextureFromImage;
    using ::UnloadTexture;
    using ::GetMousePosition;
    using ::GetMouseWheelMove;
    using ::IsMouseButtonPressed;
    using ::IsMouseButtonReleased;
    using ::ShowCursor;
}

// Define a callback type for receiving image data from the path tracer
// This creates a type alias called 'OnImageCallback' that represents a function that:
// 1. Takes image data (pointer to pixels), width, height, and number of color channels as parameters
// 2. Returns void (nothing)
// std::function is a flexible wrapper that can store any callable object (functions, lambdas, etc.)
using OnImageCallback = std::function<void(const unsigned char* data, int width, int height, int channels)>;

// Structure to hold image data in the queue
// This allows us to safely pass image data between threads
struct ImageData {
    unsigned char* data;
    int width;
    int height;
    int channels;
    
    ImageData(unsigned char* d, int w, int h, int c) 
        : data(d), width(w), height(h), channels(c) {}
};

class PathTracerPreview {
private:
    // Window properties
    int screenWidth;
    int screenHeight;
    const char* windowTitle;
    
    // Image/texture properties - use raylib's Texture2D
    rl::Texture2D texture;
    bool imageLoaded;
    float imageScale;
    
    // THREAD SAFETY: These members handle safe communication between threads
    // The mutex protects access to the queue, ensuring only one thread can modify it at a time
    std::mutex queueMutex;
    // The queue stores image data that needs to be processed by the main thread
    // This is crucial because OpenGL operations (like texture creation) must happen on the main thread
    std::queue<ImageData> imageQueue;
    
    // This member variable stores a function that will be called when new image data arrives
    // It will be set to a lambda function in the constructor
    OnImageCallback onImageCallback;
    
    // Mouse interaction properties
    bool orbiting = false;
    bool panning = false;
    float orbitSpeed = 0.5f;
    float panSpeed = 0.01f;
    rl::Vector2 prevMousePos = {0, 0};
    dl::bella_sdk::Engine* engine = nullptr;  // Reference to the bella engine for camera control
    
    // Store initial camera state for reset functionality
    bool hasInitialCamera = false;
    // We won't store the transform directly since the API doesn't support it
    // Instead, we'll just remember that we've initialized

public:
    PathTracerPreview(int width, int height, const char* title) 
        : screenWidth(width), screenHeight(height), windowTitle(title), 
          texture({0}), imageLoaded(false), imageScale(1.0f) {
        
        // Set configuration flags before initializing window
        rl::SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE);
        
        // Initialize window
        rl::InitWindow(screenWidth, screenHeight, windowTitle);
        
        // Check if window was initialized successfully
        if (!rl::IsWindowReady()) {
            std::cerr << "ERROR: Failed to initialize window" << std::endl;
            return;
        }
        
        // Set target FPS
        rl::SetTargetFPS(60);
        
        // THREAD SAFETY: Set up the callback that will be called by the path tracer
        // This creates a lambda function (an anonymous function) that captures 'this' pointer
        // so it can access the current instance's methods.
        // When this callback is invoked later with image data, it will call queueImageData
        // to safely pass the data between threads.
        onImageCallback = [this](const unsigned char* data, int width, int height, int channels) {
            this->queueImageData(data, width, height, channels);
        };
        
        //std::cout << "Window initialized successfully" << std::endl;
    }
    
    // Set the engine reference for camera control
    void setEngine(dl::bella_sdk::Engine* engineRef) {
        engine = engineRef;
        
        // Mark that we have an initial camera state
        /*
        if (engine) {
            storeInitialCameraTransform();
        }
        */
    }
    
    ~PathTracerPreview() {
        // Clean up resources
        if (texture.id != 0) rl::UnloadTexture(texture);
        
        // THREAD SAFETY: Clean up any remaining image data in the queue
        clearImageQueue();
        
        rl::CloseWindow();
    }
    
    // This method returns the callback function so other components can use it
    // The 'const' means this method doesn't modify the class state
    // It returns a copy of the callback function
    OnImageCallback getCallback() const {
        return onImageCallback;
    }
    
    // THREAD SAFETY: Queue image data to be processed by the main thread
    // This method can be safely called from any thread
    // It copies the image data and adds it to a thread-safe queue
    void queueImageData(const unsigned char* data, int width, int height, int channels) {
        if (!data || width <= 0 || height <= 0 || channels <= 0) {
            std::cerr << "ERROR: Invalid image data parameters" << std::endl;
            return;
        }
        
        // Create a copy of the data to ensure it remains valid even after the caller frees their copy
        size_t dataSize = width * height * channels;
        unsigned char* dataCopy = new unsigned char[dataSize];
        std::memcpy(dataCopy, data, dataSize);
        
        // THREAD SAFETY: Add to queue with lock to prevent race conditions
        {
            std::lock_guard<std::mutex> lock(queueMutex);
            imageQueue.push(ImageData(dataCopy, width, height, channels));
        }
    }
    
    // THREAD SAFETY: Process any queued image data - call this from the main thread
    // This is a key method that bridges between threads:
    // 1. The bella engine thread adds data to the queue via queueImageData()
    // 2. The main thread calls this method to safely retrieve and process that data
    void processImageQueue() {
        ImageData imageData(nullptr, 0, 0, 0);
        bool hasData = false;
        
        // THREAD SAFETY: Get the next image data from the queue with proper locking
        {
            std::lock_guard<std::mutex> lock(queueMutex);
            if (!imageQueue.empty()) {
                imageData = imageQueue.front();
                imageQueue.pop();
                hasData = true;
            }
        }
        
        // Process the image data if we got any
        // This happens in the main thread where OpenGL operations are safe
        if (hasData) {
            updateImage(imageData.data, imageData.width, imageData.height, imageData.channels);
            delete[] imageData.data; // Clean up the data copy
        }
    }
    
    // THREAD SAFETY: Clear the image queue
    // This ensures we don't leak memory if there are pending images when the program exits
    void clearImageQueue() {
        std::lock_guard<std::mutex> lock(queueMutex);
        while (!imageQueue.empty()) {
            ImageData& imageData = imageQueue.front();
            delete[] imageData.data;
            imageQueue.pop();
        }
    }
    
    // Update the displayed image with new data from the path tracer
    // IMPORTANT: This method must ONLY be called from the main thread
    // because it creates OpenGL textures which are context-dependent
    void updateImage(const unsigned char* data, int width, int height, int channels) {
        
        try {
            // Check if data is valid
            if (!data) {
                std::cerr << "ERROR: Data pointer is NULL" << std::endl;
                return;
            }
            
            // Unload existing texture if any
            if (texture.id != 0) {
                rl::UnloadTexture(texture);
                texture = {0};
            }
            
            // Create a copy of the data
            unsigned char* dataCopy = new unsigned char[width * height * 4]; // Always use 4 channels (RGBA)
            
            // Convert the data to RGBA format
            for (int i = 0; i < width * height; i++) {
                int srcIdx = i * channels;
                int destIdx = i * 4;
                
                if (channels >= 3) {
                    dataCopy[destIdx] = data[srcIdx];     // R
                    dataCopy[destIdx + 1] = data[srcIdx + 1]; // G
                    dataCopy[destIdx + 2] = data[srcIdx + 2]; // B
                    dataCopy[destIdx + 3] = (channels >= 4) ? data[srcIdx + 3] : 255; // A
                } else if (channels == 1) {
                    // Grayscale
                    dataCopy[destIdx] = dataCopy[destIdx + 1] = dataCopy[destIdx + 2] = data[srcIdx];
                    dataCopy[destIdx + 3] = 255;
                }
            }
            
            // Create a simple image using the raw data
            
            // Create a new image with the data
            rl::Image image = {0};
            image.data = dataCopy;
            image.width = width;
            image.height = height;
            image.mipmaps = 1;
            image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
            
            // THREAD SAFETY: Load texture from the image
            // This is now safe because we're in the main thread with valid OpenGL context
            // This was the source of our segfault when called from a different thread
            texture = rl::LoadTextureFromImage(image);
            
            // Unload the image data (texture keeps a copy)
            rl::UnloadImage(image);
            
            if (texture.id == 0) {
                std::cerr << "ERROR: Failed to create texture" << std::endl;
                return;
            }
            
            
            // Update display settings
            imageLoaded = true;
            
            // Calculate scale to fit the image in the window with some padding
            float scaleX = static_cast<float>(screenWidth - 40) / texture.width;
            float scaleY = static_cast<float>(screenHeight - 40) / texture.height;
            imageScale = (scaleX < scaleY) ? scaleX : scaleY;  // Use the smaller scale
            
        } catch (const std::exception& e) {
            std::cerr << "ERROR: Exception in updateImage: " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "ERROR: Unknown exception in updateImage" << std::endl;
        }
    }
    
    // Main loop - call this to run the preview window
    void run() {
        // Store previous window size to detect resizing
        int prevScreenWidth = screenWidth;
        int prevScreenHeight = screenHeight;
        
        while (!rl::WindowShouldClose()) {
            // THREAD SAFETY: Process any queued image data in the main thread
            // This is where we safely handle the image data that was queued by other threads
            processImageQueue();
            
            // Check if window has been resized
            if (rl::IsWindowResized()) {
                // Update screen dimensions
                screenWidth = rl::GetScreenWidth();
                screenHeight = rl::GetScreenHeight();
                
                // Recalculate image scale to fit the new window size
                if (imageLoaded && texture.id != 0) {
                    float scaleX = static_cast<float>(screenWidth - 40) / texture.width;
                    float scaleY = static_cast<float>(screenHeight - 40) / texture.height;
                    imageScale = (scaleX < scaleY) ? scaleX : scaleY;  // Use the smaller scale
                }
            }
            
            // Update
            if (imageLoaded) {
                // Allow zooming with mouse wheel
                float wheelMove = rl::GetMouseWheelMove();
                if (wheelMove != 0.0f) {
                    if (engine && engine->rendering()) {
                        dl::bella_sdk::Scene::EventScope eventScope(engine->scene());
                        // Create a Vec2 with y component only for dolly effect
                        dl::Vec2 dollyDelta;
                        dollyDelta.y = wheelMove * 0.8;
                        dl::bella_sdk::zoomCamera( engine->scene().cameraPath(), dollyDelta, true );
                    }
                }
                
                // Handle mouse interaction for camera orbiting
                handleMouseInteraction();
            }
            
            // Draw
            rl::BeginDrawing();
            
            rl::ClearBackground(RAYWHITE);
            
            if (imageLoaded && texture.id != 0) {
                // Draw the texture centered in the window
                rl::DrawTextureEx(
                    texture, 
                    { 
                        static_cast<float>(screenWidth)/2 - texture.width*imageScale/2, 
                        static_cast<float>(screenHeight)/2 - texture.height*imageScale/2 
                    }, 
                    0, imageScale, WHITE
                );
                
                // Display the current scale factor
                //DrawText(TextFormat("Scale: %.2fx", imageScale), 10, screenHeight - 30, 20, DARKGRAY);
                
                // Display orbit/pan status and controls
                //if (orbiting) {
                //    DrawText("Orbiting Camera", 10, screenHeight - 60, 20, RED);
                //} else if (panning) {
                //    DrawText("Panning Camera", 10, screenHeight - 60, 20, BLUE);
                //}
                
                // Display control instructions
                //DrawText("Mouse Controls:", 10, 10, 20, DARKGRAY);
                //DrawText("- Left Click + Drag: Orbit Camera", 10, 35, 18, DARKGRAY);
                //DrawText("- Middle Click + Drag: Pan Camera", 10, 60, 18, DARKGRAY);
                //DrawText("- Right Click: Reset Camera", 10, 85, 18, DARKGRAY);
                //DrawText("- Mouse Wheel: Zoom Image", 10, 110, 18, DARKGRAY);
                //DrawText("- Shift + Mouse Wheel: Dolly Camera", 10, 135, 18, DARKGRAY);
            } else {
                // No image loaded yet
                rl::DrawText("Waiting for Bella to render...", screenWidth/2 - 150, screenHeight/2 - 10, 20, DARKGRAY);
            }
            
            rl::EndDrawing();
        }
    }
    
    // Handle mouse interaction for camera orbiting
    void handleMouseInteraction() {
        // Only process mouse interaction if we have a valid engine reference
        if (!engine) return;
        
        // Check for mouse button press/release for orbiting (left button)
        if (rl::IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
            orbiting = true;
            panning = false;  // Ensure we're not doing both at once
            prevMousePos = rl::GetMousePosition();
        } else if (rl::IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
            orbiting = false;
        }
        
        // Check for mouse button press/release for panning (middle button)
        if (rl::IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) {
            panning = true;
            orbiting = false;  // Ensure we're not doing both at once
            prevMousePos = rl::GetMousePosition();
        } else if (rl::IsMouseButtonReleased(MOUSE_MIDDLE_BUTTON)) {
            panning = false;
        }
        
        // Check for right-click to reset camera
        /*if (rl::IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) {
            if (engine->rendering()) {
                // Use our custom reset camera function
                resetCamera();
            }
        }*/
        
        // Handle orbiting when left mouse is dragged
        if (orbiting) {
            rl::Vector2 currentMousePos = rl::GetMousePosition();
            
            // Calculate delta movement
            float deltaX = (currentMousePos.x - prevMousePos.x) * orbitSpeed;
            float deltaY = (currentMousePos.y - prevMousePos.y) * orbitSpeed;
            
            // Only orbit if there's actual movement
            if (deltaX != 0.0f || deltaY != 0.0f) {
                // Create a Vec2 for the delta (using bsdk's Vec2)
                // Fix: Initialize Vec2 properly according to its API
                dl::Vec2 delta;
                delta.x = deltaX;
                delta.y = deltaY;
                
                // Orbit the camera - similar to the C# implementation
                if (engine->rendering()) {
                    // Create an EventScope to batch scene updates efficiently
                    // This collects all scene changes within this scope and applies them together
                    // when the eventScope object is destroyed at the end of this block.
                    // It improves performance and ensures consistency of multiple scene changes.
                    dl::bella_sdk::Scene::EventScope eventScope(engine->scene());
                    // Use the bsdk namespace to avoid ambiguity with Path
                    dl::bella_sdk::orbitCamera(engine->scene().cameraPath(), delta);
                }
                
                // Update previous position for next frame
                prevMousePos = currentMousePos;
            }
        }
        
        // Handle panning when middle mouse is dragged
        if (panning) {
            rl::Vector2 currentMousePos = rl::GetMousePosition();
            
            // Calculate delta movement
            float deltaX = (currentMousePos.x - prevMousePos.x) * panSpeed;
            float deltaY = (currentMousePos.y - prevMousePos.y) * panSpeed;
            
            // Only pan if there's actual movement
            if (deltaX != 0.0f || deltaY != 0.0f) {
                // Create a Vec2 for the delta (using bsdk's Vec2)
                dl::Vec2 delta;
                delta.x = deltaX;
                delta.y = deltaY;
                
                if (engine->rendering()) {
                    // Create an EventScope to batch scene updates efficiently
                    // This collects all scene changes within this scope and applies them together
                    // when the eventScope object is destroyed at the end of this block.
                    // It improves performance and ensures consistency of multiple scene changes.
                    dl::bella_sdk::Scene::EventScope eventScope(engine->scene());
                    dl::bella_sdk::panCamera(engine->scene().cameraPath(), delta, true);
                }
                
                // Update previous position for next frame
                prevMousePos = currentMousePos;
            }
        }
    }
    
    // Simulate receiving data from the path tracer (for testing)
    void simulateDataFromPathTracer(const char* filename) {
        // Load an image from file - use raylib's Image type
        rl::Image image = rl::LoadImage(filename);
        if (image.data != NULL) {
            // Call our callback function with the image data to process it
            // This simulates what would happen when the path tracer produces an image
            // It determines the number of channels based on the image format
            onImageCallback(
                static_cast<const unsigned char*>(image.data),
                image.width,
                image.height,
                image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE ? 1 :
                image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA ? 2 :
                image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8 ? 3 : 4
            );
            rl::UnloadImage(image);
        }
    }
};

// Forward declaration
class PathTracerPreview;

// Custom override engine observer that connects bsdk's Image to our PathTracerPreview
struct BellaEngineObserver : public dl::bella_sdk::EngineObserver {
private:
    PathTracerPreview* preview;

public:
    BellaEngineObserver(PathTracerPreview* preview) : preview(preview) {}

    void onStarted(dl::String pass) override {
        dl::logInfo("Started pass %s", pass.buf());
    }
    
    void onStatus(dl::String pass, dl::String status) override {
        dl::logInfo("%s [%s]", status.buf(), pass.buf());
    }
    
    void onProgress(dl::String pass, dl::bella_sdk::Progress progress) override {
        dl::logInfo("%s [%s]", progress.toString().buf(), pass.buf());
    }
    
    // This is the key method that receives images from the bella engine
    // IMPORTANT: This method is called from the bella engine's thread, NOT the main thread
    void onImage(dl::String pass, dl::bella_sdk::Image image) override {
        dl::logInfo("Received image from bella: %d x %d", (int)image.width(), (int)image.height());
        
        // Get the dimensions of the image
        int width = (int)image.width();
        int height = (int)image.height();
        
        if (!preview) {
            std::cerr << "ERROR: Preview pointer is NULL" << std::endl;
            return;
        }
        
        try {
            // Create a buffer for our RGBA data
            unsigned char* buffer = new unsigned char[width * height * 4];
            
            // Get the raw RGBA data pointer - rgba8() returns Rgba8* (RgbaT<unsigned char>*)
            // No mutex needed as the developers confirmed the data survives within this callback
            dl::Rgba8* rgba_data = image.rgba8();
            
            if (!rgba_data) {
                std::cerr << "ERROR: rgba8() returned NULL" << std::endl;
                delete[] buffer;
                return;
            }
            
            // Direct memory copy for optimal performance
            std::memcpy(buffer, rgba_data, width * height * 4);
            
            // THREAD SAFETY: Use the callback to queue data for processing by the main thread
            try {
                if (preview->getCallback()) {
                    // Get the callback function from the preview object and invoke it
                    // This passes the image data to the main thread via the callback
                    // When called, this executes the lambda we defined in the constructor
                    preview->getCallback()(buffer, width, height, 4);
                    //std::cout << "Image data queued successfully" << std::endl;
                } else {
                    std::cerr << "ERROR: Preview callback is NULL" << std::endl;
                    delete[] buffer;
                }
            } catch (const std::exception& e) {
                std::cerr << "Exception in queueing image data: " << e.what() << std::endl;
                delete[] buffer;  // Clean up if queueing throws
            } catch (...) {
                std::cerr << "Unknown exception in queueing image data" << std::endl;
                delete[] buffer;  // Clean up if queueing throws
            }
        } catch (const std::exception& e) {
            std::cerr << "Exception in onImage: " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "Unknown exception in onImage" << std::endl;
        }
    }
    
    void onError(dl::String pass, dl::String msg) override {
        dl::logError("%s [%s]", msg.buf(), pass.buf());
    }
    
    void onStopped(dl::String pass) override {
        dl::logInfo("Stopped %s", pass.buf());
    }
};

int DL_main(dl::Args& args)
{
    int s_oomBellaLogContext = 0;
    dl::subscribeLog(&s_oomBellaLogContext, oom::bella::log);
    dl::flushStartupMessages();

    args.add("wd",  "watchdir",   "",   "watch directory for changes");
    args.add("tp",  "thirdparty",   "",   "prints third party licenses");
    args.add("li",  "licenseinfo",   "",   "prints license info");
    args.add("i",  "input",   "",   "prints license info");

    if (args.helpRequested()) {
        std::cout << args.help("poomer-efsw © 2025 Harvey Fong","", "1.0") << std::endl;
        return 0;
    }
    
    if (args.have("--licenseinfo")) {
        std::cout << "poomer-raylib-bella_onimage © 2025 Harvey Fong" << std::endl;
        std::cout << oom::license::printLicense() << std::endl;
        return 0;
    }
 
    if (args.have("--thirdparty")) {
        std::cout << oom::license::printBellaSDK() << "\n====\n" << std::endl;
        return 0;
    }
    auto belPath = dl::bella_sdk::previewPath();

    if (args.have("--input")) {
        belPath = args.value("--input");
        if (!dl::fs::exists(belPath)) {
            dl::logError("Input file %s does not exist", belPath.buf());
            return 1;
        }
    }

    try {
        SetTraceLogLevel(LOG_ERROR); 
        // Set raylib configuration flags before creating the window
        rl::SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE);
        
        PathTracerPreview preview(400, 400, "poomer-raylib-bella_onimage");
        if (!rl::IsWindowReady()) {
            std::cerr << "ERROR: Window initialization failed" << std::endl;
            return 1;
        }
        
        // Add a small delay to ensure the OpenGL context is fully set up
        //std::cout << "Waiting for OpenGL context to initialize..." << std::endl;
        for (int i = 0; i < 5; i++) {
            rl::BeginDrawing();
            rl::ClearBackground(RAYWHITE);
            rl::DrawText("Initializing...", 10, 10, 20, DARKGRAY);
            rl::EndDrawing();
        }
        //std::cout << "OpenGL context initialized" << std::endl;

        oom::misc::saveHDRI();

        // Initialize the bella engine
        dl::bella_sdk::Engine engine;
        engine.scene().loadDefs();
        engine.enableInteractiveMode();
        engine.enableDisplayTransform();
        
        // Pass the engine reference to the preview window for camera control
        preview.setEngine(&engine);
        
        // Create our custom observer and connect it to the preview window
        BellaEngineObserver engineObserver(&preview);
        engine.subscribe(&engineObserver);

        // Get the preview scene with material sphere
        if (belPath != "") {
            
            // Use the read method to load the scene, load left some cruft
            if (!engine.scene().read(belPath)) {
                dl::logError("Failed to read %s from %s", belPath.buf(), dl::fs::currentDir().buf());
                return 1;
            }
            
            if (!engine.start()) {
                dl::logError("Engine failed to start.");
                return 1;
            }
            
            // Store the initial camera state after the scene is loaded and engine started
            //preview.storeInitialCameraTransform();
            
        } else {
            // For testing, load a sample image, seems to have broke
            preview.simulateDataFromPathTracer("res/DayEnvironmentHDRI019_1K-TONEMAPPED.jpg");
        }

        // Run the preview window - this will block until the window is closed
        preview.run();
        
        // Clean up
        engine.stop();
        engine.unsubscribe(&engineObserver);
        
        return 0;
    } catch (const std::exception& e) {
        std::cerr << "FATAL ERROR: Exception in main: " << e.what() << std::endl;
        return 1;
    } catch (...) {
        std::cerr << "FATAL ERROR: Unknown exception in main" << std::endl;
        return 1;
    }
} 
